Compare commits

...
Author SHA1 Message Date
ohif-bot 7eb8c4c641 chore(release): publish [skip ci]
- @ohif/extension-vtk@1.7.15
 - @ohif/ui@1.5.7
 - @ohif/viewer@4.5.23
2020-11-02 10:35:07 +00:00
matthiasg 619f361510 fix: Reordered definitions in providers to prevent uninitialized var access (#2108) 2020-11-02 11:33:16 +01:00
ohif-bot 330eeaa216 chore(release): publish [skip ci]
- @ohif/extension-vtk@1.7.14
 - @ohif/viewer@4.5.22
2020-10-20 12:49:10 +00:00
James Petts d627dee3b2 Update crosshairs UI and fix bug where you could scroll offscreen. (#2126)
* Update crosshairs UI and fix bug where you could scroll offscreen.

* Update tests for removed rotate tool.
2020-10-20 14:45:52 +02:00
ohif-bot d8714ea9c4 chore(release): publish [skip ci]
- @ohif/extension-vtk@1.7.13
 - @ohif/viewer@4.5.21
2020-10-15 14:17:26 +00:00
James Petts 3c05920705 [IDC-2098] Update RVTKJSV (#2107)
* Update RVTKJSV

* Update react-vtkjs-viewport

* Fix WWWC tool with new rvtkjsv version
2020-10-15 16:15:28 +02:00
ohif-bot c6629bda75 chore(release): publish [skip ci]
- @ohif/extension-vtk@1.7.12
 - @ohif/viewer@4.5.20
2020-10-13 16:25:26 +00:00
James Petts 588bcc9d17 Throw error if data too large in WebGL 1 enabled browser (#2106) 2020-10-13 18:23:14 +02:00
ohif-bot c31f8f9d65 chore(release): publish [skip ci]
- @ohif/extension-vtk@1.7.11
 - @ohif/viewer@4.5.19
2020-10-13 13:06:12 +00:00
James Petts c0280e28ca Bring in rotatable crosshairs. (#2101)
* Bring in rotatable crosshairs.

* Pull in react-vtkjs-viewport 0.13.1 fix.
2020-10-13 15:00:01 +02:00
ohif-bot 7c3ac387ef chore(release): publish [skip ci]
- @ohif/extension-dicom-rt@0.4.9
 - @ohif/viewer@4.5.18
2020-10-12 08:26:15 +00:00
James Petts d6f79d9ddf [IDC-2063] Harden RTSTRUCT import (#2102)
* WIP

* Fix RTSTRUCT import with missing ContourImageSequence
2020-10-12 10:24:28 +02:00
ohif-bot 9448993cf0 chore(release): publish [skip ci]
- @ohif/extension-cornerstone@2.9.6
 - @ohif/extension-dicom-html@1.2.9
 - @ohif/extension-dicom-rt@0.4.8
 - @ohif/extension-dicom-segmentation@0.4.7
 - @ohif/extension-dicom-tag-browser@0.0.4
 - @ohif/extension-vtk@1.7.10
 - @ohif/core@2.10.7
 - @ohif/viewer@4.5.17
2020-10-07 10:54:55 +00:00
James Petts 1c556b4cc3 Update dcmjs version (#2094) 2020-10-07 12:53:30 +02:00
ohif-bot 94a9067fe3 chore(release): publish [skip ci]
- @ohif/extension-debugging@0.1.6
 - @ohif/extension-dicom-rt@0.4.7
 - @ohif/extension-vtk@1.7.9
 - @ohif/ui@1.5.6
 - @ohif/viewer@4.5.16
2020-10-06 09:51:18 +00:00
James Petts cfba4df70e IDC-2064 & IDC-2059 add more options to debug extension (#2069)
* Add more to debug extension.

* Update and comment out unit tests broke from v3.
2020-10-06 11:48:56 +02:00
ohif-bot 11c1d12872 chore(release): publish [skip ci]
- @ohif/extension-vtk@1.7.8
 - @ohif/core@2.10.6
 - @ohif/viewer@4.5.15
2020-09-30 18:03:17 +00:00
Ștefan Silviu-AlexandruandStefan Silviu 91364db882 fix: use SeriesMetadata method instead of property in findMostRecentStructuredReport (#2038)
fixes #1714

Call getInstanceCount() instead of manually checking length (which does not work)

Co-authored-by: Stefan Silviu <silviu.stefan@ab4.systems>
2020-09-30 20:01:07 +02:00
Nicolas Byl bea31d5fec fix: Enable port changes for Docker image using the PORT variable (#2016) 2020-09-30 19:55:28 +02:00
ohif-bot 48ecbc261a chore(release): publish [skip ci]
- @ohif/viewer@4.5.14
2020-09-30 10:19:10 +00:00
Woonchan ChoandWoonchan Cho 8bc467fb07 [IDC-2052] PWA start url (#2070)
Change start_url in manifest.json from "./index.html" to "./"

Co-authored-by: Woonchan Cho <wcho24@wustl.edu>
2020-09-30 12:16:18 +02:00
ohif-bot cffc44f0f4 chore(release): publish [skip ci]
- @ohif/extension-cornerstone@2.9.5
 - @ohif/viewer@4.5.13
2020-09-29 10:42:25 +00:00
James Petts ea0fc07e30 Update react-cornerstone-viewport for performance improvements (#2062)
* Update react-cornerstone-viewport

* Bump react-cornerstone-viewport version.

* Comment out tests which always cause problems.
2020-09-29 12:40:27 +02:00
ohif-bot b8d241fa33 chore(release): publish [skip ci]
- @ohif/extension-dicom-tag-browser@0.0.3
 - @ohif/viewer@4.5.12
2020-09-24 10:35:44 +00:00
James Petts ccdfcd4a85 [IDC-2049] Sort Tags in Tag browser split items in sequences, add indent after space. (#2053)
* Sort tag browser, add items, add indent.

* Remove debugger.
2020-09-24 12:34:22 +02:00
ohif-bot 6cf72d1757 chore(release): publish [skip ci]
- @ohif/extension-dicom-rt@0.4.6
 - @ohif/extension-dicom-segmentation@0.4.6
 - @ohif/viewer@4.5.11
2020-09-17 19:11:25 +00:00
James Petts bb1cdbda3a [IDC-1905] Hover tooltips for Segmentation + RTSTRUCT extension (#2044)
* WIP debug dialog

* Rename the p10 downloader extension to debugger extension, add button to toolbar. Deactivate it by default.

* Fix unit tests

* WIP

* WIP

* Finish mailTo

* tooltop
2020-09-17 21:09:46 +02:00
ohif-bot fab122dd8f chore(release): publish [skip ci]
- @ohif/extension-debugging@0.1.5
 - @ohif/extension-vtk@1.7.7
 - @ohif/ui@1.5.5
 - @ohif/viewer@4.5.10
2020-09-17 18:31:52 +00:00
James Petts 42803b0ae8 [IDC-2006] - optional mailTo for debugging extension. (#2027)
* WIP debug dialog

* Rename the p10 downloader extension to debugger extension, add button to toolbar. Deactivate it by default.

* Fix unit tests

* WIP

* WIP

* Finish mailTo
2020-09-17 20:28:08 +02:00
ohif-bot 5a78da8ff6 chore(release): publish [skip ci]
- @ohif/extension-dicom-html@1.2.8
 - @ohif/extension-dicom-microscopy@0.51.3
 - @ohif/extension-dicom-pdf@1.0.6
 - @ohif/extension-dicom-rt@0.4.5
 - @ohif/extension-dicom-segmentation@0.4.5
 - @ohif/extension-dicom-tag-browser@0.0.2
 - @ohif/extension-vtk@1.7.6
 - @ohif/core@2.10.5
 - @ohif/ui@1.5.4
 - @ohif/viewer@4.5.9
2020-09-10 10:17:12 +00:00
James Petts 0ef88e88eb [IDC-1129] - DICOM Tag Viewer extension (#2022)
* WIP render top level tags.

* Add drop down to select series.

* Fix errors with type 2 sequences.

* WIP swap instance.

* Fix formatting and make fullscreen.

* Remove debuggers.

* Finish formatting.

* Fix error with double SeriesNumber deconstruction.

* Address reviewer comments.
2020-09-10 12:13:24 +02:00
ohif-bot a7b5f1847a chore(release): publish [skip ci]
- @ohif/extension-debugging@0.1.4
 - @ohif/extension-dicom-rt@0.4.4
 - @ohif/extension-dicom-segmentation@0.4.4
 - @ohif/viewer@4.5.8
2020-09-10 09:14:18 +00:00
James Petts 5cb6e63b88 [IDC-1672] Highlight SEG segment/ RT structure when click to jump. (#2034)
* Highlight for RTSTRUCT.

* SEG temp crosshairs.

* Disable RT highlighting for now.

* Remove TODO
2020-09-10 11:11:59 +02:00
ohif-bot 48eb4a9ec3 chore(release): publish [skip ci]
- @ohif/viewer@4.5.7
2020-09-09 13:00:24 +00:00
James Petts d9231fd431 Split query parameter calls. (#2035) 2020-09-09 14:59:07 +02:00
ohif-bot e5b578bd8e chore(release): publish [skip ci]
- @ohif/extension-vtk@1.7.5
 - @ohif/core@2.10.4
 - @ohif/viewer@4.5.6
2020-09-03 13:51:56 +00:00
James Petts f38979037d IDC-1532 multiple series search google (#2026)
* Multiple series search for google cloud adapter.

* Revert IDC config.

* fix: 🐛 Series filtering on multiple series for google

* Revert changes to default config.

* Address reviewers comments.

* Fixed spelling mistakes
2020-09-03 15:48:32 +02:00
ohif-bot 45ca88f5b8 chore(release): publish [skip ci]
- @ohif/extension-cornerstone@2.9.4
 - @ohif/extension-debugging@0.1.3
 - @ohif/extension-dicom-html@1.2.7
 - @ohif/extension-dicom-microscopy@0.51.2
 - @ohif/extension-dicom-pdf@1.0.5
 - @ohif/extension-dicom-rt@0.4.3
 - @ohif/extension-dicom-segmentation@0.4.3
 - @ohif/extension-lesion-tracker@0.2.1
 - @ohif/extension-vtk@1.7.4
 - @ohif/core@2.10.3
 - @ohif/viewer@4.5.5
2020-09-03 11:00:46 +00:00
James Petts a1dee16a3d [IDC-1939] Debug Dialog part 1 (#2011)
* WIP debug dialog

* Rename the p10 downloader extension to debugger extension, add button to toolbar. Deactivate it by default.

* Fix unit tests
2020-09-03 12:58:04 +02:00
ohif-bot 6a37a2a661 chore(release): publish [skip ci]
- @ohif/extension-cornerstone@2.9.3
 - @ohif/extension-dicom-html@1.2.6
 - @ohif/extension-dicom-rt@0.4.2
 - @ohif/extension-dicom-segmentation@0.4.2
 - @ohif/extension-vtk@1.7.3
 - @ohif/core@2.10.2
 - @ohif/viewer@4.5.4
2020-09-02 12:19:23 +00:00
James Petts a8caf4bba4 [IDC-2017] Harden segmentation import to support more SEGs (#2024)
* fix: 🐛 Upgrade dcmjs version to support more SEGs
2020-09-02 14:17:30 +02:00
ohif-bot 29dcc82e28 chore(release): publish [skip ci]
- @ohif/extension-dicom-html@1.2.5
 - @ohif/extension-dicom-microscopy@0.51.1
 - @ohif/extension-dicom-pdf@1.0.4
 - @ohif/extension-dicom-rt@0.4.1
 - @ohif/extension-dicom-segmentation@0.4.1
 - @ohif/extension-vtk@1.7.2
 - @ohif/core@2.10.1
 - @ohif/viewer@4.5.3
2020-08-28 15:52:27 +00:00
James Petts dbf0501031 [IDC-1994] Sort series list by SeriesNumber, and sort by same SeriesNumber by date/time. (#2010)
* Sort based on SeriesNumber and SeriesDate/SeriesTime.

* Harden, and perform final sort in algorithm if last N entries have the same SeriesNumber.

* Switch to insertion rather than sorting as sorting is too slow. Reimplement low priority sorting into new insertion method.

* Fix local file viewing.
2020-08-28 17:51:08 +02:00
ohif-bot ef28fa2ac1 chore(release): publish [skip ci]
- @ohif/extension-vtk@1.7.1
 - @ohif/viewer@4.5.2
2020-08-24 09:45:34 +00:00
James Petts 779a7e0976 fix: 🐛 Fail gracefully on an MPR load error (#1992)
* feat: 🎸 Update react-vtkjs-viewport usage to use requestPool

* Fix import of react-vtkjs-viewport to cornerstone-tools path.

* Increase maximum load time of MPR test now we are throttling requests.

* fix: 🐛 Fail gracefully on an MPR load error

* Respond to reviewer comments.
2020-08-24 11:44:09 +02:00
ohif-bot 7f3ca1fc23 chore(release): publish [skip ci]
- @ohif/extension-cornerstone@2.9.2
 - @ohif/viewer@4.5.1
2020-08-20 14:50:30 +00:00
Alex BroaddusandDanny Brown 59ab68b4ed fix: Updated react-cornerstone-viewport to version 4.0.2 (#2001)
Co-authored-by: Danny Brown <danny.ri.brown@gmail.com>
2020-08-20 10:48:16 -04:00
ohif-bot 248bc0a64a chore(release): publish [skip ci]
- @ohif/viewer@4.5.0
2020-08-20 13:13:52 +00:00
ladeirarodolfoandRodolfo Ladeira 5fdace1432 feat: 🎸 Filter/promote multiple series instances (#1533)
improve filter/promote to be applied on multiple series instances

✅ Closes: 1532

Co-authored-by: Rodolfo Ladeira <rodolfo_radical@hotmail.com>
2020-08-20 15:12:00 +02:00
ohif-bot 71b6b454a3 chore(release): publish [skip ci]
- @ohif/extension-cornerstone@2.9.1
 - @ohif/viewer@4.4.1
2020-08-20 02:25:10 +00:00
Alex BroaddusandDanny Brown 3b02a06f5e perf(stackPrefetch): Added stackPrefetch config with 20 max concurrent requests (#2000)
Co-authored-by: Danny Brown <danny.ri.brown@gmail.com>
2020-08-19 22:23:00 -04:00
Erik Ziegler ebdcde1c4d fix: Fix incorrect command name in Percy test (#1999) 2020-08-19 15:42:25 -04:00
Erik Ziegler 829ac2b410 fix: Avoid lerna:restore unless we are on Netlify (closes #1926, #1996) (#1997) 2020-08-19 10:58:54 +02:00
Erik Ziegler 09745a0349 wip 2020-08-19 10:41:58 +02:00
Erik Ziegler cd73dd3027 fix: Avoid lerna:restore unless we are on Netlify (closes #1926, #1996) 2020-08-19 10:01:47 +02:00
ohif-bot ca511edeff chore(release): publish [skip ci]
- @ohif/extension-cornerstone@2.9.0
 - @ohif/extension-dicom-rt@0.4.0
 - @ohif/extension-dicom-segmentation@0.4.0
 - @ohif/extension-vtk@1.7.0
 - @ohif/core@2.10.0
 - @ohif/viewer@4.4.0
2020-08-18 12:03:55 +00:00
James PettsandErik Ziegler bb5f30ce2a feat: 🎸 Update react-vtkjs-viewport usage to use requestPool (#1984)
* feat: 🎸 Update react-vtkjs-viewport usage to use requestPool

* Fix import of react-vtkjs-viewport to cornerstone-tools path.

* Increase maximum load time of MPR test now we are throttling requests.

* Remove debugger

Co-authored-by: Erik Ziegler <erik.sweed@gmail.com>
2020-08-18 14:01:44 +02:00
ohif-bot 87d5c68e71 chore(release): publish [skip ci]
- @ohif/extension-dicom-rt@0.3.0
 - @ohif/viewer@4.3.1
2020-08-10 12:18:19 +00:00
James Petts 0e87ab37c2 feat: 🎸 Add support for POINT and OPEN_PLANAR for RT
* feat: 🎸 Add support for POINT and OPEN_PLANAR for RT
2020-08-10 14:16:19 +02:00
ohif-bot 756b005fe1 chore(release): publish [skip ci]
- @ohif/viewer@4.3.0
2020-08-10 09:42:47 +00:00
James Petts 3d86b5f876 feat: 🎸 Optional disable measurements panel in app config (#1912)
Can disable the measurements panel by adding disableMeasurementPanel:
true to the appConfig.

Closes: #1864
2020-08-10 11:40:11 +02:00
121 changed files with 3334 additions and 570 deletions

No files matched your search

+6
View File
@@ -28,6 +28,12 @@ if [ -n "$CLIENT_ID" ] || [ -n "$HEALTHCARE_API_ENDPOINT" ]
cp /usr/share/nginx/html/google.js /usr/share/nginx/html/app-config.js
fi
if [ -n "${PORT}" ]
then
echo "Changing port to ${PORT}..."
sed -i -e "s/listen 80/listen ${PORT}/g" /etc/nginx/conf.d/default.conf
fi
echo "Starting Nginx to serve the OHIF Viewer..."
exec "$@"
+1
View File
@@ -11,6 +11,7 @@ node -v
# Install build deps and all monorepo package dependencies. Yarn Workspaces
# should also symlink all projects appropriately
yarn run lerna:restore
yarn install --no-ignore-optional --pure-lockfile
# Build && Move PWA Output
+65
View File
@@ -3,6 +3,71 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [2.9.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.9.5...@ohif/extension-cornerstone@2.9.6) (2020-10-07)
**Note:** Version bump only for package @ohif/extension-cornerstone
## [2.9.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.9.4...@ohif/extension-cornerstone@2.9.5) (2020-09-29)
**Note:** Version bump only for package @ohif/extension-cornerstone
## [2.9.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.9.3...@ohif/extension-cornerstone@2.9.4) (2020-09-03)
**Note:** Version bump only for package @ohif/extension-cornerstone
## [2.9.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.9.2...@ohif/extension-cornerstone@2.9.3) (2020-09-02)
**Note:** Version bump only for package @ohif/extension-cornerstone
## [2.9.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.9.1...@ohif/extension-cornerstone@2.9.2) (2020-08-20)
### Bug Fixes
* Updated react-cornerstone-viewport to version 4.0.2 ([#2001](https://github.com/OHIF/Viewers/issues/2001)) ([59ab68b](https://github.com/OHIF/Viewers/commit/59ab68b4edbfec010170c4429c300a53f688bf94))
## [2.9.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.9.0...@ohif/extension-cornerstone@2.9.1) (2020-08-20)
### Performance Improvements
* **stackPrefetch:** Added stackPrefetch config with 20 max concurrent requests ([#2000](https://github.com/OHIF/Viewers/issues/2000)) ([3b02a06](https://github.com/OHIF/Viewers/commit/3b02a06f5e250660edde4862ce44147db3dc3ab9))
# [2.9.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.8.5...@ohif/extension-cornerstone@2.9.0) (2020-08-18)
### Features
* 🎸 Update react-vtkjs-viewport usage to use requestPool ([#1984](https://github.com/OHIF/Viewers/issues/1984)) ([bb5f30c](https://github.com/OHIF/Viewers/commit/bb5f30ce2a0192d2e021beaaadfff22fd38e17b9))
## [2.8.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.8.4...@ohif/extension-cornerstone@2.8.5) (2020-08-05)
**Note:** Version bump only for package @ohif/extension-cornerstone
+4 -4
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-cornerstone",
"version": "2.8.5",
"version": "2.9.6",
"description": "OHIF extension for Cornerstone",
"author": "OHIF",
"license": "MIT",
@@ -34,9 +34,9 @@
"@ohif/ui": "^0.50.0",
"cornerstone-core": "^2.2.8",
"cornerstone-math": "^0.1.8",
"cornerstone-tools": "4.15.1",
"cornerstone-tools": "^4.20.1",
"cornerstone-wado-image-loader": "^3.1.0",
"dcmjs": "0.16.0",
"dcmjs": "0.16.5",
"dicom-parser": "^1.8.3",
"hammerjs": "^2.0.8",
"prop-types": "^15.6.2",
@@ -51,6 +51,6 @@
"classnames": "^2.2.6",
"lodash.merge": "^4.6.2",
"lodash.throttle": "^4.1.1",
"react-cornerstone-viewport": "4.0.1"
"react-cornerstone-viewport": "4.0.4"
}
}
+2
View File
@@ -3,6 +3,7 @@ import init from './init.js';
import commandsModule from './commandsModule.js';
import toolbarModule from './toolbarModule.js';
import CornerstoneViewportDownloadForm from './CornerstoneViewportDownloadForm';
import { version } from '../package.json';
const Component = React.lazy(() => {
return import('./OHIFCornerstoneViewport');
@@ -24,6 +25,7 @@ export default {
* Only required property. Should be a unique value across all extensions.
*/
id: 'cornerstone',
version,
/**
*
@@ -34,4 +34,11 @@ export default function(configuration = {}) {
cornerstoneTools.toolColors.setActiveColor('rgb(0, 255, 0)');
cornerstoneTools.store.state.touchProximity = 40;
// Configure stack prefetch
cornerstoneTools.stackPrefetch.setConfiguration({
maxImagesToPrefetch: Infinity,
preserveExistingPool: false,
maxSimultaneousRequests: 20,
});
}
@@ -3,6 +3,38 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [0.1.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-debugging@0.1.5...@ohif/extension-debugging@0.1.6) (2020-10-06)
**Note:** Version bump only for package @ohif/extension-debugging
## [0.1.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-debugging@0.1.4...@ohif/extension-debugging@0.1.5) (2020-09-17)
**Note:** Version bump only for package @ohif/extension-debugging
## [0.1.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-debugging@0.1.3...@ohif/extension-debugging@0.1.4) (2020-09-10)
**Note:** Version bump only for package @ohif/extension-debugging
## 0.1.3 (2020-09-03)
**Note:** Version bump only for package @ohif/extension-debugging
## [0.1.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-p10-downloader@0.1.1...@ohif/extension-dicom-p10-downloader@0.1.2) (2020-06-18)
**Note:** Version bump only for package @ohif/extension-dicom-p10-downloader
File renamed without changes.
File renamed without changes.
@@ -1,7 +1,7 @@
{
"name": "@ohif/extension-dicom-p10-downloader",
"version": "0.1.2",
"description": "OHIF extension for downloading DICOM P10 files",
"name": "@ohif/extension-debugging",
"version": "0.1.6",
"description": "OHIF extension for debugging.",
"author": "OHIF",
"license": "MIT",
"repository": "OHIF/Viewers",
@@ -33,6 +33,7 @@
},
"dependencies": {
"@babel/runtime": "^7.5.5",
"detect-browser": "5.1.1",
"dicomweb-client": "^0.6.0",
"file-saver": "^2.0.2",
"jszip": "^3.2.2"
@@ -0,0 +1,14 @@
.debugReportModalHeader {
padding: 10px 0 10px;
color: var(--active-color);
}
.debug-report-modal-container {
display: flex;
flex-direction: column;
}
.debug-report-modal-buttons-container {
display: flex;
flex-direction: row;
}
@@ -0,0 +1,273 @@
import React from 'react';
import { detect } from 'detect-browser';
import './DebugReportModal.css';
import { ToolbarButton } from '@ohif/ui';
const DubugReportModal = ({
viewports,
studies,
servers,
extensionManager,
mailTo,
debugModalMessage,
}) => {
const copyDebugDataToClipboard = () => {
const body = getEmailBody();
const textArea = document.createElement('textarea');
textArea.value = body;
document.body.appendChild(textArea);
textArea.select();
document.execCommand('copy');
document.body.removeChild(textArea);
};
const getEmailBody = () => {
let body = `Enter the description of your problem here: \n\n\n`;
body += `============= SESSION INFO =============\n\n`;
// App version
body += '== App ==\n';
body += `version\t${window.version}\n\n`;
// Extensions Versions
body += '== Extensions Versions ==\n';
const { registeredExtensionVesions } = extensionManager;
Object.keys(registeredExtensionVesions).forEach(extensionId => {
const version = registeredExtensionVesions[extensionId];
body += `${extensionId}\t${version}\n`;
});
body += '\n';
// Browser Info
const browser = detect();
const { name, os, type, version } = browser;
body += '== Browser Info ==\n';
body += `name\t ${name}\n`;
body += `os\t ${os}\n`;
body += `type\t ${type}\n`;
body += `version\t ${version}\n\n`;
// Study URL
body += '== URL ==\n';
body += `URL\t ${window.location.href}\n\n`;
// Layout
const { numRows, numColumns, viewportSpecificData } = viewports;
body += '== Viewport Layout ==\n';
body += `Rows\t${numRows}\n`;
body += `Columns\t${numColumns}\n\n`;
body += '== Viewports ==\n';
Object.keys(viewportSpecificData).forEach(viewportIndex => {
const vsd = viewportSpecificData[viewportIndex];
const [row, column] = _viewportIndexToViewportPosition(
viewportIndex,
numColumns
);
body += `[${row},${column}]\t${vsd.SeriesInstanceUID}\n`;
});
return body;
};
const mailToFunction = () => {
const StudyInstanceUID = Object.keys(studies.studyData)[0];
const subject = encodeURI(`Issue with Study: ${StudyInstanceUID}`);
let body = getEmailBody();
// TODO Text dump of rest of stuff.
body = encodeURI(body);
window.location.href = `mailto:${mailTo}?subject=${subject}&body=${body}`;
};
return (
<div className="debug-report-modal-container">
{debugModalMessage ? <p>{debugModalMessage}</p> : null}
<div className="debug-report-modal-buttons-container">
{mailTo ? (
<div>
<ToolbarButton
label={'Send Bug Report'}
onClick={mailToFunction}
icon={'envelope-square'}
isActive={false}
/>
</div>
) : null}
<div>
<ToolbarButton
label={'Copy To Clipboard'}
onClick={copyDebugDataToClipboard}
icon={'clipboard'}
isActive={false}
/>
</div>
</div>
<div>
<table>
{getAppVersion()}
{getExtensionVersions(extensionManager)}
{getBrowserInfo()}
{getCurrentStudyUrl()}
{getLayout(viewports)}
</table>
</div>
</div>
);
};
const getAppVersion = () => {
return (
<React.Fragment>
<tr>
<th className="debugReportModalHeader">App</th>
</tr>
<tr>
<td>Version</td>
<td>{window.version}</td>
</tr>
</React.Fragment>
);
};
const getCurrentStudyUrl = () => {
return (
<React.Fragment>
<tr>
<th className="debugReportModalHeader">URL</th>
</tr>
<tr>
<td>URL</td>
<td>{window.location.href}</td>
</tr>
</React.Fragment>
);
};
const getExtensionVersions = extensionManager => {
const { registeredExtensionVesions } = extensionManager;
const lineItems = Object.keys(registeredExtensionVesions).map(extensionId => {
const version = registeredExtensionVesions[extensionId];
return (
<tr>
<td>{extensionId}</td>
<td>{version}</td>
</tr>
);
});
return (
<React.Fragment>
<th className="debugReportModalHeader">Extensions</th>
{lineItems}
</React.Fragment>
);
};
const getLayout = viewports => {
const { numRows, numColumns } = viewports;
return (
<React.Fragment>
<tr>
<th className="debugReportModalHeader">Viewports</th>
</tr>
<tr>
<th>Layout</th>
</tr>
<tr>
<td>Rows</td>
<td>{numRows}</td>
</tr>
<tr>
<td>Columns</td>
<td>{numColumns}</td>
</tr>
<tr>
<th>SeriesInstanceUIDs</th>
</tr>
{getSeriesInstanceUIDsPerRow(viewports)}
</React.Fragment>
);
};
const getBrowserInfo = () => {
const browser = detect();
const { name, os, type, version } = browser;
return (
<React.Fragment>
<tr>
<th className="debugReportModalHeader">Platform</th>
</tr>
<tr>
<td>name</td>
<td>{name}</td>
</tr>
<tr>
<td>os</td>
<td>{os}</td>
</tr>
<tr>
<td>type</td>
<td>{type}</td>
</tr>
<tr>
<td>version</td>
<td>{version}</td>
</tr>
</React.Fragment>
);
};
const getSeriesInstanceUIDsPerRow = viewports => {
const { viewportSpecificData, numColumns } = viewports;
// NOTE viewportSpecificData is actually an object with numerical keys.
return Object.keys(viewportSpecificData).map(viewportIndex => {
const vsd = viewportSpecificData[viewportIndex];
const [row, column] = _viewportIndexToViewportPosition(
viewportIndex,
numColumns
);
return (
<tr>
<td>{`[${row},${column}]`}</td>
<td>{vsd.SeriesInstanceUID}</td>
</tr>
);
});
};
const _viewportIndexToViewportPosition = (viewportIndex, numColumns) => {
const row = Math.floor(viewportIndex / numColumns);
const column = viewportIndex % numColumns;
return [row, column];
};
export default DubugReportModal;
@@ -8,12 +8,16 @@ import {
getSOPInstanceReferencesFromViewports,
} from './utils';
import _downloadAndZip, { downloadInstances } from './downloadAndZip';
import DebugReportModal from './DebugReportModal';
import React from 'react';
import state from './state';
const {
utils: { Queue },
} = OHIF;
export function getCommands(context) {
export function getCommands(context, servicesManager, extensionManager) {
const queue = new Queue(1);
const actions = {
/**
@@ -87,9 +91,34 @@ export function getCommands(context) {
serverConfig
);
},
openDebugInfoModal({ viewports, studies, servers }) {
const { UIModalService } = servicesManager.services;
const WrappedDebugReportModal = function() {
return (
<DebugReportModal
viewports={viewports}
studies={studies}
servers={servers}
extensionManager={extensionManager}
mailTo={state.mailTo}
debugModalMessage={state.debugModalMessage}
/>
);
};
UIModalService.show({
content: WrappedDebugReportModal,
title: `Debugging Information`,
});
},
};
const definitions = {
openDebugInfoModal: {
commandFn: actions.openDebugInfoModal,
storeContexts: ['viewports', 'servers', 'studies'],
},
downloadAndZip: {
commandFn: queue.bindSafe(actions.downloadAndZip, error),
storeContexts: ['servers'],
+61
View File
@@ -0,0 +1,61 @@
import { getDicomWebClientFromConfig } from './utils';
import { getCommands } from './commandsModule';
import { version } from '../package.json';
import toolbarModule from './toolbarModule';
import state from './state';
/**
* Constants
*/
/**
* Globals
*/
const sharedContext = {
dicomWebClient: null,
};
/**
* Extension
*/
export default {
/**
* Only required property. Should be a unique value across all extensions.
*/
id: 'dicom-p10-downloader',
version,
/**
* LIFECYCLE HOOKS
*/
preRegistration({ appConfig, configuration }) {
const dicomWebClient = getDicomWebClientFromConfig(appConfig);
if (dicomWebClient) {
sharedContext.dicomWebClient = dicomWebClient;
}
if (configuration) {
if (configuration.mailTo) {
state.mailTo = configuration.mailTo;
}
if (configuration.debugModalMessage) {
state.debugModalMessage = configuration.debugModalMessage;
}
}
},
/**
* MODULE GETTERS
*/
getCommandsModule({ servicesManager, extensionManager }) {
return getCommands(sharedContext, servicesManager, extensionManager);
},
getToolbarModule() {
return toolbarModule;
},
};
+3
View File
@@ -0,0 +1,3 @@
const state = { mailTo: undefined, message: undefined };
export default state;
+20
View File
@@ -0,0 +1,20 @@
const TOOLBAR_BUTTON_TYPES = {
COMMAND: 'command',
};
const definitions = [
{
id: 'Debug Info',
label: 'Debug Info',
icon: 'cog',
//
type: TOOLBAR_BUTTON_TYPES.COMMAND,
commandName: 'openDebugInfoModal',
context: 'VIEWER',
},
];
export default {
definitions,
defaultContext: 'VIEWER',
};
+40
View File
@@ -3,6 +3,46 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.2.9](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.2.8...@ohif/extension-dicom-html@1.2.9) (2020-10-07)
**Note:** Version bump only for package @ohif/extension-dicom-html
## [1.2.8](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.2.7...@ohif/extension-dicom-html@1.2.8) (2020-09-10)
**Note:** Version bump only for package @ohif/extension-dicom-html
## [1.2.7](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.2.6...@ohif/extension-dicom-html@1.2.7) (2020-09-03)
**Note:** Version bump only for package @ohif/extension-dicom-html
## [1.2.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.2.5...@ohif/extension-dicom-html@1.2.6) (2020-09-02)
**Note:** Version bump only for package @ohif/extension-dicom-html
## [1.2.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.2.4...@ohif/extension-dicom-html@1.2.5) (2020-08-28)
**Note:** Version bump only for package @ohif/extension-dicom-html
## [1.2.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.2.3...@ohif/extension-dicom-html@1.2.4) (2020-07-13)
**Note:** Version bump only for package @ohif/extension-dicom-html
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-dicom-html",
"version": "1.2.4",
"version": "1.2.9",
"description": "OHIF extension for rendering structured reports to HTML",
"author": "OHIF",
"license": "MIT",
@@ -29,7 +29,7 @@
},
"peerDependencies": {
"@ohif/core": "^0.50.0",
"dcmjs": "0.16.0",
"dcmjs": "0.16.5",
"prop-types": "^15.6.2",
"react": "^16.8.6",
"react-dom": "^16.8.6"
@@ -22,6 +22,14 @@ const OHIFDicomHtmlSopClassHandler = {
getDisplaySetFromSeries(series, study, dicomWebClient, authorizationHeaders) {
const instance = series.getFirstInstance();
const metadata = instance.getData().metadata;
const {
SeriesDescription,
SeriesNumber,
SeriesDate,
SeriesTime,
} = metadata;
return {
plugin: 'html',
Modality: 'SR',
@@ -31,6 +39,11 @@ const OHIFDicomHtmlSopClassHandler = {
SOPInstanceUID: instance.getSOPInstanceUID(),
SeriesInstanceUID: series.getSeriesInstanceUID(),
StudyInstanceUID: study.getStudyInstanceUID(),
SeriesDescription,
metadata,
SeriesDate,
SeriesTime,
SeriesNumber,
authorizationHeaders,
};
},
+2
View File
@@ -1,5 +1,6 @@
import React from 'react';
import OHIFDicomHtmlSopClassHandler from './OHIFDicomHtmlSopClassHandler.js';
import { version } from '../package.json';
const Component = React.lazy(() => {
return import('./OHIFDicomHtmlViewport');
@@ -18,6 +19,7 @@ export default {
* Only required property. Should be a unique value across all extensions.
*/
id: 'html',
version,
getViewportModule() {
return OHIFDicomHtmlViewport;
+24
View File
@@ -3,6 +3,30 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [0.51.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-microscopy@0.51.2...@ohif/extension-dicom-microscopy@0.51.3) (2020-09-10)
**Note:** Version bump only for package @ohif/extension-dicom-microscopy
## [0.51.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-microscopy@0.51.1...@ohif/extension-dicom-microscopy@0.51.2) (2020-09-03)
**Note:** Version bump only for package @ohif/extension-dicom-microscopy
## [0.51.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-microscopy@0.51.0...@ohif/extension-dicom-microscopy@0.51.1) (2020-08-28)
**Note:** Version bump only for package @ohif/extension-dicom-microscopy
# [0.51.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-microscopy@0.50.9...@ohif/extension-dicom-microscopy@0.51.0) (2020-06-04)
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-dicom-microscopy",
"version": "0.51.0",
"version": "0.51.3",
"description": "OHIF extension for Dicom Microscopy",
"author": "OHIF",
"license": "MIT",
@@ -12,6 +12,14 @@ const DicomMicroscopySopClassHandler = {
getDisplaySetFromSeries(series, study, dicomWebClient) {
const instance = series.getFirstInstance();
const metadata = instance.getData().metadata;
const {
SeriesDescription,
SeriesNumber,
ContentDate,
ContentTime,
} = metadata;
// Note: We are passing the dicomweb client into each viewport!
return {
@@ -22,6 +30,11 @@ const DicomMicroscopySopClassHandler = {
SOPInstanceUID: instance.getSOPInstanceUID(),
SeriesInstanceUID: series.getSeriesInstanceUID(),
StudyInstanceUID: study.getStudyInstanceUID(),
SeriesDescription,
SeriesDate: ContentDate, // Map ContentDate/Time to SeriesTime for series list sorting.
SeriesTime: ContentTime,
SeriesNumber,
metadata,
};
},
};
+2
View File
@@ -1,5 +1,6 @@
import React from 'react';
import DicomMicroscopySopClassHandler from './DicomMicroscopySopClassHandler.js';
import { version } from '../package.json';
const Component = React.lazy(() => {
return import('./DicomMicroscopyViewport');
@@ -18,6 +19,7 @@ export default {
* Only required property. Should be a unique value across all extensions.
*/
id: 'microscopy',
version,
getViewportModule() {
return DicomMicroscopyViewport;
@@ -1,43 +0,0 @@
import { getDicomWebClientFromConfig } from './utils';
import { getCommands } from './commandsModule';
/**
* Constants
*/
/**
* Globals
*/
const sharedContext = {
dicomWebClient: null,
};
/**
* Extension
*/
export default {
/**
* Only required property. Should be a unique value across all extensions.
*/
id: 'dicom-p10-downloader',
/**
* LIFECYCLE HOOKS
*/
preRegistration({ appConfig }) {
const dicomWebClient = getDicomWebClientFromConfig(appConfig);
if (dicomWebClient) {
sharedContext.dicomWebClient = dicomWebClient;
}
},
/**
* MODULE GETTERS
*/
getCommandsModule() {
return getCommands(sharedContext);
},
};
+24
View File
@@ -3,6 +3,30 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.0.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-pdf@1.0.5...@ohif/extension-dicom-pdf@1.0.6) (2020-09-10)
**Note:** Version bump only for package @ohif/extension-dicom-pdf
## [1.0.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-pdf@1.0.4...@ohif/extension-dicom-pdf@1.0.5) (2020-09-03)
**Note:** Version bump only for package @ohif/extension-dicom-pdf
## [1.0.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-pdf@1.0.3...@ohif/extension-dicom-pdf@1.0.4) (2020-08-28)
**Note:** Version bump only for package @ohif/extension-dicom-pdf
## [1.0.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-pdf@1.0.2...@ohif/extension-dicom-pdf@1.0.3) (2020-04-02)
**Note:** Version bump only for package @ohif/extension-dicom-pdf
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-dicom-pdf",
"version": "1.0.3",
"version": "1.0.6",
"description": "OHIF extension for Dicom PDF",
"author": "OHIF",
"license": "MIT",
@@ -12,6 +12,14 @@ const OHIFDicomPDFSopClassHandler = {
getDisplaySetFromSeries(series, study, dicomWebClient, authorizationHeaders) {
const instance = series.getFirstInstance();
const metadata = instance.getData().metadata;
const {
ContentDate,
ContentTime,
SeriesDescription,
SeriesNumber,
} = metadata;
return {
plugin: 'pdf',
Modality: 'DOC',
@@ -21,6 +29,11 @@ const OHIFDicomPDFSopClassHandler = {
SOPInstanceUID: instance.getSOPInstanceUID(),
SeriesInstanceUID: series.getSeriesInstanceUID(),
StudyInstanceUID: study.getStudyInstanceUID(),
SeriesDescription,
SeriesDate: ContentDate, // Map ContentDate/Time to SeriesTime for series list sorting.
SeriesTime: ContentTime,
SeriesNumber,
metadata,
authorizationHeaders: authorizationHeaders,
};
},
+2
View File
@@ -1,5 +1,6 @@
import React from 'react';
import OHIFDicomPDFSopClassHandler from './OHIFDicomPDFSopClassHandler.js';
import { version } from '../package.json';
const Component = React.lazy(() => {
return import('./ConnectedOHIFDicomPDFViewer');
@@ -18,6 +19,7 @@ export default {
* Only required property. Should be a unique value across all extensions.
*/
id: 'pdf',
version,
getViewportModule() {
return ConnectedOHIFDicomPDFViewer;
},
+94
View File
@@ -3,6 +3,100 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [0.4.9](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.4.8...@ohif/extension-dicom-rt@0.4.9) (2020-10-12)
**Note:** Version bump only for package @ohif/extension-dicom-rt
## [0.4.8](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.4.7...@ohif/extension-dicom-rt@0.4.8) (2020-10-07)
**Note:** Version bump only for package @ohif/extension-dicom-rt
## [0.4.7](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.4.6...@ohif/extension-dicom-rt@0.4.7) (2020-10-06)
**Note:** Version bump only for package @ohif/extension-dicom-rt
## [0.4.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.4.5...@ohif/extension-dicom-rt@0.4.6) (2020-09-17)
**Note:** Version bump only for package @ohif/extension-dicom-rt
## [0.4.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.4.4...@ohif/extension-dicom-rt@0.4.5) (2020-09-10)
**Note:** Version bump only for package @ohif/extension-dicom-rt
## [0.4.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.4.3...@ohif/extension-dicom-rt@0.4.4) (2020-09-10)
**Note:** Version bump only for package @ohif/extension-dicom-rt
## [0.4.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.4.2...@ohif/extension-dicom-rt@0.4.3) (2020-09-03)
**Note:** Version bump only for package @ohif/extension-dicom-rt
## [0.4.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.4.1...@ohif/extension-dicom-rt@0.4.2) (2020-09-02)
**Note:** Version bump only for package @ohif/extension-dicom-rt
## [0.4.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.4.0...@ohif/extension-dicom-rt@0.4.1) (2020-08-28)
**Note:** Version bump only for package @ohif/extension-dicom-rt
# [0.4.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.3.0...@ohif/extension-dicom-rt@0.4.0) (2020-08-18)
### Features
* 🎸 Update react-vtkjs-viewport usage to use requestPool ([#1984](https://github.com/OHIF/Viewers/issues/1984)) ([bb5f30c](https://github.com/OHIF/Viewers/commit/bb5f30ce2a0192d2e021beaaadfff22fd38e17b9))
# [0.3.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.2.7...@ohif/extension-dicom-rt@0.3.0) (2020-08-10)
### Features
* 🎸 Add support for POINT and OPEN_PLANAR for RT ([0e87ab3](https://github.com/OHIF/Viewers/commit/0e87ab37c29fcf9af74bbcefca854c6e6b8707bc))
## [0.2.7](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.2.6...@ohif/extension-dicom-rt@0.2.7) (2020-08-10)
**Note:** Version bump only for package @ohif/extension-dicom-rt
+6 -4
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-dicom-rt",
"version": "0.2.7",
"version": "0.4.9",
"description": "OHIF extension for rendering DICOM RTSTRUCTs on top of cornerstone images.",
"author": "OHIF",
"license": "MIT",
@@ -30,11 +30,13 @@
"peerDependencies": {
"@ohif/core": "^0.50.0",
"cornerstone-core": "^2.2.8",
"cornerstone-tools": "4.15.1",
"dcmjs": "0.16.0",
"cornerstone-tools": "^4.20.1",
"dcmjs": "0.16.5",
"gl-matrix": "^3.3.0",
"prop-types": "^15.6.2",
"react": "^16.8.6",
"react-dom": "^16.8.6"
"react-dom": "^16.8.6",
"react-tooltip": "4.2.10"
},
"dependencies": {
"@babel/runtime": "^7.5.5",
@@ -27,6 +27,7 @@ const OHIFDicomRTStructSopClassHandler = {
const {
SeriesDate,
SeriesTime,
SeriesNumber,
SeriesDescription,
FrameOfReferenceUID,
SOPInstanceUID,
@@ -53,7 +54,9 @@ const OHIFDicomRTStructSopClassHandler = {
isLoaded: false,
SeriesDate,
SeriesTime,
SeriesNumber,
SeriesDescription,
metadata,
};
if (!metadata.ReferencedSeriesSequence) {
@@ -12,6 +12,9 @@ import StructureSetItem from '../StructureSetItem/StructureSetItem';
import RTPanelSettings from '../RTSettings/RTSettings';
import PanelSection from '../PanelSection/PanelSection';
import LoadingIndicator from '../LoadingIndicator/LoadingIndicator';
import TOOL_NAMES from '../../utils/toolNames';
const { RTSTRUCT_DISPLAY_TOOL } = TOOL_NAMES;
const { studyMetadataManager } = utils;
@@ -144,13 +147,16 @@ const RTPanel = ({
if (isCornerstone()) {
const enabledElements = cornerstone.getEnabledElements();
const element = enabledElements[activeIndex].element;
const toolState = cornerstoneTools.getToolState(element, 'stack');
const stackToolState = cornerstoneTools.getToolState(
element,
'stack'
);
if (!toolState) {
if (!stackToolState) {
return;
}
const imageIds = toolState.data[0].imageIds;
const imageIds = stackToolState.data[0].imageIds;
const module = cornerstoneTools.getModule('rtstruct');
const imageId = module.getters.imageIdOfCenterFrameOfROIContour(
@@ -159,6 +165,18 @@ const RTPanel = ({
imageIds
);
const toolState = cornerstoneTools.globalImageIdSpecificToolStateManager.saveToolState();
const imageIdSpecificToolState = toolState[imageId];
const rtstructData =
imageIdSpecificToolState[RTSTRUCT_DISPLAY_TOOL];
const specificData = rtstructData.data.find(
rtData => rtData.ROINumber === ROINumber
);
specificData.highlight = true;
const frameIndex = imageIds.indexOf(imageId);
const SOPInstanceUID = cornerstone.metaData.get(
'SOPInstanceUID',
@@ -1,6 +1,7 @@
import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import { TableListItem, Icon, Tooltip, OverlayTrigger } from '@ohif/ui';
import ReactTooltip from 'react-tooltip';
import './StructureSetItem.css';
@@ -68,7 +69,18 @@ const StructureSetItem = ({
>
<div>
<div className="item-label" style={{ marginBottom: 4 }}>
<span>{label}</span>
<a data-tip data-for={`StructureHover${index}`}>
<span>{label}</span>
</a>
<ReactTooltip
id={`StructureHover${index}`}
delayShow={250}
place="right"
border={true}
type="light"
>
<span>{label}</span>
</ReactTooltip>
{!isDisabled && (
<Icon
className={`eye-icon ${isVisible && '--visible'}`}
+2 -1
View File
@@ -1,15 +1,16 @@
import React from 'react';
import init from './init.js';
import sopClassHandlerModule from './OHIFDicomRTStructSopClassHandler';
import id from './id.js';
import RTPanel from './components/RTPanel/RTPanel';
import { version } from '../package.json';
export default {
/**
* Only required property. Should be a unique value across all extensions.
*/
id,
version,
/**
*
+122 -29
View File
@@ -4,6 +4,7 @@ import cornerstone from 'cornerstone-core';
import cornerstoneTools from 'cornerstone-tools';
import transformPointsToImagePlane from './utils/transformPointsToImagePlane';
import TOOL_NAMES from './utils/toolNames';
import { vec3 } from 'gl-matrix';
const dicomlab2RGB = dcmjs.data.Colors.dicomlab2RGB;
const globalImageIdSpecificToolStateManager =
@@ -76,23 +77,24 @@ export default async function loadRTStruct(
const isSupported = false;
for (let c = 0; c < ContourSequence.length; c++) {
const ContourSequenceArray = _toArray(ContourSequence);
for (let c = 0; c < ContourSequenceArray.length; c++) {
const {
ContourImageSequence,
ContourData,
NumberOfContourPoints,
ContourGeometricType,
} = ContourSequence[c];
} = ContourSequenceArray[c];
if (ContourGeometricType !== 'CLOSED_PLANAR') {
// TODO: Do we want to visualise types other than closed planar?
// We could easily do open planar and point.
continue;
}
isSupported = true;
const sopInstanceUID = ContourImageSequence.ReferencedSOPInstanceUID;
const sopInstanceUID = ContourImageSequence
? ContourImageSequence.ReferencedSOPInstanceUID
: _getClosestSOPInstanceUID(
ContourData,
ContourGeometricType,
NumberOfContourPoints,
imageIdSopInstanceUidPairs
);
const imageId = _getImageId(imageIdSopInstanceUidPairs, sopInstanceUID);
const imageIdSpecificToolData = _getOrCreateImageIdSpecificToolData(
toolState,
@@ -102,26 +104,38 @@ export default async function loadRTStruct(
const imagePlane = cornerstone.metaData.get('imagePlaneModule', imageId);
const points = [];
let measurementData;
for (let p = 0; p < NumberOfContourPoints * 3; p += 3) {
points.push({
x: ContourData[p],
y: ContourData[p + 1],
z: ContourData[p + 2],
});
switch (ContourGeometricType) {
case 'CLOSED_PLANAR':
case 'OPEN_PLANAR':
case 'POINT':
isSupported = true;
for (let p = 0; p < NumberOfContourPoints * 3; p += 3) {
points.push({
x: ContourData[p],
y: ContourData[p + 1],
z: ContourData[p + 2],
});
}
transformPointsToImagePlane(points, imagePlane);
measurementData = {
handles: {
points,
},
type: ContourGeometricType,
structureSetSeriesInstanceUid: rtStructDataset.SeriesInstanceUID,
ROINumber: ReferencedROINumber,
};
imageIdSpecificToolData.push(measurementData);
break;
default:
continue;
}
transformPointsToImagePlane(points, imagePlane);
const measurementData = {
handles: {
points,
},
structureSetSeriesInstanceUid: rtStructDataset.SeriesInstanceUID,
ROINumber: ReferencedROINumber,
};
imageIdSpecificToolData.push(measurementData);
}
_setROIContourMetadata(
@@ -314,3 +328,82 @@ function _getImageIdSopInstanceUidPairsForDisplaySet(
};
});
}
function _toArray(objOrArray) {
return Array.isArray(objOrArray) ? objOrArray : [objOrArray];
}
function _getClosestSOPInstanceUID(
ContourData,
ContourGeometricType,
NumberOfContourPoints,
imageIdSopInstanceUidPairs
) {
const closest = {
distance: Infinity,
sopInstanceUID: null,
};
let point;
switch (ContourGeometricType) {
case 'POINT':
point = ContourData;
break;
case 'CLOSED_PLANAR':
case 'OPEN_PLANAR':
// These are defined as planar, so get the of the region to get the
// Best mapping to a plane even if its potentially off center.
point = [0, 0, 0];
for (let p = 0; p < NumberOfContourPoints * 3; p += 3) {
point[0] += ContourData[p];
point[1] += ContourData[p + 1];
point[2] += ContourData[p + 2];
}
point[0] /= NumberOfContourPoints;
point[1] /= NumberOfContourPoints;
point[2] /= NumberOfContourPoints;
}
imageIdSopInstanceUidPairs.forEach(pair => {
const { imageId } = pair;
const imagePlaneModule = cornerstone.metaData.get(
'imagePlaneModule',
imageId
);
const distance = distanceFromPointToPlane(point, imagePlaneModule);
if (distance < closest.distance) {
closest.distance = distance;
closest.sopInstanceUID = pair.sopInstanceUID;
}
});
return closest.sopInstanceUID;
}
/**
*
* @param {number[3]} P - The point
* @param {object} imagePlaneModule The cornerstone metadata object for the imagePlane
*/
function distanceFromPointToPlane(P, imagePlaneModule) {
const {
rowCosines,
columnCosines,
imagePositionPatient: Q,
} = imagePlaneModule;
let N = [];
vec3.cross(N, rowCosines, columnCosines);
const [A, B, C] = N;
const D = -A * Q[0] - B * Q[1] - C * Q[2];
return Math.abs(A * P[0] + B * P[1] + C * P[2] + D); // Denominator is sqrt(A**2 + B**2 + C**2) which is 1 as its a normal vector
}
@@ -1,9 +1,10 @@
import { importInternal, getToolState, toolColors } from 'cornerstone-tools';
import TOOL_NAMES from '../utils/toolNames';
import drawCanvasCrosshairs from '../utils/drawCanvasCrosshairs';
// Cornerstone 3rd party dev kit imports
const draw = importInternal('drawing/draw');
const drawCircle = importInternal('drawing/drawCircle');
const drawJoinedLines = importInternal('drawing/drawJoinedLines');
const getNewContext = importInternal('drawing/getNewContext');
const BaseTool = importInternal('base/BaseTool');
@@ -36,11 +37,17 @@ export default class RTStructDisplayTool extends BaseTool {
return;
}
const { lineWidth, opacity } = rtstructModule.configuration;
const {
lineWidth,
opacity,
highlightOpacity,
} = rtstructModule.configuration;
// We have tool data for this element - iterate over each one and draw it
const context = getNewContext(eventData.canvasContext.canvas);
let crossHairCenter;
for (let i = 0; i < toolState.data.length; i++) {
const data = toolState.data[i];
@@ -72,22 +79,76 @@ export default class RTStructDisplayTool extends BaseTool {
const colorArray = ROIContourData.colorArray;
const color = `rgba(${colorArray[0]},${colorArray[1]},${
colorArray[2]
},${opacity})`;
},${opacity})`;
lineWidth;
let highlight = data.highlight;
const options = { color, lineWidth };
draw(context, context => {
drawJoinedLines(
context,
eventData.element,
points[points.length - 1],
points,
{
color,
lineWidth,
}
);
if (highlight) {
crossHairCenter = { x: 0, y: 0 };
points.forEach(point => {
crossHairCenter.x += point.x;
crossHairCenter.y += point.y;
});
crossHairCenter.x /= points.length;
crossHairCenter.y /= points.length;
// TODO: Disabling hightlight for now, it'd be good to bring it back
// when we have a good way of doing this for SEG.
// options.fillStyle = color = `rgba(${colorArray[0]},${colorArray[1]},${
// colorArray[2]
// },${highlightOpacity})`;
// Draw highlight lines.
delete data.highlight; // Don't highlight on next render.
}
switch (data.type) {
case 'CLOSED_PLANAR':
this._renderClosedPlanar(context, eventData.element, points, options);
break;
case 'POINT':
this._renderPoint(context, eventData.element, points, options);
break;
case 'OPEN_PLANAR':
this._renderOpenPlanar(context, eventData.element, points, options);
break;
}
}
if (crossHairCenter) {
drawCanvasCrosshairs(eventData, crossHairCenter, {
color: toolColors.getActiveColor(),
lineWidth: 1,
});
}
}
_renderClosedPlanar(context, element, points, options) {
draw(context, context => {
drawJoinedLines(
context,
element,
points[points.length - 1],
points,
options
);
});
}
_renderPoint(context, element, points, options) {
draw(context, context => {
drawCircle(context, element, points[0], 3, options);
});
}
_renderOpenPlanar(context, element, points, options) {
draw(context, context => {
drawJoinedLines(context, element, points[0], points, options);
});
}
}
@@ -13,7 +13,8 @@ function refreshViewport() {
const configuration = {
lineWidth: 3,
opacity: 1.0,
opacity: 0.75,
highlightOpacity: 0.5,
};
const state = {
@@ -0,0 +1,56 @@
import cornerstone from 'cornerstone-core';
import cornerstoneTools from 'cornerstone-tools';
const { importInternal } = cornerstoneTools;
const draw = importInternal('drawing/draw');
const drawLine = importInternal('drawing/drawLine');
const getNewContext = importInternal('drawing/getNewContext');
export default function _drawCanvasCrosshairs(eventData, center, options) {
const context = getNewContext(eventData.canvasContext.canvas);
const { element } = eventData;
const centerCanvas = cornerstone.pixelToCanvas(element, center);
const { clientWidth: width, clientHeight: height } = element;
const offset = 10;
draw(context, context => {
drawLine(
context,
element,
{ x: centerCanvas.x + offset, y: centerCanvas.y },
{ x: width, y: centerCanvas.y },
options,
'canvas'
);
drawLine(
context,
element,
{ x: centerCanvas.x - offset, y: centerCanvas.y },
{ x: 0, y: centerCanvas.y },
options,
'canvas'
);
drawLine(
context,
element,
{ x: centerCanvas.x, y: centerCanvas.y + offset },
{ x: centerCanvas.x, y: height },
options,
'canvas'
);
drawLine(
context,
element,
{ x: centerCanvas.x, y: centerCanvas.y - offset },
{ x: centerCanvas.x, y: 0 },
options,
'canvas'
);
});
}
@@ -3,6 +3,73 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [0.4.7](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.4.6...@ohif/extension-dicom-segmentation@0.4.7) (2020-10-07)
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
## [0.4.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.4.5...@ohif/extension-dicom-segmentation@0.4.6) (2020-09-17)
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
## [0.4.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.4.4...@ohif/extension-dicom-segmentation@0.4.5) (2020-09-10)
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
## [0.4.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.4.3...@ohif/extension-dicom-segmentation@0.4.4) (2020-09-10)
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
## [0.4.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.4.2...@ohif/extension-dicom-segmentation@0.4.3) (2020-09-03)
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
## [0.4.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.4.1...@ohif/extension-dicom-segmentation@0.4.2) (2020-09-02)
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
## [0.4.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.4.0...@ohif/extension-dicom-segmentation@0.4.1) (2020-08-28)
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
# [0.4.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.3.4...@ohif/extension-dicom-segmentation@0.4.0) (2020-08-18)
### Features
* 🎸 Update react-vtkjs-viewport usage to use requestPool ([#1984](https://github.com/OHIF/Viewers/issues/1984)) ([bb5f30c](https://github.com/OHIF/Viewers/commit/bb5f30ce2a0192d2e021beaaadfff22fd38e17b9))
## [0.3.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.3.3...@ohif/extension-dicom-segmentation@0.3.4) (2020-08-10)
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
+5 -4
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-dicom-segmentation",
"version": "0.3.4",
"version": "0.4.7",
"description": "OHIF extension for viewing segmentations in the 2D MPR view",
"author": "OHIF",
"license": "MIT",
@@ -30,8 +30,8 @@
"peerDependencies": {
"@ohif/core": "^0.50.0",
"cornerstone-core": "^2.2.8",
"cornerstone-tools": "4.15.1",
"dcmjs": "0.16.0",
"cornerstone-tools": "^4.20.1",
"dcmjs": "0.16.5",
"prop-types": "^15.6.2",
"react": "^16.8.6",
"react-dom": "^16.8.6"
@@ -39,6 +39,7 @@
"dependencies": {
"@babel/runtime": "^7.5.5",
"gl-matrix": "^3.3.0",
"react-select": "^3.0.8"
"react-select": "^3.0.8",
"react-tooltip": "4.2.10"
}
}
@@ -1,6 +1,7 @@
import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import { TableListItem, Icon } from '@ohif/ui';
import ReactTooltip from 'react-tooltip';
import './SegmentItem.css';
@@ -17,7 +18,15 @@ ColoredCircle.propTypes = {
color: PropTypes.array.isRequired,
};
const SegmentItem = ({ index, label, onClick, itemClass, color, visible = true, onVisibilityChange }) => {
const SegmentItem = ({
index,
label,
onClick,
itemClass,
color,
visible = true,
onVisibilityChange,
}) => {
const [isVisible, setIsVisible] = useState(visible);
useEffect(() => {
@@ -37,7 +46,18 @@ const SegmentItem = ({ index, label, onClick, itemClass, color, visible = true,
>
<div>
<div className="segment-label" style={{ marginBottom: 4 }}>
<span>{label}</span>
<a data-tip data-for={`SegmentHover${index}`}>
<span>{label}</span>
</a>
<ReactTooltip
id={`SegmentHover${index}`}
delayShow={250}
place="right"
border={true}
type="light"
>
<span>{label}</span>
</ReactTooltip>
<Icon
className={`eye-icon ${isVisible && '--visible'}`}
name={isVisible ? 'eye' : 'eye-closed'}
@@ -61,7 +81,7 @@ const SegmentItem = ({ index, label, onClick, itemClass, color, visible = true,
<span style={{ marginRight: '4px' }}>
<Icon name="edit" width="14px" height="14px" />
</span>
Relabel
Relabel
</button>
<button
className="btnAction"
@@ -70,7 +90,7 @@ const SegmentItem = ({ index, label, onClick, itemClass, color, visible = true,
<span style={{ marginRight: '4px' }}>
<Icon name="edit" width="14px" height="14px" />
</span>
Description
Description
</button>
</div>
)}
@@ -90,7 +110,7 @@ SegmentItem.propTypes = {
SegmentItem.defaultProps = {
itemClass: '',
onClick: () => { }
onClick: () => {},
};
export default SegmentItem;
@@ -5,6 +5,7 @@ import cornerstone from 'cornerstone-core';
import moment from 'moment';
import { utils, log } from '@ohif/core';
import { ScrollableArea, TableList, Icon } from '@ohif/ui';
import DICOMSegTempCrosshairsTool from '../../tools/DICOMSegTempCrosshairsTool';
import setActiveLabelmap from '../../utils/setActiveLabelMap';
import refreshViewports from '../../utils/refreshViewports';
@@ -334,6 +335,12 @@ const SegmentationPanel = ({
imageId
);
DICOMSegTempCrosshairsTool.addCrosshair(
element,
imageId,
segmentNumber
);
onSegmentItemClick({
StudyInstanceUID,
SOPInstanceUID,
@@ -33,6 +33,7 @@ export default function getSopClassHandlerModule({ servicesManager }) {
SOPInstanceUID,
SeriesInstanceUID,
StudyInstanceUID,
SeriesNumber,
} = metadata;
const segDisplaySet = {
@@ -52,7 +53,9 @@ export default function getSopClassHandlerModule({ servicesManager }) {
isLoaded: false,
SeriesDate,
SeriesTime,
SeriesNumber,
SeriesDescription,
metadata,
};
segDisplaySet.getSourceDisplaySet = function(studies) {
@@ -1,4 +1,7 @@
import setActiveLabelmap from './utils/setActiveLabelMap';
import { classes } from '@ohif/core';
const { ImageSet } = classes;
export default function getSourceDisplaySet(studies, segDisplaySet) {
const referencedDisplaySet = _getReferencedDisplaySet(segDisplaySet, studies);
@@ -19,15 +22,43 @@ const _getReferencedDisplaySet = (segDisplaySet, studies) => {
ds => ds.displaySetInstanceUID !== segDisplaySet.displaySetInstanceUID
);
const ReferencedSeriesSequence = Array.isArray(
segDisplaySet.metadata.ReferencedSeriesSequence
)
? segDisplaySet.metadata.ReferencedSeriesSequence
: [segDisplaySet.metadata.ReferencedSeriesSequence];
const { metadata } = segDisplaySet;
const referencedSeriesInstanceUIDs = ReferencedSeriesSequence.map(
ReferencedSeries => ReferencedSeries.SeriesInstanceUID
);
let referencedSeriesInstanceUIDs;
if (metadata.ReferencedSeriesSequence) {
const ReferencedSeriesSequence = _toArray(
metadata.ReferencedSeriesSequence
);
referencedSeriesInstanceUIDs = ReferencedSeriesSequence.map(
ReferencedSeries => ReferencedSeries.SeriesInstanceUID
);
} else {
const { PerFrameFunctionalGroupsSequence } = metadata;
let SourceImageSequence;
if (metadata.SourceImageSequence) {
SourceImageSequence = metadata.SourceImageSequence;
} else {
const firstFunctionalGroups = _toArray(
PerFrameFunctionalGroupsSequence
)[0];
const { DerivationImageSequence } = firstFunctionalGroups;
SourceImageSequence = DerivationImageSequence;
}
const firstSourceImage = _toArray(SourceImageSequence)[0];
const { ReferencedSOPInstanceUID } = firstSourceImage;
referencedSeriesInstanceUIDs = _findReferencedSeriesInstanceUIDsFromSOPInstanceUID(
otherDisplaySets,
ReferencedSOPInstanceUID
);
}
const referencedDisplaySet = otherDisplaySets.find(ds =>
referencedSeriesInstanceUIDs.includes(ds.SeriesInstanceUID)
@@ -35,3 +66,23 @@ const _getReferencedDisplaySet = (segDisplaySet, studies) => {
return referencedDisplaySet;
};
const _findReferencedSeriesInstanceUIDsFromSOPInstanceUID = (
displaySets,
SOPInstanceUID
) => {
const imageSets = displaySets.filter(ds => ds instanceof ImageSet);
for (let i = 0; i < imageSets.length; i++) {
const { images } = imageSets[i];
for (let j = 0; j < images.length; j++) {
if (images[j].SOPInstanceUID === SOPInstanceUID) {
return [images[j].getData().metadata.SeriesInstanceUID];
}
}
}
};
function _toArray(arrayOrObject) {
return Array.isArray(arrayOrObject) ? arrayOrObject : [arrayOrObject];
}
+2 -1
View File
@@ -1,15 +1,16 @@
import React from 'react';
import init from './init.js';
import toolbarModule from './toolbarModule.js';
import getSopClassHandlerModule from './getOHIFDicomSegSopClassHandler.js';
import SegmentationPanel from './components/SegmentationPanel/SegmentationPanel.js';
import { version } from '../package.json';
export default {
/**
* Only required property. Should be a unique value across all extensions.
*/
id: 'com.ohif.dicom-segmentation',
version,
/**
*
@@ -1,4 +1,5 @@
import csTools from 'cornerstone-tools';
import DICOMSegTempCrosshairsTool from './tools/DICOMSegTempCrosshairsTool';
/**
*
@@ -17,4 +18,6 @@ export default function init({ servicesManager, configuration = {} }) {
alwaysEraseOnClick: true,
},
});
csTools.addTool(DICOMSegTempCrosshairsTool);
}
@@ -0,0 +1,127 @@
import cornerstoneTools, {
importInternal,
getToolState,
toolColors,
getModule,
globalImageIdSpecificToolStateManager,
} from 'cornerstone-tools';
import cornerstone from 'cornerstone-core';
import drawCanvasCrosshairs from '../utils/drawCanvasCrosshairs';
import TOOL_NAMES from './TOOL_NAMES';
const { DICOM_SEG_TEMP_CROSSHAIRS_TOOL } = TOOL_NAMES;
const { getters } = getModule('segmentation');
// Cornerstone 3rd party dev kit imports
const BaseTool = importInternal('base/BaseTool');
/**
* @class RTStructDisplayTool - Renders RTSTRUCT data in a read only manner (i.e. as an overlay).
* @extends cornerstoneTools.BaseTool
*/
export default class DICOMSegTempCrosshairsTool extends BaseTool {
constructor(props = {}) {
const defaultProps = {
mixins: ['enabledOrDisabledBinaryTool'],
name: DICOM_SEG_TEMP_CROSSHAIRS_TOOL,
};
const initialProps = Object.assign(defaultProps, props);
super(initialProps);
this._rtStructModule = cornerstoneTools.getModule('rtstruct');
}
renderToolData(evt) {
const eventData = evt.detail;
const { element } = eventData;
const toolState = getToolState(evt.currentTarget, this.name);
if (!toolState) {
return;
}
// We have tool data for this element - iterate over each one and draw it
for (let i = 0; i < toolState.data.length; i++) {
const data = toolState.data[i];
const crossHairCenter = data.center;
drawCanvasCrosshairs(eventData, crossHairCenter, {
color: toolColors.getActiveColor(),
lineWidth: 1,
});
// Remove the crosshairs, we only render them for one redraw.
toolState.data.pop();
}
}
}
DICOMSegTempCrosshairsTool.addCrosshair = (element, imageId, segmentNumber) => {
const labelmap3D = getters.labelmap3D(element);
const stackToolState = cornerstoneTools.getToolState(element, 'stack');
const enabledElement = cornerstone.getEnabledElement(element);
const { rows, columns } = enabledElement.image;
if (!stackToolState) {
return;
}
const imageIds = stackToolState.data[0].imageIds;
const imageIdIndex = imageIds.findIndex(imgId => imgId === imageId);
const labelmap2D = labelmap3D.labelmaps2D[imageIdIndex];
const { pixelData } = labelmap2D;
let xCenter = 0;
let yCenter = 0;
let count = 0;
for (let y = 0; y < rows; y++) {
for (let x = 0; x < columns; x++) {
if (pixelData[y * columns + x] === segmentNumber) {
count++;
xCenter += x + 0.5;
yCenter += y + 0.5;
}
}
}
xCenter /= count;
yCenter /= count;
const globalToolState = globalImageIdSpecificToolStateManager.saveToolState();
if (!globalToolState[imageId]) {
globalToolState[imageId] = {};
}
const imageIdSpecificToolState = globalToolState[imageId];
if (!imageIdSpecificToolState[DICOM_SEG_TEMP_CROSSHAIRS_TOOL]) {
imageIdSpecificToolState[DICOM_SEG_TEMP_CROSSHAIRS_TOOL] = { data: [] };
} else if (!imageIdSpecificToolState[DICOM_SEG_TEMP_CROSSHAIRS_TOOL].data) {
imageIdSpecificToolState[DICOM_SEG_TEMP_CROSSHAIRS_TOOL].data = [];
}
const toolSpecificData =
imageIdSpecificToolState[DICOM_SEG_TEMP_CROSSHAIRS_TOOL].data;
toolSpecificData.push({ center: { x: xCenter, y: yCenter }, segmentNumber });
// Enable the tool if not enabled for the element.
const tool = cornerstoneTools.getToolForElement(
element,
DICOM_SEG_TEMP_CROSSHAIRS_TOOL
);
if (tool.mode !== 'enabled') {
// If not already active or passive, set passive so contours render.
cornerstoneTools.setToolEnabled(DICOM_SEG_TEMP_CROSSHAIRS_TOOL);
}
};
@@ -0,0 +1,5 @@
const TOOL_NAMES = {
DICOM_SEG_TEMP_CROSSHAIRS_TOOL: 'DICOMSegTempCrosshairsTool',
};
export default TOOL_NAMES;
@@ -0,0 +1,56 @@
import cornerstone from 'cornerstone-core';
import cornerstoneTools from 'cornerstone-tools';
const { importInternal } = cornerstoneTools;
const draw = importInternal('drawing/draw');
const drawLine = importInternal('drawing/drawLine');
const getNewContext = importInternal('drawing/getNewContext');
export default function _drawCanvasCrosshairs(eventData, center, options) {
const context = getNewContext(eventData.canvasContext.canvas);
const { element } = eventData;
const centerCanvas = cornerstone.pixelToCanvas(element, center);
const { clientWidth: width, clientHeight: height } = element;
const offset = 10;
draw(context, context => {
drawLine(
context,
element,
{ x: centerCanvas.x + offset, y: centerCanvas.y },
{ x: width, y: centerCanvas.y },
options,
'canvas'
);
drawLine(
context,
element,
{ x: centerCanvas.x - offset, y: centerCanvas.y },
{ x: 0, y: centerCanvas.y },
options,
'canvas'
);
drawLine(
context,
element,
{ x: centerCanvas.x, y: centerCanvas.y + offset },
{ x: centerCanvas.x, y: height },
options,
'canvas'
);
drawLine(
context,
element,
{ x: centerCanvas.x, y: centerCanvas.y - offset },
{ x: centerCanvas.x, y: 0 },
options,
'canvas'
);
});
}
@@ -0,0 +1,8 @@
const path = require('path');
const webpackCommon = require('./../../../.webpack/webpack.commonjs.js');
const SRC_DIR = path.join(__dirname, '../src');
const DIST_DIR = path.join(__dirname, '../dist');
module.exports = (env, argv) => {
return webpackCommon(env, argv, { SRC_DIR, DIST_DIR });
};
@@ -0,0 +1,38 @@
const merge = require('webpack-merge');
const path = require('path');
const webpackCommon = require('./../../../.webpack/webpack.commonjs.js');
const pkg = require('./../package.json');
const ROOT_DIR = path.join(__dirname, './..');
const SRC_DIR = path.join(__dirname, '../src');
const DIST_DIR = path.join(__dirname, '../dist');
module.exports = (env, argv) => {
const commonConfig = webpackCommon(env, argv, { SRC_DIR, DIST_DIR });
return merge(commonConfig, {
devtool: 'source-map',
stats: {
colors: true,
hash: true,
timings: true,
assets: true,
chunks: false,
chunkModules: false,
modules: false,
children: false,
warnings: true,
},
optimization: {
minimize: true,
sideEffects: true,
},
output: {
path: ROOT_DIR,
library: 'OHIFExtDicomP10Downloader',
libraryTarget: 'umd',
libraryExport: 'default',
filename: pkg.main,
},
});
};
+24
View File
@@ -0,0 +1,24 @@
# Change Log
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [0.0.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-tag-browser@0.0.3...@ohif/extension-dicom-tag-browser@0.0.4) (2020-10-07)
**Note:** Version bump only for package @ohif/extension-dicom-tag-browser
## [0.0.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-tag-browser@0.0.2...@ohif/extension-dicom-tag-browser@0.0.3) (2020-09-24)
**Note:** Version bump only for package @ohif/extension-dicom-tag-browser
## 0.0.2 (2020-09-10)
**Note:** Version bump only for package @ohif/extension-dicom-tag-browser
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 Open Health Imaging Foundation
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+1
View File
@@ -0,0 +1 @@
# @ohif/extension-dicom-tag-browser
+40
View File
@@ -0,0 +1,40 @@
{
"name": "@ohif/extension-dicom-tag-browser",
"version": "0.0.4",
"description": "OHIF extension for checking DICOM headers.",
"author": "OHIF",
"license": "MIT",
"repository": "OHIF/Viewers",
"main": "dist/index.umd.js",
"module": "src/index.js",
"publishConfig": {
"access": "public"
},
"engines": {
"node": ">=10",
"npm": ">=6",
"yarn": ">=1.16.0"
},
"files": [
"dist",
"README.md"
],
"scripts": {
"dev": "cross-env NODE_ENV=development webpack --config .webpack/webpack.dev.js --watch --debug --output-pathinfo",
"build": "cross-env NODE_ENV=production webpack --config .webpack/webpack.prod.js",
"build:package": "yarn run build",
"prepublishOnly": "yarn run build",
"start": "yarn run dev"
},
"peerDependencies": {
"@ohif/core": "^2.6.0",
"dcmjs": "0.16.5",
"react": "^16.8.6"
},
"dependencies": {
"@babel/runtime": "^7.5.5",
"dicomweb-client": "^0.6.0",
"moment": "2.24.0",
"react-select": "^3.0.8"
}
}
@@ -0,0 +1,59 @@
import React from 'react';
import Select from 'react-select';
const DicomBrowserSelect = ({ value, formatOptionLabel, options }) => (
<Select
value={value}
formatOptionLabel={formatOptionLabel}
styles={dicomBrowserSelectStyles}
options={options}
/>
);
const computedstyle = getComputedStyle(document.body);
const uiGrayDarker = computedstyle.getPropertyValue('--ui-gray-darker');
const activeColor = computedstyle.getPropertyValue('--active-color');
const defaultColor = computedstyle.getPropertyValue('--default-color');
const uiGrayDark = computedstyle.getPropertyValue('--ui-gray-dark');
const dicomBrowserSelectStyles = {
singleValue: (base, state) => ({
...base,
width: '100%',
}),
control: (base, state) => ({
...base,
cursor: 'pointer',
background: uiGrayDarker,
borderRadius: state.isFocused ? '5px 5px 5px 5px' : 5,
borderColor: state.isFocused ? activeColor : defaultColor,
minHeight: '50px',
'&:hover': {
borderColor: activeColor,
},
}),
menu: base => ({
...base,
borderRadius: 5,
background: uiGrayDarker,
}),
option: (base, state) => ({
...base,
width: '100%',
cursor: 'pointer',
'&:first-of-type': {
borderTopLeftRadius: 5,
borderTopRightRadius: 5,
},
'&:last-of-type': {
borderBottomLeftRadius: 5,
borderBottomRightRadius: 5,
},
background: state.isSelected ? uiGrayDark : uiGrayDarker,
'&:hover': {
background: uiGrayDark,
},
}),
};
export default DicomBrowserSelect;
@@ -0,0 +1,26 @@
.dcmseg-segmentation-item {
display: flex;
justify-content: start;
margin: 0;
width: 100%;
}
.dcmseg-segmentation-item .segmentation-meta {
display: flex;
flex-direction: column;
flex-grow: 1;
overflow: hidden;
}
.dcmseg-segmentation-item .segmentation-meta-title {
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
color: white;
max-width: calc(100% - 30px);
}
.dcmseg-segmentation-item .segmentation-meta-description {
font-size: 12px;
color: var(--text-secondary-color);
}
@@ -0,0 +1,27 @@
import React from 'react';
import PropTypes from 'prop-types';
import './DicomBrowserSelectItem.css';
const DicomBrowserSelectItem = ({ onClick, title, description }) => {
return (
<li className="dcmseg-segmentation-item" onClick={onClick}>
<div className="segmentation-meta">
<div className="segmentation-meta-title">{title}</div>
<div className="segmentation-meta-description">{description}</div>
</div>
</li>
);
};
DicomBrowserSelectItem.propTypes = {
onClick: PropTypes.func.isRequired,
title: PropTypes.string.isRequired,
description: PropTypes.string.isRequired,
};
DicomBrowserSelectItem.defaultProps = {
description: '',
};
export default DicomBrowserSelectItem;
@@ -0,0 +1,27 @@
.dicom-tag-browser-table {
margin-right: auto;
margin-left: auto;
}
.dicom-tag-browser-table tr {
padding-left: 10px;
padding-right: 10px;
color: var(--table-text-primary-color);
border-top: 1px solid #ddd;
white-space: nowrap;
}
.dicom-tag-browser-table td.dicom-tag-browser-table-center {
text-align: center;
}
.dicom-tag-browser-table th {
padding-left: 10px;
padding-right: 10px;
text-align: center;
color: var(--active-color);
}
.dicom-tag-browser-table th.dicom-tag-browser-table-left {
text-align: left;
}
@@ -0,0 +1,335 @@
import React, { useState, useEffect } from 'react';
import { classes } from '@ohif/core';
import dcmjs from 'dcmjs';
import DicomBrowserSelect from './DicomBrowserSelect';
import moment from 'moment';
import './DicomTagBrowser.css';
import DicomBrowserSelectItem from './DicomBrowserSelectItem';
const { ImageSet } = classes;
const { DicomMetaDictionary } = dcmjs.data;
const { nameMap } = DicomMetaDictionary;
const DicomTagBrowser = ({ displaySets, displaySetInstanceUID }) => {
const [
activeDisplaySetInstanceUID,
setActiveDisplaySetInstanceUID,
] = useState(displaySetInstanceUID);
const [activeInstance, setActiveInstance] = useState(0);
const [tags, setTags] = useState([]);
const [instanceList, setInstanceList] = useState([]);
const [displaySetList, setDisplaySetList] = useState([]);
const [isImageStack, setIsImageStack] = useState(false);
useEffect(() => {
const activeDisplaySet = displaySets.find(
ds => ds.displaySetInstanceUID === activeDisplaySetInstanceUID
);
const newDisplaySetList = displaySets.map(displaySet => {
const {
displaySetInstanceUID,
SeriesDate,
SeriesTime,
SeriesNumber,
SeriesDescription,
Modality,
} = displaySet;
/* Map to display representation */
const dateStr = `${SeriesDate}:${SeriesTime}`.split('.')[0];
const date = moment(dateStr, 'YYYYMMDD:HHmmss');
const displayDate = date.format('ddd, MMM Do YYYY');
return {
value: displaySetInstanceUID,
title: `${SeriesNumber} (${Modality}): ${SeriesDescription}`,
description: displayDate,
onClick: () => {
setActiveDisplaySetInstanceUID(displaySetInstanceUID);
setActiveInstance(0);
},
};
});
let metadata;
const isImageStack = activeDisplaySet instanceof ImageSet;
let selectedInstanceValue;
let instanceList;
if (isImageStack) {
const { images } = activeDisplaySet;
const image = images[activeInstance];
instanceList = images.map((image, index) => {
const metadata = image.getData().metadata;
const { InstanceNumber } = metadata;
return {
value: index,
title: `Instance Number: ${InstanceNumber}`,
description: '',
onClick: () => {
setActiveInstance(index);
},
};
});
metadata = image.getData().metadata;
} else {
metadata = activeDisplaySet.metadata;
}
setTags(getSortedTags(metadata));
setInstanceList(instanceList);
setDisplaySetList(newDisplaySetList);
setIsImageStack(isImageStack);
}, [activeDisplaySetInstanceUID, activeInstance]);
const selectedDisplaySetValue = displaySetList.find(
ds => ds.value === activeDisplaySetInstanceUID
);
let instanceSelectList = null;
if (isImageStack) {
const selectedInstanceValue = instanceList[activeInstance];
instanceSelectList = (
<DicomBrowserSelect
value={selectedInstanceValue}
formatOptionLabel={DicomBrowserSelectItem}
options={instanceList}
/>
);
}
return (
<div>
<DicomBrowserSelect
value={selectedDisplaySetValue}
formatOptionLabel={DicomBrowserSelectItem}
options={displaySetList}
/>
{instanceSelectList}
<DicomTagTable tags={tags}></DicomTagTable>
</div>
);
};
function DicomTagTable({ tags }) {
const rows = getFormattedRowsFromTags(tags);
return (
<div>
<table className="dicom-tag-browser-table">
<tr>
<th className="dicom-tag-browser-table-left">Tag</th>
<th className="dicom-tag-browser-table-left">Value Representation</th>
<th className="dicom-tag-browser-table-left">Keyword</th>
<th className="dicom-tag-browser-table-left">Value</th>
</tr>
{rows.map(row => {
const className = row.className ? row.className : null;
return (
<tr className={className}>
<td>{row[0]}</td>
<td className="dicom-tag-browser-table-center">{row[1]}</td>
<td>{row[2]}</td>
<td>{row[3]}</td>
</tr>
);
})}
</table>
</div>
);
}
function getFormattedRowsFromTags(tags) {
const rows = [];
tags.forEach(tagInfo => {
if (tagInfo.vr === 'SQ') {
rows.push([
`${tagInfo.tagIndent}${tagInfo.tag}`,
tagInfo.vr,
tagInfo.keyword,
'',
]);
const { values } = tagInfo;
values.forEach((item, index) => {
const formatedRowsFromTags = getFormattedRowsFromTags(item);
rows.push([
`${item[0].tagIndent}(FFFE,E000)`,
'',
`Item #${index}`,
'',
]);
rows.push(...formatedRowsFromTags);
});
} else {
rows.push([
`${tagInfo.tagIndent}${tagInfo.tag}`,
tagInfo.vr,
tagInfo.keyword,
tagInfo.value,
]);
}
});
return rows;
}
function getSortedTags(metadata) {
const tagList = getRows(metadata);
// Sort top level tags, sequence groups are sorted when created.
_sortTagList(tagList);
return tagList;
}
function getRows(metadata, depth = 0) {
// Tag, Type, Value, Keyword
const keywords = Object.keys(metadata);
let tagIndent = '';
for (let i = 0; i < depth; i++) {
tagIndent += '>';
}
if (depth > 0) {
tagIndent += ' '; // If indented, add a space after the indents.
}
const rows = [];
for (let i = 0; i < keywords.length; i++) {
let keyword = keywords[i];
if (keyword === '_vrMap') {
continue;
}
const tagInfo = nameMap[keyword];
let value = metadata[keyword];
if (tagInfo && tagInfo.vr === 'SQ') {
const sequenceAsArray = toArray(value);
// Push line defining the sequence
const sequence = {
tag: tagInfo.tag,
tagIndent,
vr: tagInfo.vr,
keyword,
values: [],
};
rows.push(sequence);
if (value === null) {
// Type 2 Sequence
continue;
}
sequenceAsArray.forEach(item => {
const sequenceRows = getRows(item, depth + 1);
// Sort the sequence group.
_sortTagList(sequenceRows);
sequence.values.push(sequenceRows);
});
continue;
}
if (Array.isArray(value)) {
value = value.join('\\');
}
if (typeof value === 'number') {
value = value.toString();
}
if (typeof value !== 'string') {
if (value === null) {
value = ' ';
} else {
if (typeof value === 'object') {
if (value.InlineBinary) {
value = 'Inline Binary';
} else if (value.BulkDataURI) {
value = `Bulk Data URI`; //: ${value.BulkDataURI}`;
} else if (value.Alphabetic) {
value = value.Alphabetic;
} else {
console.error('Unrecognised Value for element:');
console.error(value);
value = ' ';
}
} else {
console.error('Unrecognised Value for element:');
console.error(value);
value = ' ';
}
}
}
// tag / vr/ keyword/ value
// Remove retired tags
keyword = keyword.replace('RETIRED_', '');
if (tagInfo) {
rows.push({
tag: tagInfo.tag,
tagIndent,
vr: tagInfo.vr,
keyword,
value,
});
} else {
// Private tag
const tag = `(${keyword.substring(0, 4)},${keyword.substring(4, 8)})`;
rows.push({
tag,
tagIndent,
vr: '',
keyword: 'Private Tag',
value,
});
}
}
return rows;
}
function toArray(objectOrArray) {
return Array.isArray(objectOrArray) ? objectOrArray : [objectOrArray];
}
function _sortTagList(tagList) {
tagList.sort((a, b) => {
if (a.tag < b.tag) {
return -1;
}
return 1;
});
}
export default DicomTagBrowser;
@@ -0,0 +1,52 @@
import { utils } from '@ohif/core';
import React from 'react';
import DicomTagBrowser from './components/DicomTagBrowser';
const { studyMetadataManager } = utils;
export default function getCommandsModule(servicesManager) {
const actions = {
openDICOMTagViewer({ viewports }) {
const { activeViewportIndex, viewportSpecificData } = viewports;
const activeViewportSpecificData =
viewportSpecificData[activeViewportIndex];
const {
StudyInstanceUID,
displaySetInstanceUID,
} = activeViewportSpecificData;
const studyMetadata = studyMetadataManager.get(StudyInstanceUID);
const displaySets = studyMetadata.getDisplaySets();
const { UIModalService } = servicesManager.services;
const WrappedDicomTagBrowser = function() {
return (
<DicomTagBrowser
displaySets={displaySets}
displaySetInstanceUID={displaySetInstanceUID}
/>
);
};
UIModalService.show({
content: WrappedDicomTagBrowser,
title: `DICOM Tag Browser`,
fullscreen: true,
});
},
};
const definitions = {
openDICOMTagViewer: {
commandFn: actions.openDICOMTagViewer,
storeContexts: ['servers', 'viewports'],
},
};
return {
actions,
definitions,
};
}
+26
View File
@@ -0,0 +1,26 @@
import getCommandsModule from './getCommandsModule';
import toolbarModule from './toolbarModule';
/**
* Constants
*/
/**
* Extension
*/
export default {
/**
* Only required property. Should be a unique value across all extensions.
*/
id: 'dicom-tag-browser',
/**
* MODULE GETTERS
*/
getCommandsModule({ servicesManager }) {
return getCommandsModule(servicesManager);
},
getToolbarModule() {
return toolbarModule;
},
};
@@ -0,0 +1,20 @@
const TOOLBAR_BUTTON_TYPES = {
COMMAND: 'command',
};
const definitions = [
{
id: 'TagBrowser',
label: 'Tag Browser',
icon: 'list',
//
type: TOOLBAR_BUTTON_TYPES.COMMAND,
commandName: 'openDICOMTagViewer',
context: 'VIEWER',
},
];
export default {
definitions,
defaultContext: 'VIEWER',
};
+8
View File
@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [0.2.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-lesion-tracker@0.2.0...@ohif/extension-lesion-tracker@0.2.1) (2020-09-03)
**Note:** Version bump only for package @ohif/extension-lesion-tracker
# [0.2.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-lesion-tracker@0.1.0...@ohif/extension-lesion-tracker@0.2.0) (2020-02-10)
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-lesion-tracker",
"version": "0.2.0",
"version": "0.2.1",
"description": "OHIF extension for Lesion Tracker",
"author": "OHIF",
"license": "MIT",
+3 -1
View File
@@ -1,10 +1,12 @@
import MeasurementComparisonTable from './components/MeasurementComparisonTable';
import { version } from '../package.json';
export default {
/**
* Only required property. Should be a unique value across all extensions.
*/
id: 'lesion-tracker',
version,
/**
* @param {object} params
@@ -26,7 +28,7 @@ export default {
component: MeasurementComparisonTable,
},
],
defaultContext: ['VIEWER']
defaultContext: ['VIEWER'],
};
},
+134
View File
@@ -3,6 +3,140 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.7.15](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.7.14...@ohif/extension-vtk@1.7.15) (2020-11-02)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.7.14](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.7.13...@ohif/extension-vtk@1.7.14) (2020-10-20)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.7.13](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.7.12...@ohif/extension-vtk@1.7.13) (2020-10-15)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.7.12](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.7.11...@ohif/extension-vtk@1.7.12) (2020-10-13)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.7.11](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.7.10...@ohif/extension-vtk@1.7.11) (2020-10-13)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.7.10](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.7.9...@ohif/extension-vtk@1.7.10) (2020-10-07)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.7.9](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.7.8...@ohif/extension-vtk@1.7.9) (2020-10-06)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.7.8](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.7.7...@ohif/extension-vtk@1.7.8) (2020-09-30)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.7.7](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.7.6...@ohif/extension-vtk@1.7.7) (2020-09-17)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.7.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.7.5...@ohif/extension-vtk@1.7.6) (2020-09-10)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.7.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.7.4...@ohif/extension-vtk@1.7.5) (2020-09-03)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.7.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.7.3...@ohif/extension-vtk@1.7.4) (2020-09-03)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.7.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.7.2...@ohif/extension-vtk@1.7.3) (2020-09-02)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.7.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.7.1...@ohif/extension-vtk@1.7.2) (2020-08-28)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.7.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.7.0...@ohif/extension-vtk@1.7.1) (2020-08-24)
### Bug Fixes
* 🐛 Fail gracefully on an MPR load error ([#1992](https://github.com/OHIF/Viewers/issues/1992)) ([779a7e0](https://github.com/OHIF/Viewers/commit/779a7e0976aa3a2d2bf1dc8f056950f0545c9fff))
# [1.7.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.6.10...@ohif/extension-vtk@1.7.0) (2020-08-18)
### Features
* 🎸 Update react-vtkjs-viewport usage to use requestPool ([#1984](https://github.com/OHIF/Viewers/issues/1984)) ([bb5f30c](https://github.com/OHIF/Viewers/commit/bb5f30ce2a0192d2e021beaaadfff22fd38e17b9))
## [1.6.10](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.6.9...@ohif/extension-vtk@1.6.10) (2020-08-10)
**Note:** Version bump only for package @ohif/extension-vtk
+7 -7
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-vtk",
"version": "1.6.10",
"version": "1.7.15",
"description": "OHIF extension for VTK.js",
"author": "OHIF",
"license": "MIT",
@@ -33,9 +33,9 @@
"@ohif/i18n": "^0.50.0",
"@ohif/ui": "^0.50.0",
"cornerstone-core": "^2.2.8",
"cornerstone-tools": "4.15.1",
"cornerstone-tools": "^4.20.1",
"cornerstone-wado-image-loader": "^3.1.0",
"dcmjs": "0.16.0",
"dcmjs": "0.16.5",
"dicom-parser": "^1.8.3",
"i18next": "^17.0.3",
"i18next-browser-languagedetector": "^3.0.1",
@@ -50,12 +50,12 @@
"dependencies": {
"@babel/runtime": "^7.5.5",
"lodash.throttle": "^4.1.1",
"react-vtkjs-viewport": "^0.9.0"
"react-vtkjs-viewport": "^0.14.1"
},
"devDependencies": {
"@ohif/core": "^2.9.14",
"@ohif/ui": "^1.5.3",
"cornerstone-tools": "4.15.1",
"@ohif/core": "^2.10.7",
"@ohif/ui": "^1.5.7",
"cornerstone-tools": "^4.20.1",
"cornerstone-wado-image-loader": "^3.1.0",
"dicom-parser": "^1.8.3",
"gh-pages": "^2.0.1",
+9 -9
View File
@@ -32,16 +32,16 @@ class LoadingIndicator extends PureComponent {
</div>
</div>
) : (
<div className="imageViewerLoadingIndicator loadingIndicator">
<div className="indicatorContents">
<p>
{this.props.t('Loading...')}
<i className="fa fa-spin fa-circle-o-notch fa-fw" />
{percComplete}
</p>
</div>
<div className="imageViewerLoadingIndicator loadingIndicator">
<div className="indicatorContents">
<p>
{this.props.t('Loading...')}
<i className="fa fa-spin fa-circle-o-notch fa-fw" />
{percComplete}
</p>
</div>
)}
</div>
)}
</React.Fragment>
);
}
+51 -26
View File
@@ -49,27 +49,30 @@ class OHIFVTKViewport extends Component {
state = {
volumes: null,
paintFilterLabelMapImageData: null,
paintFilterBackgroundImageData: null
paintFilterBackgroundImageData: null,
percentComplete: 0,
isLoaded: false,
};
static propTypes = {
viewportData: PropTypes.shape({
studies: PropTypes.array,
studies: PropTypes.array.isRequired,
displaySet: PropTypes.shape({
StudyInstanceUID: PropTypes.string,
displaySetInstanceUID: PropTypes.string,
StudyInstanceUID: PropTypes.string.isRequired,
displaySetInstanceUID: PropTypes.string.isRequired,
sopClassUIDs: PropTypes.arrayOf(PropTypes.string),
SOPInstanceUID: PropTypes.string,
frameIndex: PropTypes.number,
}),
}),
viewportIndex: PropTypes.number,
viewportIndex: PropTypes.number.isRequired,
children: PropTypes.node,
onScroll: PropTypes.func,
servicesManager: PropTypes.object.isRequired,
};
static defaultProps = {
onScroll: () => { },
onScroll: () => {},
};
static id = 'OHIFVTKViewport';
@@ -156,9 +159,11 @@ class OHIFVTKViewport extends Component {
const { activeLabelmapIndex } = brushStackState;
const labelmap3D = brushStackState.labelmaps3D[activeLabelmapIndex];
this.segmentsDefaultProperties = labelmap3D.segmentsHidden.map(isHidden => {
return { visible: !isHidden };
});
this.segmentsDefaultProperties = labelmap3D.segmentsHidden.map(
isHidden => {
return { visible: !isHidden };
}
);
const vtkLabelmapID = `${firstImageId}_${activeLabelmapIndex}`;
@@ -349,7 +354,7 @@ class OHIFVTKViewport extends Component {
if (
displaySet.displaySetInstanceUID !==
prevDisplaySet.displaySetInstanceUID ||
prevDisplaySet.displaySetInstanceUID ||
displaySet.SOPInstanceUID !== prevDisplaySet.SOPInstanceUID ||
displaySet.frameIndex !== prevDisplaySet.frameIndex
) {
@@ -360,34 +365,54 @@ class OHIFVTKViewport extends Component {
loadProgressively(imageDataObject) {
loadImageData(imageDataObject);
const { isLoading, insertPixelDataPromises } = imageDataObject;
const NumberOfFrames = insertPixelDataPromises.length;
const { isLoading, imageIds } = imageDataObject;
if (!isLoading) {
this.setState({ isLoaded: true });
return;
}
insertPixelDataPromises.forEach(promise => {
promise.then(numberProcessed => {
const percentComplete = Math.floor(
(numberProcessed * 100) / NumberOfFrames
);
const NumberOfFrames = imageIds.length;
if (percentComplete !== this.state.percentComplete) {
this.setState({
percentComplete,
const onPixelDataInsertedCallback = numberProcessed => {
const percentComplete = Math.floor(
(numberProcessed * 100) / NumberOfFrames
);
if (percentComplete !== this.state.percentComplete) {
this.setState({
percentComplete,
});
}
};
const onPixelDataInsertedErrorCallback = error => {
const { UINotificationService } = this.props.servicesManager.services;
if (!this.hasError) {
if (this.props.viewportIndex === 0) {
// Only show the notification from one viewport 1 in MPR2D.
UINotificationService.show({
title: 'MPR Load Error',
message: error.message,
type: 'error',
autoClose: false,
});
}
});
});
Promise.all(insertPixelDataPromises).then(() => {
this.hasError = true;
}
};
const onAllPixelDataInsertedCallback = () => {
this.setState({
isLoaded: true,
});
});
};
imageDataObject.onPixelDataInserted(onPixelDataInsertedCallback);
imageDataObject.onAllPixelDataInserted(onAllPixelDataInsertedCallback);
imageDataObject.onPixelDataInsertedError(onPixelDataInsertedErrorCallback);
}
render() {
@@ -435,7 +460,7 @@ class OHIFVTKViewport extends Component {
segmentsDefaultProperties: this.segmentsDefaultProperties,
onNewSegmentationRequested: () => {
this.setStateFromProps();
}
},
}}
onScroll={this.props.onScroll}
/>
+97 -18
View File
@@ -1,9 +1,9 @@
import throttle from 'lodash.throttle';
import {
vtkInteractorStyleMPRCrosshairs,
vtkInteractorStyleMPRWindowLevel,
vtkInteractorStyleRotatableMPRCrosshairs,
vtkSVGRotatableCrosshairsWidget,
vtkInteractorStyleMPRRotate,
vtkSVGCrosshairsWidget,
} from 'react-vtkjs-viewport';
import { getImageData } from 'react-vtkjs-viewport';
import { vec3 } from 'gl-matrix';
@@ -11,11 +11,10 @@ import setMPRLayout from './utils/setMPRLayout.js';
import setViewportToVTK from './utils/setViewportToVTK.js';
import Constants from 'vtk.js/Sources/Rendering/Core/VolumeMapper/Constants.js';
import OHIFVTKViewport from './OHIFVTKViewport';
import vtkCoordinate from 'vtk.js/Sources/Rendering/Core/Coordinate';
const { BlendMode } = Constants;
const commandsModule = ({ commandsManager }) => {
const commandsModule = ({ commandsManager, UINotificationService }) => {
// TODO: Put this somewhere else
let apis = {};
@@ -119,6 +118,14 @@ const commandsModule = ({ commandsManager }) => {
getVtkApis: ({ index }) => {
return apis[index];
},
resetMPRView() {
apis.forEach(api => {
api.resetOrientation();
});
// Reset the crosshairs
apis[0].svgWidgets.rotatableCrosshairsWidget.resetCrosshairs(apis, 0);
},
axial: async ({ viewports }) => {
const api = await _getActiveViewportVTKApi(viewports);
@@ -165,7 +172,7 @@ const commandsModule = ({ commandsManager }) => {
segmentNumber,
frameIndex,
frame,
done = () => { }
done = () => {},
}) => {
let api = apis[viewports.activeViewportIndex];
@@ -180,10 +187,13 @@ const commandsModule = ({ commandsManager }) => {
displaySetInstanceUID,
SOPClassUID,
SOPInstanceUID,
frameIndex,
frameIndex
);
const imageDataObject = getImageData(stack.imageIds, displaySetInstanceUID);
const imageDataObject = getImageData(
stack.imageIds,
displaySetInstanceUID
);
let pixelIndex = 0;
let x = 0;
@@ -209,9 +219,12 @@ const commandsModule = ({ commandsManager }) => {
y /= count;
const position = [x, y, frameIndex];
const worldPos = _convertModelToWorldSpace(position, imageDataObject.vtkImageData);
const worldPos = _convertModelToWorldSpace(
position,
imageDataObject.vtkImageData
);
api.svgWidgets.crosshairsWidget.moveCrosshairs(worldPos, apis);
api.svgWidgets.rotatableCrosshairsWidget.moveCrosshairs(worldPos, apis);
done();
},
setSegmentationConfiguration: async ({
@@ -254,21 +267,38 @@ const commandsModule = ({ commandsManager }) => {
await Promise.all(promises);
},
enableRotateTool: () => {
apis.forEach(api => {
apis.forEach((api, apiIndex) => {
const istyle = vtkInteractorStyleMPRRotate.newInstance();
api.setInteractorStyle({ istyle });
api.setInteractorStyle({
istyle,
configuration: { apis, apiIndex, uid: api.uid },
});
});
},
enableCrosshairsTool: () => {
apis.forEach((api, apiIndex) => {
const istyle = vtkInteractorStyleMPRCrosshairs.newInstance();
const istyle = vtkInteractorStyleRotatableMPRCrosshairs.newInstance();
api.setInteractorStyle({
istyle,
configuration: { apis, apiIndex },
configuration: {
apis,
apiIndex,
uid: api.uid,
},
});
});
const rotatableCrosshairsWidget =
apis[0].svgWidgets.rotatableCrosshairsWidget;
const referenceLines = rotatableCrosshairsWidget.getReferenceLines();
// Initilise crosshairs if not initialised.
if (!referenceLines) {
rotatableCrosshairsWidget.resetCrosshairs(apis, 0);
}
},
enableLevelTool: () => {
function updateVOI(apis, windowWidth, windowCenter) {
@@ -291,10 +321,14 @@ const commandsModule = ({ commandsManager }) => {
},
};
apis.forEach(api => {
apis.forEach((api, apiIndex) => {
const istyle = vtkInteractorStyleMPRWindowLevel.newInstance();
api.setInteractorStyle({ istyle, callbacks });
api.setInteractorStyle({
istyle,
callbacks,
configuration: { apis, apiIndex, uid: api.uid },
});
});
},
setSlabThickness: ({ slabThickness }) => {
@@ -391,18 +425,59 @@ const commandsModule = ({ commandsManager }) => {
// Add widgets and set default interactorStyle of each viewport.
apis.forEach((api, apiIndex) => {
api.addSVGWidget(
vtkSVGCrosshairsWidget.newInstance(),
'crosshairsWidget'
vtkSVGRotatableCrosshairsWidget.newInstance(),
'rotatableCrosshairsWidget'
);
const uid = api.uid;
const istyle = vtkInteractorStyleMPRCrosshairs.newInstance();
const istyle = vtkInteractorStyleRotatableMPRCrosshairs.newInstance();
api.setInteractorStyle({
istyle,
configuration: { apis, apiIndex, uid },
});
api.svgWidgets.rotatableCrosshairsWidget.setApiIndex(apiIndex);
api.svgWidgets.rotatableCrosshairsWidget.setApis(apis);
});
const firstApi = apis[0];
// Initialise crosshairs
apis[0].svgWidgets.rotatableCrosshairsWidget.resetCrosshairs(apis, 0);
// Check if we have full WebGL 2 support
const openGLRenderWindow = apis[0].genericRenderWindow.getOpenGLRenderWindow();
if (!openGLRenderWindow.getWebgl2()) {
// Throw a warning if we don't have WebGL 2 support,
// And the volume is too big to fit in a 2D texture
const openGLContext = openGLRenderWindow.getContext();
const maxTextureSizeInBytes = openGLContext.getParameter(
openGLContext.MAX_TEXTURE_SIZE
);
const maxBufferLengthFloat32 =
(maxTextureSizeInBytes * maxTextureSizeInBytes) / 4;
const dimensions = firstApi.volumes[0]
.getMapper()
.getInputData()
.getDimensions();
const volumeLength = dimensions[0] * dimensions[1] * dimensions[2];
if (volumeLength > maxBufferLengthFloat32) {
UINotificationService.show({
title: 'Browser does not support WebGL 2',
message:
'This volume is too large to fit in WebGL 1 textures and will display incorrectly. Please use a different browser to view this data',
type: 'error',
autoClose: false,
});
}
}
},
};
@@ -456,6 +531,10 @@ const commandsModule = ({ commandsManager }) => {
commandFn: actions.enableLevelTool,
options: {},
},
resetMPRView: {
commandFn: actions.resetMPRView,
options: {},
},
setBlendModeToComposite: {
commandFn: actions.setBlendModeToComposite,
options: { blendMode: BlendMode.COMPOSITE_BLEND },
+9 -4
View File
@@ -3,6 +3,7 @@ import asyncComponent from './asyncComponent.js';
import commandsModule from './commandsModule.js';
import toolbarModule from './toolbarModule.js';
import withCommandsManager from './withCommandsManager.js';
import { version } from '../package.json';
// This feels weird
// import loadLocales from './loadLocales';
@@ -15,16 +16,20 @@ const vtkExtension = {
* Only required property. Should be a unique value across all extensions.
*/
id: 'vtk',
version,
getViewportModule({ commandsManager }) {
const ExtendedVTKViewport = props => <OHIFVTKViewport {...props} />;
getViewportModule({ commandsManager, servicesManager }) {
const ExtendedVTKViewport = props => (
<OHIFVTKViewport {...props} servicesManager={servicesManager} />
);
return withCommandsManager(ExtendedVTKViewport, commandsManager);
},
getToolbarModule() {
return toolbarModule;
},
getCommandsModule({ commandsManager }) {
return commandsModule({ commandsManager });
getCommandsModule({ commandsManager, servicesManager }) {
const { UINotificationService } = servicesManager.services;
return commandsModule({ commandsManager, UINotificationService });
},
};
+12 -1
View File
@@ -25,6 +25,16 @@ const definitions = [
commandName: 'enableLevelTool',
commandOptions: {},
},
{
id: 'Reset',
label: 'Reset',
icon: 'reset',
//
type: TOOLBAR_BUTTON_TYPES.COMMAND,
commandName: 'resetMPRView',
commandOptions: {},
},
/*
{
id: 'Rotate',
label: 'Rotate',
@@ -34,6 +44,7 @@ const definitions = [
commandName: 'enableRotateTool',
commandOptions: {},
},
*/
/*
{
id: 'setBlendModeToComposite',
@@ -131,4 +142,4 @@ const definitions = [
export default {
definitions,
defaultContext: 'ACTIVE_VIEWPORT::VTK',
}
};
+2 -3
View File
@@ -34,9 +34,8 @@
"see-changed": "lerna changed",
"docs:publish": "chmod +x ./build-and-publish-docs.sh && ./build-and-publish-docs.sh",
"release": "yarn run lerna:version && yarn run lerna:publish",
"lerna:cache": "[ $NETLIFY_RESTORE = 1 ] && ./netlify-lerna-cache.sh || :",
"lerna:restore": "[ $NETLIFY_RESTORE = 1 ] && ./netlify-lerna-restore.sh || :",
"preinstall": "yarn lerna:restore",
"lerna:cache": "./netlify-lerna-cache.sh",
"lerna:restore": "./netlify-lerna-restore.sh",
"lerna:version": "npx lerna version prerelease --force-publish",
"lerna:publish": "lerna publish from-package --canary --dist-tag canary",
"link-list": "npm ls --depth=0 --link=true"
+70
View File
@@ -3,6 +3,76 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [2.10.7](https://github.com/OHIF/Viewers/compare/@ohif/core@2.10.6...@ohif/core@2.10.7) (2020-10-07)
**Note:** Version bump only for package @ohif/core
## [2.10.6](https://github.com/OHIF/Viewers/compare/@ohif/core@2.10.5...@ohif/core@2.10.6) (2020-09-30)
### Bug Fixes
* use SeriesMetadata method instead of property in findMostRecentStructuredReport ([#2038](https://github.com/OHIF/Viewers/issues/2038)) ([91364db](https://github.com/OHIF/Viewers/commit/91364db8820efc86665db258d0044c16fb3e2c87)), closes [#1714](https://github.com/OHIF/Viewers/issues/1714)
## [2.10.5](https://github.com/OHIF/Viewers/compare/@ohif/core@2.10.4...@ohif/core@2.10.5) (2020-09-10)
**Note:** Version bump only for package @ohif/core
## [2.10.4](https://github.com/OHIF/Viewers/compare/@ohif/core@2.10.3...@ohif/core@2.10.4) (2020-09-03)
**Note:** Version bump only for package @ohif/core
## [2.10.3](https://github.com/OHIF/Viewers/compare/@ohif/core@2.10.2...@ohif/core@2.10.3) (2020-09-03)
**Note:** Version bump only for package @ohif/core
## [2.10.2](https://github.com/OHIF/Viewers/compare/@ohif/core@2.10.1...@ohif/core@2.10.2) (2020-09-02)
**Note:** Version bump only for package @ohif/core
## [2.10.1](https://github.com/OHIF/Viewers/compare/@ohif/core@2.10.0...@ohif/core@2.10.1) (2020-08-28)
**Note:** Version bump only for package @ohif/core
# [2.10.0](https://github.com/OHIF/Viewers/compare/@ohif/core@2.9.14...@ohif/core@2.10.0) (2020-08-18)
### Features
* 🎸 Update react-vtkjs-viewport usage to use requestPool ([#1984](https://github.com/OHIF/Viewers/issues/1984)) ([bb5f30c](https://github.com/OHIF/Viewers/commit/bb5f30ce2a0192d2e021beaaadfff22fd38e17b9))
## [2.9.14](https://github.com/OHIF/Viewers/compare/@ohif/core@2.9.13...@ohif/core@2.9.14) (2020-08-10)
**Note:** Version bump only for package @ohif/core
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/core",
"version": "2.9.14",
"version": "2.10.7",
"description": "Generic business logic for web-based medical imaging applications",
"author": "OHIF Core Team",
"license": "MIT",
@@ -32,14 +32,14 @@
},
"peerDependencies": {
"cornerstone-core": "^2.2.8",
"cornerstone-tools": "4.15.1",
"cornerstone-tools": "^4.20.1",
"cornerstone-wado-image-loader": "^3.1.0",
"dicom-parser": "^1.8.3"
},
"dependencies": {
"@babel/runtime": "^7.5.5",
"ajv": "^6.10.0",
"dcmjs": "0.16.0",
"dcmjs": "0.16.5",
"dicomweb-client": "^0.6.0",
"immer": "6.0.2",
"isomorphic-base64": "^1.0.2",
@@ -13,7 +13,7 @@ const findMostRecentStructuredReport = studies => {
// Skip series that may not have instances yet
// This can happen if we have retrieved just the initial
// details about the series via QIDO-RS, but not the full metadata
if (!series.instances || !series.instances.length) {
if (!series || series.getInstanceCount() === 0) {
return;
}
@@ -54,9 +54,6 @@ export class OHIFStudyMetadataSource extends StudyMetadataSource {
// Get Study display sets
const displaySets = studyMetadata.createDisplaySets();
// Set studyMetadata display sets
studyMetadata.setDisplaySets(displaySets);
OHIFStudyMetadataSource._updateStudyCollections(studyMetadata);
resolve(studyMetadata);
})
@@ -10,8 +10,8 @@ import { api } from 'dicomweb-client';
// - createStacks
import { isImage } from '../../utils/isImage';
import isDisplaySetReconstructable from '../../utils/isDisplaySetReconstructable';
import isLowPriorityModality from '../../utils/isLowPriorityModality';
import errorHandler from '../../errorHandler';
import isLowPriorityModality from '../../utils/isLowPriorityModality';
export class StudyMetadata extends Metadata {
constructor(data, uid) {
@@ -308,14 +308,10 @@ export class StudyMetadata extends Metadata {
series
);
displaySets.push(...displaySetsForSeries);
displaySetsForSeries.forEach(ds => this._insertDisplaySet(ds));
});
return sortDisplaySetList(displaySets);
}
sortDisplaySets() {
sortDisplaySetList(this._displaySets);
return this._displaySets;
}
/**
@@ -346,25 +342,9 @@ export class StudyMetadata extends Metadata {
this.addDisplaySet(displaySet);
});
this.sortDisplaySets();
return true;
}
/**
* Set display sets
* @param {Array} displaySets Array of display sets (ImageSet[])
*/
setDisplaySets(displaySets) {
if (Array.isArray(displaySets) && displaySets.length > 0) {
// TODO: This is weird, can we just switch it to writable: true?
this._displaySets.splice(0);
displaySets.forEach(displaySet => this.addDisplaySet(displaySet));
this.sortDisplaySets();
}
}
/**
* Add a single display set to the list
* @param {Object} displaySet Display set object
@@ -372,7 +352,7 @@ export class StudyMetadata extends Metadata {
*/
addDisplaySet(displaySet) {
if (displaySet instanceof ImageSet || displaySet.sopClassModule) {
this._displaySets.push(displaySet);
this._insertDisplaySet(displaySet);
return true;
}
return false;
@@ -393,6 +373,96 @@ export class StudyMetadata extends Metadata {
}
}
/**
* Insert the displaySet so that the list has an increasing SeriesNumber,
* with the most recent series first for displaySets with the same SeriesNumber.
*
* If the displaySet is low priority, the same logic is applied, but is sorted within a sub list
* At the end of the list, where all low priority data is found.
*/
_insertDisplaySet(displaySet) {
const { SeriesNumber } = displaySet;
const displaySets = this._displaySets;
let insertIndex = displaySets.length;
let firstIndexWithSameSeriesNumber;
// If low priority, start search from next low priority.
if (isLowPriorityModality(displaySet.Modality)) {
let startingIndex;
// Find where the first low priority displaySet is.
for (let i = 0; i < displaySets.length; i++) {
if (isLowPriorityModality(displaySets[i].Modality)) {
startingIndex = i;
break;
}
}
if (!startingIndex) {
startingIndex = displaySets.length;
}
// Find the correct SeriesNumber location to insert within the low priority
// Modality displaySets
for (let i = startingIndex; i < displaySets.length; i++) {
if (
displaySets[i].SeriesNumber === SeriesNumber &&
!firstIndexWithSameSeriesNumber
) {
firstIndexWithSameSeriesNumber = i;
}
if (displaySets[i].SeriesNumber > SeriesNumber) {
insertIndex = i;
break;
}
}
} else {
// Find correct SeriesNumber to insert or where the low priority modalities start.
for (let i = 0; i < displaySets.length; i++) {
if (
displaySets[i].SeriesNumber === SeriesNumber &&
!firstIndexWithSameSeriesNumber
) {
firstIndexWithSameSeriesNumber = i;
}
if (
displaySets[i].SeriesNumber > SeriesNumber ||
isLowPriorityModality(displaySets[i].Modality)
) {
insertIndex = i;
break;
}
}
}
// If we have multiple displaySets with the same series number, find the insert position based on
// SeriesDate and SeriesTime.
if (firstIndexWithSameSeriesNumber !== undefined) {
// If no SeriesDate, is just a placeholder displaySet, just insert anywhere, it will be re-added later.
if (displaySet.SeriesDate) {
const seriesDateTime = `${displaySet.SeriesDate}${displaySet.SeriesTime}`;
for (let i = firstIndexWithSameSeriesNumber; i < insertIndex; i++) {
const displaySetI = displaySets[i];
if (
displaySetI.SeriesDate &&
`${displaySetI.SeriesDate}${displaySetI.SeriesTime}` <
seriesDateTime
) {
insertIndex = i;
break;
}
}
}
}
this._displaySets.splice(insertIndex, 0, displaySet);
this.displaySets = this._displaySets;
}
/**
* Search the associated display sets using the supplied callback as criteria. The callback is passed
* two arguments: display set (an ImageSet instance) and index (the integer
@@ -548,49 +618,6 @@ export class StudyMetadata extends Metadata {
return this._series.indexOf(series);
}
/**
* It sorts the series based on display sets order. Each series must be an instance
* of SeriesMetadata and each display sets must be an instance of ImageSet.
* Useful example of usage:
* Study data provided by backend does not sort series at all and client-side
* needs series sorted by the same criteria used for sorting display sets.
*/
sortSeriesByDisplaySets() {
// Object for mapping display sets' index by SeriesInstanceUID
const displaySetsMapping = {};
// Loop through each display set to create the mapping
this.forEachDisplaySet((displaySet, index) => {
if (!(displaySet instanceof ImageSet)) {
throw new OHIFError(
`StudyMetadata::sortSeriesByDisplaySets display set at index ${index} is not an instance of ImageSet`
);
}
// In case of multiframe studies, just get the first index occurence
if (displaySetsMapping[displaySet.SeriesInstanceUID] === void 0) {
displaySetsMapping[displaySet.SeriesInstanceUID] = index;
}
});
// Clone of actual series
const actualSeries = this.getSeries();
actualSeries.forEach((series, index) => {
if (!(series instanceof SeriesMetadata)) {
throw new OHIFError(
`StudyMetadata::sortSeriesByDisplaySets series at index ${index} is not an instance of SeriesMetadata`
);
}
// Get the new series index
const seriesIndex = displaySetsMapping[series.getSeriesInstanceUID()];
// Update the series object with the new series position
this._series[seriesIndex] = series;
});
}
/**
* Compares the current study instance with another one.
* @param {StudyMetadata} study An instance of the StudyMetadata class.
@@ -865,53 +892,3 @@ function _getDisplaySetFromSopClassModule(
}
return displaySet;
}
/**
* Sort series primarily by Modality (i.e., series with references to other
* series like SEG, KO or PR are grouped in the end of the list) and then by
* series number:
*
* --------
* | CT #3 |
* | CT #4 |
* | CT #5 |
* --------
* | SEG #1 |
* | SEG #2 |
* --------
*
* @param {*} a - DisplaySet
* @param {*} b - DisplaySet
*/
function seriesSortingCriteria(a, b) {
const isLowPriorityA = isLowPriorityModality(a.Modality);
const isLowPriorityB = isLowPriorityModality(b.Modality);
if (!isLowPriorityA && isLowPriorityB) {
return -1;
}
if (isLowPriorityA && !isLowPriorityB) {
return 1;
}
return sortBySeriesNumber(a, b);
}
/**
* Sort series by series number. Series with low
* @param {*} a - DisplaySet
* @param {*} b - DisplaySet
*/
function sortBySeriesNumber(a, b) {
const seriesNumberAIsGreaterOrUndefined =
a.SeriesNumber > b.SeriesNumber || (!a.SeriesNumber && b.SeriesNumber);
return seriesNumberAIsGreaterOrUndefined ? 1 : -1;
}
/**
* Sorts a list of display set objects
* @param {Array} list A list of display sets to be sorted
*/
function sortDisplaySetList(list) {
return list.sort(seriesSortingCriteria);
}
@@ -5,6 +5,7 @@ export default class ExtensionManager {
constructor({ commandsManager, servicesManager, api, appConfig = {} }) {
this.modules = {};
this.registeredExtensionIds = [];
this.registeredExtensionVesions = {};
this.moduleTypeNames = Object.values(MODULE_TYPES);
//
this._commandsManager = commandsManager;
@@ -51,6 +52,7 @@ export default class ExtensionManager {
}
let extensionId = extension.id;
const version = extension.version;
if (!extensionId) {
extensionId = Math.random()
@@ -98,6 +100,8 @@ export default class ExtensionManager {
// Track extension registration
this.registeredExtensionIds.push(extensionId);
this.registeredExtensionVesions[extensionId] = version;
}
/**
@@ -120,7 +124,8 @@ export default class ExtensionManager {
commandsManager: this._commandsManager,
appConfig: this._appConfig,
configuration,
api: this._api
api: this._api,
extensionManager: this,
});
if (!extensionModule) {
@@ -184,6 +184,8 @@ describe('ExtensionManager.js', () => {
commandsManager,
appConfig,
configuration: extensionConfiguration,
api: undefined,
extensionManager,
});
}
});
@@ -226,7 +228,7 @@ describe('ExtensionManager.js', () => {
return {
definitions: {
exampleDefinition: {
commandFn: () => { },
commandFn: () => {},
storeContexts: [],
options: {},
},
@@ -38,6 +38,7 @@ function _show({
onClose = null,
closeButton = true,
title = null,
fullscreen = false,
customClassName = null,
}) {
return serviceImplementation._show({
@@ -48,6 +49,7 @@ function _show({
onClose,
closeButton,
title,
fullscreen,
customClassName,
});
}
@@ -11,12 +11,15 @@ import { retrieveStudyMetadata } from './retrieveStudyMetadata';
* @param {Array} studyInstanceUIDs The UIDs of the Studies to be retrieved
* @param {Object} [filters] - Object containing filters to be applied on retrieve metadata process
* @param {string} [filter.seriesInstanceUID] - series instance uid to filter results against
* @param {boolean} [separateSeriesInstanceUIDFilters = false] - If true, split filtered metadata calls into multiple calls,
* as some DICOMWeb implementations only support single filters.
* @returns {Promise} that will be resolved with the metadata or rejected with the error
*/
export default function retrieveStudiesMetadata(
server,
studyInstanceUIDs,
filters
filters,
separateSeriesInstanceUIDFilters = false
) {
// Create an empty array to store the Promises for each metaData retrieval call
const promises = [];
@@ -24,7 +27,12 @@ export default function retrieveStudiesMetadata(
// Loop through the array of studyInstanceUIDs
studyInstanceUIDs.forEach(function(StudyInstanceUID) {
// Send the call and resolve or reject the related promise based on its outcome
const promise = retrieveStudyMetadata(server, StudyInstanceUID, filters);
const promise = retrieveStudyMetadata(
server,
StudyInstanceUID,
filters,
separateSeriesInstanceUIDFilters
);
// Add the current promise to the array of promises
promises.push(promise);
@@ -11,9 +11,16 @@ const StudyMetaDataPromises = new Map();
* @param {string} StudyInstanceUID The UID of the Study to be retrieved
* @param {Object} [filters] - Object containing filters to be applied on retrieve metadata process
* @param {string} [filter.seriesInstanceUID] - series instance uid to filter results against
* @param {boolean} [separateSeriesInstanceUIDFilters = false] - If true, split filtered metadata calls into multiple calls,
* as some DICOMWeb implementations only support single filters.
* @returns {Promise} that will be resolved with the metadata or rejected with the error
*/
export function retrieveStudyMetadata(server, StudyInstanceUID, filters) {
export function retrieveStudyMetadata(
server,
StudyInstanceUID,
filters,
separateSeriesInstanceUIDFilters = false
) {
// @TODO: Whenever a study metadata request has failed, its related promise will be rejected once and for all
// and further requests for that metadata will always fail. On failure, we probably need to remove the
// corresponding promise from the "StudyMetaDataPromises" map...
@@ -33,11 +40,29 @@ export function retrieveStudyMetadata(server, StudyInstanceUID, filters) {
}
// Create a promise to handle the data retrieval
const promise = new Promise((resolve, reject) => {
RetrieveMetadata(server, StudyInstanceUID, filters).then(function(data) {
resolve(data);
}, reject);
});
let promise;
if (
filters &&
filters.seriesInstanceUID &&
separateSeriesInstanceUIDFilters
) {
promise = __separateSeriesRequestToAggregatePromiseateSeriesRequestToAggregatePromise(
server,
StudyInstanceUID,
filters
);
} else {
promise = RetrieveMetadata(server, StudyInstanceUID, filters);
/*
promise = new Promise((resolve, reject) => {
RetrieveMetadata(server, StudyInstanceUID, filters).then(function(data) {
resolve(data);
}, reject);
});
*/
}
// Store the promise in cache
StudyMetaDataPromises.set(StudyInstanceUID, promise);
@@ -45,6 +70,49 @@ export function retrieveStudyMetadata(server, StudyInstanceUID, filters) {
return promise;
}
/**
* Splits up seriesInstanceUID filters to multiple calls for platforms
* @param {Object} server Object with server configuration parameters
* @param {string} StudyInstanceUID The UID of the Study to be retrieved
* @param {Object} filters - Object containing filters to be applied on retrieve metadata process
*/
function __separateSeriesRequestToAggregatePromiseateSeriesRequestToAggregatePromise(
server,
StudyInstanceUID,
filters
) {
const { seriesInstanceUID } = filters;
const seriesInstanceUIDs = seriesInstanceUID.split(',');
return new Promise((resolve, reject) => {
const promises = [];
seriesInstanceUIDs.forEach(uid => {
const seriesSpecificFilters = Object.assign({}, filters, {
seriesInstanceUID: uid,
});
promises.push(
RetrieveMetadata(server, StudyInstanceUID, seriesSpecificFilters)
);
});
Promise.all(promises).then(results => {
const data = results[0];
let series = [];
results.forEach(result => {
series = [...series, ...result.series];
});
data.series = series;
resolve(data);
}, reject);
});
}
/**
* Delete the cached study metadata retrieval promise to ensure that the browser will
* re-retrieve the study metadata when it is next requested
@@ -1,4 +1,11 @@
const LOW_PRIORITY_MODALITIES = Object.freeze(['SEG', 'KO', 'PR']);
const LOW_PRIORITY_MODALITIES = Object.freeze([
'SEG',
'DOC',
'RTSTRUCT',
'SR',
'KO',
'PR',
]);
export default function isLowPriorityModality(Modality) {
return LOW_PRIORITY_MODALITIES.includes(Modality);
+35
View File
@@ -3,6 +3,41 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.5.7](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.5.6...@ohif/ui@1.5.7) (2020-11-02)
### Bug Fixes
* Reordered definitions in providers to prevent uninitialized var access ([#2108](https://github.com/OHIF/Viewers/issues/2108)) ([619f361](https://github.com/OHIF/Viewers/commit/619f3615109d6cc4c69ff2f9fedbf83bd9309719))
## [1.5.6](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.5.5...@ohif/ui@1.5.6) (2020-10-06)
**Note:** Version bump only for package @ohif/ui
## [1.5.5](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.5.4...@ohif/ui@1.5.5) (2020-09-17)
**Note:** Version bump only for package @ohif/ui
## [1.5.4](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.5.3...@ohif/ui@1.5.4) (2020-09-10)
**Note:** Version bump only for package @ohif/ui
## [1.5.3](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.5.2...@ohif/ui@1.5.3) (2020-07-23)
**Note:** Version bump only for package @ohif/ui
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/ui",
"version": "1.5.3",
"version": "1.5.7",
"description": "A set of React components for Medical Imaging Viewers",
"author": "OHIF Contributors",
"license": "MIT",
@@ -22,6 +22,7 @@ const OHIFModal = ({
closeButton,
shouldCloseOnEsc,
isOpen,
fullscreen,
title,
onClose,
children,
@@ -41,9 +42,13 @@ const OHIFModal = ({
);
};
const classes = fullscreen
? classNames('OHIFModal', className, 'OHIFModal-fullscreen')
: classNames('OHIFModal', className);
return (
<Modal
className={classNames('OHIFModal', className)}
className={classes}
data-cy="modal"
shouldCloseOnEsc={shouldCloseOnEsc}
isOpen={isOpen}
@@ -18,9 +18,17 @@
@media (min-width: 768px)
width: 600px
&.OHIFModal-fullscreen
width: 95%
@media (min-width: 992px)
width: 900px
&.OHIFModal-fullscreen
width: 95%
&__content
padding: 20px
max-height: 90vh;
@@ -45,36 +45,6 @@ const DialogProvider = ({ children, service }) => {
};
};
/**
* Sets the implementation of a dialog service that can be used by extensions.
*
* @returns void
*/
useEffect(() => {
if (service) {
service.setServiceImplementation({ create, dismiss, dismissAll });
}
}, [create, dismiss, service]);
/**
* UI Dialog
*
* @typedef {Object} DialogProps
* @property {string} id The dialog id.
* @property {DialogContent} content The dialog content.
* @property {Object} contentProps The dialog content props.
* @property {boolean} isDraggable Controls if dialog content is draggable or not.
* @property {boolean} showOverlay Controls dialog overlay.
* @property {boolean} centralize Center the dialog on the screen.
* @property {boolean} preservePosition Use last position instead of default.
* @property {ElementPosition} defaultPosition Specifies the `x` and `y` that the dragged item should start at.
* @property {Function} onStart Called when dragging starts. If `false` is returned any handler, the action will cancel.
* @property {Function} onStop Called when dragging stops.
* @property {Function} onDrag Called while dragging.
*/
useEffect(() => _bringToFront(lastDialogId), [_bringToFront, lastDialogId]);
/**
* Creates a new dialog and return its id.
*
@@ -109,20 +79,15 @@ const DialogProvider = ({ children, service }) => {
);
/**
* Dismisses all dialogs.
* Sets the implementation of a dialog service that can be used by extensions.
*
* @returns void
*/
const dismissAll = () => {
setDialogs([]);
};
/**
* Indicate if there are no dialogs present.
*
* @returns True if no dialogs are present.
*/
const isEmpty = () => dialogs && dialogs.length < 1;
useEffect(() => {
if (service) {
service.setServiceImplementation({ create, dismiss, dismissAll });
}
}, [create, dismiss, service]);
/**
* Moves the dialog to the foreground if clicked.
@@ -139,6 +104,41 @@ const DialogProvider = ({ children, service }) => {
});
}, []);
/**
* UI Dialog
*
* @typedef {Object} DialogProps
* @property {string} id The dialog id.
* @property {DialogContent} content The dialog content.
* @property {Object} contentProps The dialog content props.
* @property {boolean} isDraggable Controls if dialog content is draggable or not.
* @property {boolean} showOverlay Controls dialog overlay.
* @property {boolean} centralize Center the dialog on the screen.
* @property {boolean} preservePosition Use last position instead of default.
* @property {ElementPosition} defaultPosition Specifies the `x` and `y` that the dragged item should start at.
* @property {Function} onStart Called when dragging starts. If `false` is returned any handler, the action will cancel.
* @property {Function} onStop Called when dragging stops.
* @property {Function} onDrag Called while dragging.
*/
useEffect(() => _bringToFront(lastDialogId), [_bringToFront, lastDialogId]);
/**
* Dismisses all dialogs.
*
* @returns void
*/
const dismissAll = () => {
setDialogs([]);
};
/**
* Indicate if there are no dialogs present.
*
* @returns True if no dialogs are present.
*/
const isEmpty = () => dialogs && dialogs.length < 1;
const renderDialogs = () =>
dialogs.map(dialog => {
const {
@@ -219,14 +219,12 @@ const DialogProvider = ({ children, service }) => {
</Draggable>
);
return (
showOverlay ? (
<div className="Overlay" key={id}>
{dragableItem()}
</div>
) : (
dragableItem()
)
return showOverlay ? (
<div className="Overlay" key={id}>
{dragableItem()}
</div>
) : (
dragableItem()
);
});
@@ -249,11 +247,7 @@ const DialogProvider = ({ children, service }) => {
return (
<DialogContext.Provider value={{ create, dismiss, dismissAll, isEmpty }}>
{!isEmpty() &&
<div className="DraggableArea">
{renderDialogs()}
</div>
}
{!isEmpty() && <div className="DraggableArea">{renderDialogs()}</div>}
{children}
</DialogContext.Provider>
);
@@ -36,21 +36,11 @@ const ModalProvider = ({ children, modal: Modal, service }) => {
closeButton: true,
title: null,
customClassName: '',
fullscreen: false,
};
const [options, setOptions] = useState(DEFAULT_OPTIONS);
/**
* Sets the implementation of a modal service that can be used by extensions.
*
* @returns void
*/
useEffect(() => {
if (service) {
service.setServiceImplementation({ hide, show });
}
}, [hide, service, show]);
/**
* Show the modal and override its configuration props.
*
@@ -70,6 +60,17 @@ const ModalProvider = ({ children, modal: Modal, service }) => {
DEFAULT_OPTIONS,
]);
/**
* Sets the implementation of a modal service that can be used by extensions.
*
* @returns void
*/
useEffect(() => {
if (service) {
service.setServiceImplementation({ hide, show });
}
}, [hide, service, show]);
const {
content: ModalContent,
contentProps,
@@ -78,6 +79,7 @@ const ModalProvider = ({ children, modal: Modal, service }) => {
title,
customClassName,
shouldCloseOnEsc,
fullscreen,
closeButton,
} = options;
@@ -89,6 +91,7 @@ const ModalProvider = ({ children, modal: Modal, service }) => {
shouldCloseOnEsc={shouldCloseOnEsc}
isOpen={isOpen}
title={title}
fullscreen={fullscreen}
closeButton={closeButton}
onClose={() => {
if (onClose) {
@@ -27,17 +27,6 @@ const SnackbarProvider = ({ children, service }) => {
const [count, setCount] = useState(1);
const [snackbarItems, setSnackbarItems] = useState([]);
/**
* Sets the implementation of a notification service that can be used by extensions.
*
* @returns void
*/
useEffect(() => {
if (service) {
service.setServiceImplementation({ hide, show });
}
}, [service, hide, show]);
const show = useCallback(
options => {
if (!options || (!options.title && !options.message)) {
@@ -92,6 +81,17 @@ const SnackbarProvider = ({ children, service }) => {
setSnackbarItems(() => []);
};
/**
* Sets the implementation of a notification service that can be used by extensions.
*
* @returns void
*/
useEffect(() => {
if (service) {
service.setServiceImplementation({ hide, show });
}
}, [service, hide, show]);
/**
* expose snackbar methods to window for debug purposes
* TODO: Check if it's really necessary
+4
View File
@@ -19,6 +19,7 @@ import chevronDown from './icons/chevron-down.svg';
import circle from './icons/circle.svg';
import circleNotch from './icons/circle-notch.svg';
import circleO from './icons/circle-o.svg';
import clipboard from './icons/clipboard.svg';
import cog from './icons/cog.svg';
import createComment from './icons/create-comment.svg';
import createScreenCapture from './icons/create-screen-capture.svg';
@@ -87,6 +88,7 @@ import user from './icons/user.svg';
import youtube from './icons/youtube.svg';
import eye from './icons/eye.svg';
import eyeClosed from './icons/eye-closed.svg';
import envelopeSquare from './icons/envelope-square.svg';
const ICONS = {
eye,
@@ -142,6 +144,7 @@ const ICONS = {
'lock-alt': lockAlt,
'step-backward': stepBackward,
'step-forward': stepForward,
clipboard: clipboard,
stop,
'th-large': thLarge,
'th-list': thList,
@@ -176,6 +179,7 @@ const ICONS = {
lung,
liver,
save: saveRegular,
'envelope-square': envelopeSquare,
};
/**
@@ -0,0 +1,11 @@
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 384 512"
aria-labelledby="title"
width="1em"
height="1em"
fill="currentColor"
>
<title id="title">Clipboard</title>
<path d="M336 64h-80c0-35.3-28.7-64-64-64s-64 28.7-64 64H48C21.5 64 0 85.5 0 112v352c0 26.5 21.5 48 48 48h288c26.5 0 48-21.5 48-48V112c0-26.5-21.5-48-48-48zM192 40c13.3 0 24 10.7 24 24s-10.7 24-24 24-24-10.7-24-24 10.7-24 24-24zm144 418c0 3.3-2.7 6-6 6H54c-3.3 0-6-2.7-6-6V118c0-3.3 2.7-6 6-6h42v36c0 6.6 5.4 12 12 12h168c6.6 0 12-5.4 12-12v-36h42c3.3 0 6 2.7 6 6z"/>
</svg>

After

Width:  |  Height:  |  Size: 560 B

@@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 448 512"
aria-labelledby="envelope-square"
width="1em"
height="1em"
fill="currentColor"
>
<path d="M400 32H48C21.49 32 0 53.49 0 80v352c0 26.51 21.49 48 48 48h352c26.51 0 48-21.49 48-48V80c0-26.51-21.49-48-48-48zM178.117 262.104C87.429 196.287 88.353 196.121 64 177.167V152c0-13.255 10.745-24 24-24h272c13.255 0 24 10.745 24 24v25.167c-24.371 18.969-23.434 19.124-114.117 84.938-10.5 7.655-31.392 26.12-45.883 25.894-14.503.218-35.367-18.227-45.883-25.895zM384 217.775V360c0 13.255-10.745 24-24 24H88c-13.255 0-24-10.745-24-24V217.775c13.958 10.794 33.329 25.236 95.303 70.214 14.162 10.341 37.975 32.145 64.694 32.01 26.887.134 51.037-22.041 64.72-32.025 61.958-44.965 81.325-59.406 95.283-70.199z"/>
</svg>

After

Width:  |  Height:  |  Size: 811 B

Loaded 100 of 121 files, more files were not shown because too many files have changed in this diff. Show more