Compare commits

..
Author SHA1 Message Date
ohif-bot 12e73228d1 chore(release): publish [skip ci]
- @ohif/viewer@4.8.6
2021-01-26 12:43:46 +00:00
Jason Klotzer 4002401c66 fixes #2249 2021-01-26 13:39:48 +01:00
ohif-bot 18a8e059b6 chore(release): publish [skip ci]
- @ohif/extension-dicom-rt@0.6.3
 - @ohif/viewer@4.8.5
2021-01-21 17:33:28 +00:00
Igor Octaviano f3582e514e Merge pull request #2244 from OHIF/IDC-2123
feat(IDC-2123): When RTSTRUCT does not apply to the specific image series, the button and the panel should not be shown
2021-01-21 14:30:41 -03:00
igoroctaviano c76bfd4a58 Merge branch 'IDC-2123' of github.com:OHIF/Viewers into IDC-2123 2021-01-21 14:15:25 -03:00
igoroctaviano 7f8cf5f5da :wq
erge branch 'master' of github.com:OHIF/Viewers into IDC-2123
2021-01-21 14:15:13 -03:00
Igor Octaviano 5ef926477c Merge pull request #2240 from OHIF/IDC-1136
docs(IDC-1136): Add funding acknowledgment of the IDC project
2021-01-21 13:59:34 -03:00
igoroctaviano 87f5b97983 Merge branch 'master' of github.com:OHIF/Viewers into IDC-1136 2021-01-21 13:12:00 -03:00
ohif-bot 4c7b2b4ae0 chore(release): publish [skip ci]
- @ohif/extension-dicom-microscopy@0.51.4
 - @ohif/viewer@4.8.4
2021-01-21 14:59:06 +00:00
igoroctaviano d1e52dd6a6 Merge branch 'master' of github.com:OHIF/Viewers into IDC-1136 2021-01-21 11:55:17 -03:00
Igor Octaviano cb147dfc69 Merge pull request #2241 from OHIF/IDC-2223
feat(IDC-2223): Add error notification on microscopy viewport initialization errors
2021-01-21 11:54:51 -03:00
igoroctaviano a5fdeddfb4 Merge branch 'master' of github.com:OHIF/Viewers into IDC-2223 2021-01-21 10:42:41 -03:00
ohif-bot e0e5f6000f chore(release): publish [skip ci]
- @ohif/extension-dicom-rt@0.6.2
 - @ohif/extension-dicom-segmentation@0.5.1
 - @ohif/viewer@4.8.3
2021-01-21 13:36:40 +00:00
igoroctaviano cffb7d6f2f Merge branch 'master' of github.com:OHIF/Viewers into IDC-2223 2021-01-21 10:29:15 -03:00
Igor Octaviano 360616be13 Merge pull request #2243 from OHIF/IDC-1801
feat(IDC-1801): Improve/harmonize segmentations panel with rtstruct and add new visibility toggles
2021-01-21 10:28:40 -03:00
igoroctaviano d8fe8929b6 Merge branch 'IDC-1801' of github.com:OHIF/Viewers into IDC-1801 2021-01-21 09:37:50 -03:00
igoroctaviano 6b99a21227 Merge branch 'master' of github.com:OHIF/Viewers into IDC-1801 2021-01-21 09:37:39 -03:00
Igor Octaviano 16631e1fc4 Merge branch 'master' into IDC-1801 2021-01-21 09:36:51 -03:00
ohif-bot 587de94cea chore(release): publish [skip ci]
- @ohif/extension-vtk@1.10.2
 - @ohif/core@2.12.1
 - @ohif/ui@1.8.1
 - @ohif/viewer@4.8.2
2021-01-21 12:35:41 +00:00
Igor Octaviano 0ebe8a67d2 Merge branch 'master' into IDC-2123 2021-01-21 09:31:29 -03:00
Igor Octaviano 39a988dac7 Merge branch 'master' into IDC-1136 2021-01-21 09:31:26 -03:00
Igor Octaviano f95928bcdd Merge branch 'master' into IDC-2223 2021-01-21 09:31:22 -03:00
Igor Octaviano 0f736bd294 Merge branch 'master' into IDC-1801 2021-01-21 09:31:17 -03:00
Igor Octaviano 6cbd9839c6 Merge pull request #2246 from OHIF/IDC-2192
feat(IDC-2192): Notify buffer allocation failure trying to do MPR for a large series
2021-01-21 09:31:07 -03:00
igoroctaviano 649bc5825a Trigger netlify deploy 2021-01-21 09:06:29 -03:00
igoroctaviano 8038009afd Add exit mpr action 2021-01-20 20:59:03 -03:00
igoroctaviano f07f21117d Notify user of buffer errors from vtk / add possibility to add actions to notifications 2021-01-20 18:05:20 -03:00
igoroctaviano 77c7463c53 RTSTRUCT does not apply to the specific image series, the button and the panel should not be shown 2021-01-19 17:56:41 -03:00
igoroctaviano 449e8f25f6 Toggle visibility for cornerstone by default 2021-01-19 14:22:52 -03:00
igoroctaviano 437c01511e Toggle visibility for multiple labelmaps (overlapping) 2021-01-19 14:10:40 -03:00
igoroctaviano 00fddc1550 Improve/harmonize segmentations panel and add bulk visibility toggles 2021-01-16 01:19:12 -03:00
igoroctaviano 00b5cccae5 Add error message on microscopy viewport errors 2021-01-15 15:23:43 -03:00
igoroctaviano e545dd5b28 Add funding acknowledgment of the IDC project 2021-01-15 09:53:32 -03:00
ohif-bot efde6ed20e chore(release): publish [skip ci]
- @ohif/extension-dicom-rt@0.6.1
 - @ohif/extension-vtk@1.10.1
 - @ohif/viewer@4.8.1
2020-12-10 16:49:23 +00:00
Igor Octaviano 32022f51f2 fix: panel issues and mpr button (IDC2122-IDC2117) 2020-12-10 17:44:44 +01:00
ohif-bot d282f72d75 chore(release): publish [skip ci]
- @ohif/extension-cornerstone@2.10.0
 - @ohif/extension-dicom-html@1.3.0
 - @ohif/extension-dicom-rt@0.6.0
 - @ohif/extension-dicom-segmentation@0.5.0
 - @ohif/extension-dicom-tag-browser@0.2.0
 - @ohif/extension-vtk@1.10.0
 - @ohif/core@2.12.0
 - @ohif/ui@1.8.0
 - @ohif/viewer@4.8.0
2020-12-10 10:09:50 +00:00
Davide PunzoandIgor Octaviano 29fceacee9 feat: visualize overlapping segments in cornerstone (#2185)
* feat: visualize overlapping segments

load multiple labelMaps (splitted by dcmjs because overlapping) in a segmentations [IDC2164]

Dynamically update active labelmap [IDC2164]

Co-authored-by: Davide Punzo <punzodavide@hotmail.it>
Co-authored-by: Igor Octaviano <igoroctaviano@gmail.com>
2020-12-10 11:04:27 +01:00
Igor Octaviano a7a5a359dc Use runtime values for version (#2204) 2020-12-10 09:56:30 +01:00
ohif-bot e362e895d4 chore(release): publish [skip ci]
- @ohif/viewer@4.7.2
2020-12-03 20:45:12 +00:00
Nikola Mijajlović 45a07dfa19 refactor: Update SidePanel.js to use a functional component (#2179) 2020-12-03 21:39:14 +01:00
ohif-bot 7323e63608 chore(release): publish [skip ci]
- @ohif/extension-vtk@1.9.4
 - @ohif/viewer@4.7.1
2020-12-03 17:20:07 +00:00
Igor OctavianoandDavide Punzo 0baf18dcb2 fix: reset VOI on mpr reset (#2115)
Co-authored-by: Davide Punzo <punzodavide@hotmail.it>
2020-12-03 18:11:14 +01:00
ohif-bot cca3d6bff2 chore(release): publish [skip ci]
- @ohif/extension-dicom-rt@0.5.0
 - @ohif/viewer@4.7.0
2020-12-03 16:47:50 +00:00
Igor OctavianoandDavide Punzo f3cecf129b feat: Update approach to better handle rtdose instead of disabling panel
Co-authored-by: Davide Punzo <punzodavide@hotmail.it>
2020-12-03 17:36:02 +01:00
ohif-bot fe03fc6f55 chore(release): publish [skip ci]
- @ohif/extension-vtk@1.9.3
 - @ohif/viewer@4.6.3
2020-12-03 16:11:30 +00:00
Davide Punzo b59187c74d Re #2113: disable 2D MPR when datasets have multiple slices for the same ImagePositionPatient (#2160)
fix: disable 2D MPR when datasets are 4D
2020-12-03 17:08:38 +01:00
ohif-bot 5210c80369 chore(release): publish [skip ci]
- @ohif/extension-dicom-tag-browser@0.1.1
 - @ohif/extension-vtk@1.9.2
 - @ohif/core@2.11.1
 - @ohif/ui@1.7.1
 - @ohif/viewer@4.6.2
2020-12-03 15:19:00 +00:00
Igor OctavianoandDavide Punzo f57725ac8b fix: fallback to original dataset VR for XS
Co-authored-by: Davide Punzo <punzodavide@hotmail.it>
2020-12-03 16:15:27 +01:00
53 changed files with 1509 additions and 711 deletions

No files matched your search

+4 -2
View File
@@ -3,7 +3,6 @@ const dotenv = require('dotenv');
// //
const path = require('path'); const path = require('path');
const webpack = require('webpack'); const webpack = require('webpack');
const PACKAGE = require('../platform/viewer/package.json');
// ~~ RULES // ~~ RULES
const loadShadersRule = require('./rules/loadShaders.js'); const loadShadersRule = require('./rules/loadShaders.js');
const loadWebWorkersRule = require('./rules/loadWebWorkers.js'); const loadWebWorkersRule = require('./rules/loadWebWorkers.js');
@@ -77,7 +76,10 @@ module.exports = (env, argv, { SRC_DIR, DIST_DIR }) => {
'process.env.DEBUG': JSON.stringify(process.env.DEBUG), 'process.env.DEBUG': JSON.stringify(process.env.DEBUG),
'process.env.APP_CONFIG': JSON.stringify(process.env.APP_CONFIG || ''), 'process.env.APP_CONFIG': JSON.stringify(process.env.APP_CONFIG || ''),
'process.env.PUBLIC_URL': JSON.stringify(process.env.PUBLIC_URL || '/'), 'process.env.PUBLIC_URL': JSON.stringify(process.env.PUBLIC_URL || '/'),
'process.env.VERSION_NUMBER': JSON.stringify(PACKAGE.version || ''), 'process.env.VERSION_NUMBER': webpack.DefinePlugin.runtimeValue(() => {
const package = require('../platform/viewer/package.json');
return JSON.stringify(package.version || '');
}, ['../platform/viewer/package.json']),
'process.env.BUILD_NUM': JSON.stringify(BUILD_NUM), 'process.env.BUILD_NUM': JSON.stringify(BUILD_NUM),
/* i18n */ /* i18n */
'process.env.USE_LOCIZE': JSON.stringify(process.env.USE_LOCIZE || ''), 'process.env.USE_LOCIZE': JSON.stringify(process.env.USE_LOCIZE || ''),
+4 -2
View File
@@ -121,8 +121,7 @@ window.config = {
}; };
``` ```
- Install the viewer: - Install the viewer: `window.OHIFViewer.installViewer(window.config);`
`window.OHIFViewer.installViewer(window.config);`
This exact setup is demonstrated in this This exact setup is demonstrated in this
[CodeSandbox](https://codesandbox.io/s/viewer-script-tag-tprch) and in our [CodeSandbox](https://codesandbox.io/s/viewer-script-tag-tprch) and in our
@@ -249,6 +248,9 @@ To acknowledge the OHIF Viewer in an academic publication, please cite
> >
> _Cancer Research_, November 1 2017 (77) (21) e119-e122 DOI: > _Cancer Research_, November 1 2017 (77) (21) e119-e122 DOI:
> [10.1158/0008-5472.CAN-17-0334](https://www.doi.org/10.1158/0008-5472.CAN-17-0334) > [10.1158/0008-5472.CAN-17-0334](https://www.doi.org/10.1158/0008-5472.CAN-17-0334)
>
> NCI Imaging Data Commons: contract number 19X037Q from Leidos Biomedical
> Research under Task Order HHSN26100071 from NCI.
**Note:** If you use or find this repository helpful, please take the time to **Note:** If you use or find this repository helpful, please take the time to
star this repository on Github. This is an easy way for us to assess adoption star this repository on Github. This is an easy way for us to assess adoption
+11
View File
@@ -3,6 +3,17 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [2.10.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.9.6...@ohif/extension-cornerstone@2.10.0) (2020-12-10)
### Features
* visualize overlapping segments in cornerstone ([#2185](https://github.com/OHIF/Viewers/issues/2185)) ([29fceac](https://github.com/OHIF/Viewers/commit/29fceacee97d51f1952a0f6b574c66596d32c201))
## [2.9.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.9.5...@ohif/extension-cornerstone@2.9.6) (2020-10-07) ## [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 **Note:** Version bump only for package @ohif/extension-cornerstone
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@ohif/extension-cornerstone", "name": "@ohif/extension-cornerstone",
"version": "2.9.6", "version": "2.10.0",
"description": "OHIF extension for Cornerstone", "description": "OHIF extension for Cornerstone",
"author": "OHIF", "author": "OHIF",
"license": "MIT", "license": "MIT",
@@ -36,7 +36,7 @@
"cornerstone-math": "^0.1.8", "cornerstone-math": "^0.1.8",
"cornerstone-tools": "^4.20.1", "cornerstone-tools": "^4.20.1",
"cornerstone-wado-image-loader": "^3.1.0", "cornerstone-wado-image-loader": "^3.1.0",
"dcmjs": "0.16.5", "dcmjs": "0.17.0",
"dicom-parser": "^1.8.3", "dicom-parser": "^1.8.3",
"hammerjs": "^2.0.8", "hammerjs": "^2.0.8",
"prop-types": "^15.6.2", "prop-types": "^15.6.2",
+11
View File
@@ -3,6 +3,17 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.3.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.2.9...@ohif/extension-dicom-html@1.3.0) (2020-12-10)
### Features
* visualize overlapping segments in cornerstone ([#2185](https://github.com/OHIF/Viewers/issues/2185)) ([29fceac](https://github.com/OHIF/Viewers/commit/29fceacee97d51f1952a0f6b574c66596d32c201))
## [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) ## [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 **Note:** Version bump only for package @ohif/extension-dicom-html
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@ohif/extension-dicom-html", "name": "@ohif/extension-dicom-html",
"version": "1.2.9", "version": "1.3.0",
"description": "OHIF extension for rendering structured reports to HTML", "description": "OHIF extension for rendering structured reports to HTML",
"author": "OHIF", "author": "OHIF",
"license": "MIT", "license": "MIT",
@@ -29,7 +29,7 @@
}, },
"peerDependencies": { "peerDependencies": {
"@ohif/core": "^0.50.0", "@ohif/core": "^0.50.0",
"dcmjs": "0.16.5", "dcmjs": "0.17.0",
"prop-types": "^15.6.2", "prop-types": "^15.6.2",
"react": "^16.8.6", "react": "^16.8.6",
"react-dom": "^16.8.6" "react-dom": "^16.8.6"
+8
View File
@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [0.51.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-microscopy@0.51.3...@ohif/extension-dicom-microscopy@0.51.4) (2021-01-21)
**Note:** Version bump only for package @ohif/extension-dicom-microscopy
## [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) ## [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 **Note:** Version bump only for package @ohif/extension-dicom-microscopy
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@ohif/extension-dicom-microscopy", "name": "@ohif/extension-dicom-microscopy",
"version": "0.51.3", "version": "0.51.4",
"description": "OHIF extension for Dicom Microscopy", "description": "OHIF extension for Dicom Microscopy",
"author": "OHIF", "author": "OHIF",
"license": "MIT", "license": "MIT",
@@ -62,11 +62,24 @@ class DicomMicroscopyViewport extends Component {
); );
const microscopyViewer = api.VLWholeSlideMicroscopyImageViewer; const microscopyViewer = api.VLWholeSlideMicroscopyImageViewer;
this.viewer = new microscopyViewer({ try {
client: dicomWebClient, this.viewer = new microscopyViewer({
metadata, client: dicomWebClient,
retrieveRendered: false, metadata,
}); retrieveRendered: false,
});
} catch (error) {
console.error('[Microscopy Viewer] Failed to load:', error);
const { UINotificationService } = this.props.servicesManager.services;
if (UINotificationService) {
UINotificationService.show({
title: 'Microscopy Viewport',
message:
'Failed to load viewport. Please check that you have hardware acceleration enabled.',
type: 'error',
});
}
}
this.viewer.render({ container }); this.viewer.render({ container });
}); });
@@ -92,8 +105,8 @@ class DicomMicroscopyViewport extends Component {
{this.state.error ? ( {this.state.error ? (
<h2>{JSON.stringify(this.state.error)}</h2> <h2>{JSON.stringify(this.state.error)}</h2>
) : ( ) : (
<div style={style} ref={this.container} /> <div style={style} ref={this.container} />
)} )}
</div> </div>
); );
} }
+8 -10
View File
@@ -6,14 +6,6 @@ const Component = React.lazy(() => {
return import('./DicomMicroscopyViewport'); return import('./DicomMicroscopyViewport');
}); });
const DicomMicroscopyViewport = props => {
return (
<React.Suspense fallback={<div>Loading...</div>}>
<Component {...props} />
</React.Suspense>
);
};
export default { export default {
/** /**
* Only required property. Should be a unique value across all extensions. * Only required property. Should be a unique value across all extensions.
@@ -21,8 +13,14 @@ export default {
id: 'microscopy', id: 'microscopy',
version, version,
getViewportModule() { getViewportModule({ servicesManager }) {
return DicomMicroscopyViewport; return props => {
return (
<React.Suspense fallback={<div>Loading...</div>}>
<Component {...props} servicesManager={servicesManager} />
</React.Suspense>
);
};
}, },
getSopClassHandlerModule() { getSopClassHandlerModule() {
return DicomMicroscopySopClassHandler; return DicomMicroscopySopClassHandler;
+49
View File
@@ -3,6 +3,55 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [0.6.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.6.2...@ohif/extension-dicom-rt@0.6.3) (2021-01-21)
**Note:** Version bump only for package @ohif/extension-dicom-rt
## [0.6.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.6.1...@ohif/extension-dicom-rt@0.6.2) (2021-01-21)
**Note:** Version bump only for package @ohif/extension-dicom-rt
## [0.6.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.6.0...@ohif/extension-dicom-rt@0.6.1) (2020-12-10)
### Bug Fixes
* panel issues and mpr button (IDC2122-IDC2117) ([32022f5](https://github.com/OHIF/Viewers/commit/32022f51f2d24f53f4c98188980db038a6dfe76c))
# [0.6.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.5.0...@ohif/extension-dicom-rt@0.6.0) (2020-12-10)
### Features
* visualize overlapping segments in cornerstone ([#2185](https://github.com/OHIF/Viewers/issues/2185)) ([29fceac](https://github.com/OHIF/Viewers/commit/29fceacee97d51f1952a0f6b574c66596d32c201))
# [0.5.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.4.9...@ohif/extension-dicom-rt@0.5.0) (2020-12-03)
### Features
* Update approach to better handle rtdose instead of disabling panel ([f3cecf1](https://github.com/OHIF/Viewers/commit/f3cecf129b16f8d24d239aa44fdff7f46e6ba683))
## [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) ## [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 **Note:** Version bump only for package @ohif/extension-dicom-rt
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@ohif/extension-dicom-rt", "name": "@ohif/extension-dicom-rt",
"version": "0.4.9", "version": "0.6.3",
"description": "OHIF extension for rendering DICOM RTSTRUCTs on top of cornerstone images.", "description": "OHIF extension for rendering DICOM RTSTRUCTs on top of cornerstone images.",
"author": "OHIF", "author": "OHIF",
"license": "MIT", "license": "MIT",
@@ -31,7 +31,7 @@
"@ohif/core": "^0.50.0", "@ohif/core": "^0.50.0",
"cornerstone-core": "^2.2.8", "cornerstone-core": "^2.2.8",
"cornerstone-tools": "^4.20.1", "cornerstone-tools": "^4.20.1",
"dcmjs": "0.16.5", "dcmjs": "0.17.0",
"gl-matrix": "^3.3.0", "gl-matrix": "^3.3.0",
"prop-types": "^15.6.2", "prop-types": "^15.6.2",
"react": "^16.8.6", "react": "^16.8.6",
@@ -15,7 +15,7 @@ const OHIFDicomRTStructSopClassHandler = {
id: 'OHIFDicomRTStructSopClassHandler', id: 'OHIFDicomRTStructSopClassHandler',
type: MODULE_TYPES.SOP_CLASS_HANDLER, type: MODULE_TYPES.SOP_CLASS_HANDLER,
sopClassUIDs, sopClassUIDs,
getDisplaySetFromSeries: function( getDisplaySetFromSeries: function (
series, series,
study, study,
dicomWebClient, dicomWebClient,
@@ -71,11 +71,11 @@ const OHIFDicomRTStructSopClassHandler = {
} }
} }
rtStructDisplaySet.getSourceDisplaySet = function(studies) { rtStructDisplaySet.getSourceDisplaySet = function (studies) {
return getSourceDisplaySet(studies, rtStructDisplaySet); return getSourceDisplaySet(studies, rtStructDisplaySet);
}; };
rtStructDisplaySet.load = function(referencedDisplaySet, studies) { rtStructDisplaySet.load = function (referencedDisplaySet, studies) {
return loadRTStruct( return loadRTStruct(
rtStructDisplaySet, rtStructDisplaySet,
referencedDisplaySet, referencedDisplaySet,
@@ -39,6 +39,8 @@
border-radius: 5px; border-radius: 5px;
} }
.icons .angle-double-down, .icons .angle-double-up { .icons .angle-double-down,
.icons .angle-double-up {
margin-left: 15px; margin-left: 15px;
margin-right: -5px;
} }
@@ -9,16 +9,21 @@
.dcmrt-panel .dcmrt-panel-header { .dcmrt-panel .dcmrt-panel-header {
display: flex; display: flex;
flex-direction: row; flex-direction: column;
justify-content: space-between; justify-content: space-between;
color: white; color: white;
} }
.dcmrt-panel .dcmrt-panel-header h3 {
margin-top: 0;
}
.dcmrt-panel .dcmrt-panel-header .cog-icon { .dcmrt-panel .dcmrt-panel-header .cog-icon {
align-self: flex-end; align-self: flex-end;
color: var(--default-color); color: var(--default-color);
cursor: pointer; cursor: pointer;
margin-bottom: 20px; min-height: 23px;
max-height: 23px;
} }
.dcmrt-panel .dcmrt-panel-header .cog-icon:hover { .dcmrt-panel .dcmrt-panel-header .cog-icon:hover {
@@ -1,4 +1,4 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect, useCallback } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import cornerstoneTools from 'cornerstone-tools'; import cornerstoneTools from 'cornerstone-tools';
import cornerstone from 'cornerstone-core'; import cornerstone from 'cornerstone-core';
@@ -31,19 +31,20 @@ const refreshViewport = () => {
* *
* @param {Object} props * @param {Object} props
* @param {Array} props.studies * @param {Array} props.studies
* @param {Array} props.viewports - viewportSpecificData * @param {Array} props.getActiveViewport - get active viewport data
* @param {number} props.activeIndex - activeViewportIndex * @param {number} props.activeIndex - activeViewportIndex
* @param {number} props.isOpen - isOpen * @param {number} props.isOpen - isOpen
* @returns component * @returns component
*/ */
const RTPanel = ({ const RTPanel = ({
studies, studies,
viewports,
activeIndex, activeIndex,
isOpen, isOpen,
onContourItemClick, onContourItemClick,
activeContexts = [], activeContexts = [],
contexts = {}, contexts = {},
activeViewport,
getActiveViewport,
}) => { }) => {
const isVTK = () => activeContexts.includes(contexts.VTK); const isVTK = () => activeContexts.includes(contexts.VTK);
const isCornerstone = () => activeContexts.includes(contexts.CORNERSTONE); const isCornerstone = () => activeContexts.includes(contexts.CORNERSTONE);
@@ -54,11 +55,11 @@ const RTPanel = ({
referencedDisplaysets: [], referencedDisplaysets: [],
sets: [], sets: [],
selectedSet: null, selectedSet: null,
isLocked: false,
}; };
const [state, setState] = useState(DEFAULT_STATE); const [state, setState] = useState(DEFAULT_STATE);
const [showSettings, setShowSettings] = useState(false); const [showSettings, setShowSettings] = useState(false);
const activeViewport = viewports[activeIndex];
/* /*
* TODO: Improve the way we notify parts of the app that depends on rts to be loaded. * TODO: Improve the way we notify parts of the app that depends on rts to be loaded.
@@ -78,6 +79,8 @@ const RTPanel = ({
* without subscribing to external events. * without subscribing to external events.
*/ */
useEffect(() => { useEffect(() => {
updateStructureSets();
document.addEventListener('extensiondicomrtrtloaded', updateStructureSets); document.addEventListener('extensiondicomrtrtloaded', updateStructureSets);
return () => { return () => {
@@ -88,20 +91,30 @@ const RTPanel = ({
}; };
}, []); }, []);
useEffect(() => {
const studyMetadata = studyMetadataManager.get(
activeViewport.StudyInstanceUID
);
const referencedDisplaysets = studyMetadata.getDerivedDatasets({
referencedSeriesInstanceUID: activeViewport.SeriesInstanceUID,
Modality: 'RTSTRUCT',
});
setState(state => ({ ...state, isLocked: !referencedDisplaysets.length }));
}, [activeViewport]);
const updateStructureSets = () => { const updateStructureSets = () => {
const viewport = getActiveViewport();
const module = cornerstoneTools.getModule('rtstruct'); const module = cornerstoneTools.getModule('rtstruct');
const StructureSets = module.state.StructureSets; const StructureSets = module.state.StructureSets;
if (StructureSets && StructureSets.length) { if (StructureSets && StructureSets.length) {
const viewportSets = module.getters.structuresSetsWhichReferenceSeriesInstanceUid( const viewportSets = module.getters.structuresSetsWhichReferenceSeriesInstanceUid(
activeViewport.SeriesInstanceUID viewport.SeriesInstanceUID
); );
const studyMetadata = studyMetadataManager.get( const studyMetadata = studyMetadataManager.get(viewport.StudyInstanceUID);
activeViewport.StudyInstanceUID
);
const referencedDisplaysets = studyMetadata.getDerivedDatasets({ const referencedDisplaysets = studyMetadata.getDerivedDatasets({
referencedSeriesInstanceUID: activeViewport.SeriesInstanceUID, referencedSeriesInstanceUID: viewport.SeriesInstanceUID,
Modality: 'RTSTRUCT', Modality: 'RTSTRUCT',
}); });
@@ -118,10 +131,6 @@ const RTPanel = ({
} }
}; };
useEffect(() => {
updateStructureSets();
}, [studies, viewports, activeIndex]);
useEffect(() => { useEffect(() => {
setShowSettings(showSettings && !isOpen); setShowSettings(showSettings && !isOpen);
}, [isOpen]); }, [isOpen]);
@@ -231,16 +240,19 @@ const RTPanel = ({
return ( return (
<div className="dcmrt-panel"> <div className="dcmrt-panel">
<div className="dcmrt-panel-header"> <div className="dcmrt-panel-header">
{' '}
{!state.isLocked && (
<Icon
className="cog-icon"
name="cog"
width="25px"
height="25px"
onClick={() => setShowSettings(true)}
/>
)}
<h3>RT Structure Sets</h3> <h3>RT Structure Sets</h3>
<Icon
className="cog-icon"
name="cog"
width="25px"
height="25px"
onClick={() => setShowSettings(true)}
/>
</div> </div>
{!state.referencedDisplaysets.length && ( {!state.isLocked && !state.referencedDisplaysets.length && (
<LoadingIndicator expand height="70px" width="70px" /> <LoadingIndicator expand height="70px" width="70px" />
)} )}
{state.sets && {state.sets &&
@@ -249,12 +261,15 @@ const RTPanel = ({
const module = cornerstoneTools.getModule('rtstruct'); const module = cornerstoneTools.getModule('rtstruct');
const sets = module.getters.structuresSetsWhichReferenceSeriesInstanceUid( const sets = module.getters.structuresSetsWhichReferenceSeriesInstanceUid(
viewports[activeIndex].SeriesInstanceUID activeViewport.SeriesInstanceUID
); );
const loadedSet = sets.find( const loadedSet = sets.find(
set => set.SeriesInstanceUID === SeriesInstanceUID set => set.SeriesInstanceUID === SeriesInstanceUID
); );
if (!loadedSet) return null;
return ( return (
<PanelSection <PanelSection
key={SeriesInstanceUID} key={SeriesInstanceUID}
@@ -276,17 +291,17 @@ const RTPanel = ({
module.setters.hideStructureSet(loadedSet.SeriesInstanceUID); module.setters.hideStructureSet(loadedSet.SeriesInstanceUID);
} }
const sets = module.getters.structuresSetsWhichReferenceSeriesInstanceUid( const sets = module.getters.structuresSetsWhichReferenceSeriesInstanceUid(
viewports[activeIndex].SeriesInstanceUID activeViewport.SeriesInstanceUID
); );
setState(state => ({ ...state, sets })); setState(state => ({ ...state, sets }));
refreshViewport(); refreshViewport();
}} }}
onExpandChange={async () => { onExpandChange={async () => {
if (!isLoaded) { if (!isLoaded) {
await displaySet.load(viewports[activeIndex], studies); await displaySet.load(activeViewport, studies);
const module = cornerstoneTools.getModule('rtstruct'); const module = cornerstoneTools.getModule('rtstruct');
const sets = module.getters.structuresSetsWhichReferenceSeriesInstanceUid( const sets = module.getters.structuresSetsWhichReferenceSeriesInstanceUid(
viewports[activeIndex].SeriesInstanceUID activeViewport.SeriesInstanceUID
); );
const selectedSet = sets.find( const selectedSet = sets.find(
set => set.SeriesInstanceUID === SeriesInstanceUID set => set.SeriesInstanceUID === SeriesInstanceUID
+27 -14
View File
@@ -5,6 +5,9 @@ import id from './id.js';
import RTPanel from './components/RTPanel/RTPanel'; import RTPanel from './components/RTPanel/RTPanel';
import { version } from '../package.json'; import { version } from '../package.json';
import { utils } from '@ohif/core';
const { studyMetadataManager } = utils;
export default { export default {
/** /**
* Only required property. Should be a unique value across all extensions. * Only required property. Should be a unique value across all extensions.
@@ -45,24 +48,34 @@ export default {
icon: 'list', icon: 'list',
label: 'RTSTRUCT', label: 'RTSTRUCT',
target: 'rt-panel', target: 'rt-panel',
isDisabled: studies => { isDisabled: (studies, activeViewport) => {
if (!studies) { if (!studies) {
return true; return true;
} }
for (let i = 0; i < studies.length; i++) { if (activeViewport) {
const study = studies[i]; const study = studies.find(
s => s.StudyInstanceUID === activeViewport.StudyInstanceUID
if (study && study.series) { );
for (let j = 0; j < study.series.length; j++) { const ds = study.displaySets.find(
const series = study.series[j]; ds =>
if ( ds.displaySetInstanceUID ===
/* Could be expanded to contain RTPLAN and RTDOSE information in the future */ activeViewport.displaySetInstanceUID
['RTSTRUCT'].includes(series.Modality) );
) { const studyMetadata = studyMetadataManager.get(
return false; activeViewport.StudyInstanceUID
} );
} const referencedDisplaySets = studyMetadata.getDerivedDatasets({
referencedSeriesInstanceUID: activeViewport.SeriesInstanceUID,
Modality: 'RTSTRUCT',
});
if (
referencedDisplaySets &&
referencedDisplaySets.some(ds =>
['RTSTRUCT'].includes(ds.Modality)
)
) {
return false;
} }
} }
+11 -6
View File
@@ -90,12 +90,17 @@ export default async function loadRTStruct(
const sopInstanceUID = ContourImageSequence const sopInstanceUID = ContourImageSequence
? ContourImageSequence.ReferencedSOPInstanceUID ? ContourImageSequence.ReferencedSOPInstanceUID
: _getClosestSOPInstanceUID( : _getClosestSOPInstanceUID(
ContourData, ContourData,
ContourGeometricType, ContourGeometricType,
NumberOfContourPoints, NumberOfContourPoints,
imageIdSopInstanceUidPairs imageIdSopInstanceUidPairs
); );
const imageId = _getImageId(imageIdSopInstanceUidPairs, sopInstanceUID); const imageId = _getImageId(imageIdSopInstanceUidPairs, sopInstanceUID);
if (!imageId) {
continue;
}
const imageIdSpecificToolData = _getOrCreateImageIdSpecificToolData( const imageIdSpecificToolData = _getOrCreateImageIdSpecificToolData(
toolState, toolState,
imageId, imageId,
@@ -294,7 +299,7 @@ const _getImageId = (imageIdSopInstanceUidPairs, sopInstanceUID) => {
imageIdSopInstanceUidPairsEntry.sopInstanceUID === sopInstanceUID imageIdSopInstanceUidPairsEntry.sopInstanceUID === sopInstanceUID
); );
return imageIdSopInstanceUidPairsEntry.imageId; return imageIdSopInstanceUidPairsEntry ? imageIdSopInstanceUidPairsEntry.imageId : null;
}; };
function _getImageIdSopInstanceUidPairsForDisplaySet( function _getImageIdSopInstanceUidPairsForDisplaySet(
@@ -3,6 +3,25 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [0.5.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.5.0...@ohif/extension-dicom-segmentation@0.5.1) (2021-01-21)
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
# [0.5.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.4.7...@ohif/extension-dicom-segmentation@0.5.0) (2020-12-10)
### Features
* visualize overlapping segments in cornerstone ([#2185](https://github.com/OHIF/Viewers/issues/2185)) ([29fceac](https://github.com/OHIF/Viewers/commit/29fceacee97d51f1952a0f6b574c66596d32c201))
## [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) ## [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 **Note:** Version bump only for package @ohif/extension-dicom-segmentation
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@ohif/extension-dicom-segmentation", "name": "@ohif/extension-dicom-segmentation",
"version": "0.4.7", "version": "0.5.1",
"description": "OHIF extension for viewing segmentations in the 2D MPR view", "description": "OHIF extension for viewing segmentations in the 2D MPR view",
"author": "OHIF", "author": "OHIF",
"license": "MIT", "license": "MIT",
@@ -31,7 +31,7 @@
"@ohif/core": "^0.50.0", "@ohif/core": "^0.50.0",
"cornerstone-core": "^2.2.8", "cornerstone-core": "^2.2.8",
"cornerstone-tools": "^4.20.1", "cornerstone-tools": "^4.20.1",
"dcmjs": "0.16.5", "dcmjs": "0.17.0",
"prop-types": "^15.6.2", "prop-types": "^15.6.2",
"react": "^16.8.6", "react": "^16.8.6",
"react-dom": "^16.8.6" "react-dom": "^16.8.6"
@@ -24,7 +24,7 @@ const SegmentItem = ({
onClick, onClick,
itemClass, itemClass,
color, color,
visible = true, visible,
onVisibilityChange, onVisibilityChange,
}) => { }) => {
const [isVisible, setIsVisible] = useState(visible); const [isVisible, setIsVisible] = useState(visible);
@@ -33,6 +33,15 @@ const SegmentItem = ({
setIsVisible(visible); setIsVisible(visible);
}, [visible]); }, [visible]);
const onClickHandler = () => onClick(index);
const onVisibilityChangeHandler = event => {
event.stopPropagation();
const newVisibility = !isVisible;
setIsVisible(newVisibility);
onVisibilityChange(newVisibility, index);
};
return ( return (
<div className="dcmseg-segment-item"> <div className="dcmseg-segment-item">
<TableListItem <TableListItem
@@ -42,7 +51,7 @@ const SegmentItem = ({
itemClass={itemClass} itemClass={itemClass}
itemMeta={<ColoredCircle color={color} />} itemMeta={<ColoredCircle color={color} />}
itemMetaClass="segment-color-section" itemMetaClass="segment-color-section"
onItemClick={onClick} onItemClick={onClickHandler}
> >
<div> <div>
<div className="segment-label" style={{ marginBottom: 4 }}> <div className="segment-label" style={{ marginBottom: 4 }}>
@@ -63,12 +72,7 @@ const SegmentItem = ({
name={isVisible ? 'eye' : 'eye-closed'} name={isVisible ? 'eye' : 'eye-closed'}
width="20px" width="20px"
height="20px" height="20px"
onClick={event => { onClick={onVisibilityChangeHandler}
event.stopPropagation();
const newVisibility = !isVisible;
setIsVisible(newVisibility);
onVisibilityChange(newVisibility);
}}
/> />
</div> </div>
{false && <div className="segment-info">{'...'}</div>} {false && <div className="segment-info">{'...'}</div>}
@@ -6,6 +6,7 @@
width: 100%; width: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
padding: 20px;
} }
.dcmseg-segmentation-panel.disabled { .dcmseg-segmentation-panel.disabled {
@@ -14,19 +15,19 @@
} }
.dcmseg-segmentation-panel h3 { .dcmseg-segmentation-panel h3 {
padding-left: 20px;
margin-top: 0; margin-top: 0;
} }
.dcmseg-segmentation-panel .segmentations { .dcmseg-segmentation-panel .segmentations {
padding: 0 20px 20px 20px; padding-bottom: 10px;
} }
.dcmseg-segmentation-panel .cog-icon { .dcmseg-segmentation-panel .cog-icon {
margin: 20px 20px 0 0;
align-self: flex-end; align-self: flex-end;
color: var(--default-color); color: var(--default-color);
cursor: pointer; cursor: pointer;
min-height: 23px;
max-height: 23px;
} }
.dcmseg-segmentation-panel .cog-icon:hover { .dcmseg-segmentation-panel .cog-icon:hover {
@@ -48,11 +49,11 @@
height: 150px; height: 150px;
} }
.dcmseg-segmentation-panel .selector-form>div { .dcmseg-segmentation-panel .selector-form > div {
width: 100%; width: 100%;
} }
.dcmseg-segmentation-panel .selector-form>div:first-of-type { .dcmseg-segmentation-panel .selector-form > div:first-of-type {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: center; justify-content: center;
@@ -64,3 +65,61 @@
flex: unset; flex: unset;
margin-right: 16px; margin-right: 16px;
} }
.SegmentsSection {
display: flex;
flex-direction: column;
color: var(--text-primary-color);
font-size: 12px;
margin-bottom: 0px;
height: 100%;
}
.SegmentsSection .header {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
background-color: var(--ui-gray-dark);
border-radius: 5px;
font-weight: bold;
padding: 10px 15px;
}
.SegmentsSection .count {
font-size: 15px;
color: var(--active-color);
margin-left: 15px;
border-radius: 5px;
background-color: var(--ui-gray-darker);
text-align: center;
min-width: 25px;
}
.SegmentsSection .header .eye-icon,
.SegmentsSection .header .angle-double-down,
.SegmentsSection .header .angle-double-up {
cursor: pointer;
color: var(--active-color);
}
.SegmentsSection .header .eye-icon:hover,
.SegmentsSection .header .angle-double-down:hover,
.SegmentsSection .header .angle-double-up:hover {
color: var(--hover-color);
}
.SegmentsSection .header .eye-icon.expanded {
color: var(--default-color);
}
.SegmentsSection .header .icons {
display: flex;
align-items: center;
border-radius: 5px;
}
.SegmentsSection .header .icons .angle-double-down,
.SegmentsSection .header .icons .angle-double-up {
margin-left: 15px;
}
@@ -1,8 +1,9 @@
import React, { useState, useEffect, useCallback } from 'react'; import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import cornerstoneTools from 'cornerstone-tools'; import cornerstoneTools from 'cornerstone-tools';
import cornerstone from 'cornerstone-core'; import cornerstone from 'cornerstone-core';
import moment from 'moment'; import moment from 'moment';
import classNames from 'classnames';
import { utils, log } from '@ohif/core'; import { utils, log } from '@ohif/core';
import { ScrollableArea, TableList, Icon } from '@ohif/ui'; import { ScrollableArea, TableList, Icon } from '@ohif/ui';
import DICOMSegTempCrosshairsTool from '../../tools/DICOMSegTempCrosshairsTool'; import DICOMSegTempCrosshairsTool from '../../tools/DICOMSegTempCrosshairsTool';
@@ -67,29 +68,104 @@ const SegmentationPanel = ({
const [state, setState] = useState({ const [state, setState] = useState({
brushRadius: DEFAULT_BRUSH_RADIUS, brushRadius: DEFAULT_BRUSH_RADIUS,
brushColor: 'rgba(221, 85, 85, 1)', brushColor: 'rgba(221, 85, 85, 1)',
selectedSegment: null, selectedSegment: 0,
selectedSegmentation: null, selectedSegmentation: 0,
showSegmentationSettings: false, showSettings: false,
brushStackState: null, labelMapList: [],
labelmapList: [],
segmentList: [], segmentList: [],
cachedSegmentsProperties: [], segmentsHidden: [],
segmentNumbers: [],
isLoading: false, isLoading: false,
isDisabled: true, isDisabled: true,
}); });
const getActiveViewport = () => viewports[activeIndex];
const getFirstImageId = () => {
const { StudyInstanceUID, displaySetInstanceUID } = getActiveViewport();
const studyMetadata = studyMetadataManager.get(StudyInstanceUID);
return studyMetadata.getFirstImageId(displaySetInstanceUID);
};
const getActiveLabelMaps3D = () => {
const { labelmaps3D, activeLabelmapIndex } = getBrushStackState();
return labelmaps3D[activeLabelmapIndex];
};
const getActiveLabelMapIndex = () => {
const { activeLabelmapIndex } = getBrushStackState();
return activeLabelmapIndex;
};
const getActiveSegmentIndex = () => {
const { activeSegmentIndex } = getActiveLabelMaps3D();
return activeSegmentIndex;
};
const getActiveLabelMaps2D = () => {
const { labelmaps2D } = getActiveLabelMaps3D();
return labelmaps2D;
};
const getCurrentDisplaySet = () => {
const { StudyInstanceUID, displaySetInstanceUID } = getActiveViewport();
const studyMetadata = studyMetadataManager.get(StudyInstanceUID);
const allDisplaySets = studyMetadata.getDisplaySets();
return allDisplaySets.find(
ds => ds.displaySetInstanceUID === displaySetInstanceUID
);
};
const setActiveSegment = segmentIndex => {
const activeSegmentIndex = getActiveSegmentIndex();
const activeViewport = getActiveViewport();
if (segmentIndex === activeSegmentIndex) {
log.info(`${activeSegmentIndex} is already the active segment`);
return;
}
const labelmap3D = getActiveLabelMaps3D();
labelmap3D.activeSegmentIndex = segmentIndex;
/**
* Activates the correct label map if clicked segment
* does not belong to the active labelmap
*/
const { StudyInstanceUID } = activeViewport;
const studyMetadata = studyMetadataManager.get(StudyInstanceUID);
const allDisplaySets = studyMetadata.getDisplaySets();
let selectedSegmentation;
let newLabelmapIndex = getActiveLabelMapIndex();
allDisplaySets.forEach(displaySet => {
if (displaySet.labelmapSegments) {
Object.keys(displaySet.labelmapSegments).forEach(labelmapIndex => {
if (
displaySet.labelmapSegments[labelmapIndex].includes(segmentIndex)
) {
newLabelmapIndex = labelmapIndex;
selectedSegmentation =
displaySet.hasOverlapping === true
? displaySet.originLabelMapIndex
: labelmapIndex;
}
});
}
});
const brushStackState = getBrushStackState();
brushStackState.activeLabelmapIndex = newLabelmapIndex;
setState(state => ({ ...state, selectedSegmentation }));
refreshViewports();
return segmentIndex;
};
useEffect(() => { useEffect(() => {
const labelmapModifiedHandler = event => { const labelmapModifiedHandler = event => {
log.warn('Segmentation Panel: labelmap modified', event); log.warn('Segmentation Panel: labelmap modified', event);
const module = cornerstoneTools.getModule('segmentation'); refreshSegmentations();
const activeViewport = viewports[activeIndex];
const studyMetadata = studyMetadataManager.get(
activeViewport.StudyInstanceUID
);
const firstImageId = studyMetadata.getFirstImageId(
activeViewport.displaySetInstanceUID
);
updateState('brushStackState', module.state.series[firstImageId]);
}; };
/* /*
@@ -140,50 +216,38 @@ const SegmentationPanel = ({
}; };
}, [activeIndex, viewports]); }, [activeIndex, viewports]);
const refreshSegmentations = useCallback(() => { const refreshSegmentations = () => {
const module = cornerstoneTools.getModule('segmentation'); const activeViewport = getActiveViewport();
const activeViewport = viewports[activeIndex];
const isDisabled = !activeViewport || !activeViewport.StudyInstanceUID; const isDisabled = !activeViewport || !activeViewport.StudyInstanceUID;
if (!isDisabled) { if (!isDisabled) {
const studyMetadata = studyMetadataManager.get( const brushStackState = getBrushStackState();
activeViewport.StudyInstanceUID
);
const firstImageId = studyMetadata.getFirstImageId(
activeViewport.displaySetInstanceUID
);
const brushStackState = module.state.series[firstImageId];
if (brushStackState) { if (brushStackState) {
const labelmap3D = const labelMapList = getLabelMapList();
brushStackState.labelmaps3D[brushStackState.activeLabelmapIndex]; const {
const labelmapList = getLabelmapList( items: segmentList,
brushStackState, numbers: segmentNumbers,
firstImageId, segmentsHidden,
activeViewport } = getSegmentList();
);
const segmentList = getSegmentList(
labelmap3D,
firstImageId,
brushStackState
);
setState(state => ({ setState(state => ({
...state, ...state,
brushStackState, segmentsHidden,
selectedSegmentation: brushStackState.activeLabelmapIndex, segmentNumbers,
labelmapList, labelMapList,
segmentList, segmentList,
isDisabled, isDisabled,
})); }));
} else { } else {
setState(state => ({ setState(state => ({
...state, ...state,
labelmapList: [], segmentsHidden: [],
segmentNumbers: [],
labelMapList: [],
segmentList: [], segmentList: [],
isDisabled, isDisabled,
})); }));
} }
} }
}, [viewports, activeIndex, state.isLoading]); };
useEffect(() => { useEffect(() => {
refreshSegmentations(); refreshSegmentations();
@@ -200,306 +264,285 @@ const SegmentationPanel = ({
useEffect(() => { useEffect(() => {
setState(state => ({ setState(state => ({
...state, ...state,
showSegmentationSettings: state.showSegmentationSettings && !isOpen, showSettings: state.showSettings && !isOpen,
})); }));
}, [isOpen]); }, [isOpen]);
const getLabelmapList = useCallback( const getLabelMapList = () => {
(brushStackState, firstImageId, activeViewport) => { const activeViewport = getActiveViewport();
/* Get list of SEG labelmaps specific to active viewport (reference series) */
const referencedSegDisplaysets = _getReferencedSegDisplaysets(
activeViewport.StudyInstanceUID,
activeViewport.SeriesInstanceUID
);
return referencedSegDisplaysets.map((displaySet, index) => { /* Get list of SEG labelmaps specific to active viewport (reference series) */
const { labelmapIndex, SeriesDate, SeriesTime } = displaySet; const referencedSegDisplaysets = _getReferencedSegDisplaysets(
activeViewport.StudyInstanceUID,
activeViewport.SeriesInstanceUID
);
/* Map to display representation */ return referencedSegDisplaysets.map((displaySet, index) => {
const dateStr = `${SeriesDate}:${SeriesTime}`.split('.')[0]; const {
const date = moment(dateStr, 'YYYYMMDD:HHmmss'); labelmapIndex,
const isActiveLabelmap = originLabelMapIndex,
labelmapIndex === brushStackState.activeLabelmapIndex; hasOverlapping,
const displayDate = date.format('ddd, MMM Do YYYY'); SeriesDate,
const displayTime = date.format('h:mm:ss a'); SeriesTime,
const displayDescription = displaySet.SeriesDescription; } = displaySet;
return { /* Map to display representation */
value: labelmapIndex, const dateStr = `${SeriesDate}:${SeriesTime}`.split('.')[0];
title: displayDescription, const date = moment(dateStr, 'YYYYMMDD:HHmmss');
description: displayDate, const displayDate = date.format('ddd, MMM Do YYYY');
onClick: async () => { const displayDescription = displaySet.SeriesDescription;
const activatedLabelmapIndex = await setActiveLabelmap(
activeViewport,
studies,
displaySet,
() => onSelectedSegmentationChange(),
onDisplaySetLoadFailure
);
updateState('selectedSegmentation', activatedLabelmapIndex);
},
};
});
},
[studies]
);
const getSegmentList = useCallback( return {
(labelmap3D, firstImageId, brushStackState) => { value: hasOverlapping === true ? originLabelMapIndex : labelmapIndex,
/* title: displayDescription,
* Newly created segments have no `meta` description: displayDate,
* So we instead build a list of all segment indexes in use onClick: async () => {
* Then find any associated metadata const activatedLabelmapIndex = await setActiveLabelmap(
*/ activeViewport,
const uniqueSegmentIndexes = labelmap3D.labelmaps2D studies,
.reduce((acc, labelmap2D) => { displaySet,
if (labelmap2D) { onSelectedSegmentationChange,
const segmentIndexes = labelmap2D.segmentsOnLabelmap; onDisplaySetLoadFailure
for (let i = 0; i < segmentIndexes.length; i++) {
if (!acc.includes(segmentIndexes[i]) && segmentIndexes[i] !== 0) {
acc.push(segmentIndexes[i]);
}
}
}
return acc;
}, [])
.sort((a, b) => a - b);
const module = cornerstoneTools.getModule('segmentation');
const colorLutTable =
module.state.colorLutTables[labelmap3D.colorLUTIndex];
const hasLabelmapMeta = labelmap3D.metadata && labelmap3D.metadata.data;
const segmentList = [];
for (let i = 0; i < uniqueSegmentIndexes.length; i++) {
const segmentIndex = uniqueSegmentIndexes[i];
const color = colorLutTable[segmentIndex];
let segmentLabel = '(unlabeled)';
let segmentNumber = segmentIndex;
/* Meta */
if (hasLabelmapMeta) {
const segmentMeta = labelmap3D.metadata.data[segmentIndex];
if (segmentMeta) {
segmentNumber = segmentMeta.SegmentNumber;
segmentLabel = segmentMeta.SegmentLabel;
}
}
const sameSegment = state.selectedSegment === segmentNumber;
const setCurrentSelectedSegment = () => {
_setActiveSegment(
firstImageId,
segmentNumber,
labelmap3D.activeSegmentIndex
); );
updateState('selectedSegment', sameSegment ? null : segmentNumber); setState(state => ({
...state,
const validIndexList = []; selectedSegmentation: activatedLabelmapIndex,
labelmap3D.labelmaps2D.forEach((labelMap2D, index) => { }));
if (labelMap2D.segmentsOnLabelmap.includes(segmentNumber)) { },
validIndexList.push(index); };
} });
});
const avg = array => array.reduce((a, b) => a + b) / array.length;
const average = avg(validIndexList);
const closest = validIndexList.reduce((prev, curr) => {
return Math.abs(curr - average) < Math.abs(prev - average)
? curr
: prev;
});
if (isCornerstone()) {
const enabledElements = cornerstone.getEnabledElements();
const element = enabledElements[activeIndex].element;
const toolState = cornerstoneTools.getToolState(element, 'stack');
if (!toolState) {
return;
}
const imageIds = toolState.data[0].imageIds;
const imageId = imageIds[closest];
const frameIndex = imageIds.indexOf(imageId);
const SOPInstanceUID = cornerstone.metaData.get(
'SOPInstanceUID',
imageId
);
const StudyInstanceUID = cornerstone.metaData.get(
'StudyInstanceUID',
imageId
);
DICOMSegTempCrosshairsTool.addCrosshair(
element,
imageId,
segmentNumber
);
onSegmentItemClick({
StudyInstanceUID,
SOPInstanceUID,
frameIndex,
activeViewportIndex: activeIndex,
});
}
if (isVTK()) {
const activeViewport = viewports[activeIndex];
const studyMetadata = studyMetadataManager.get(
activeViewport.StudyInstanceUID
);
const allDisplaySets = studyMetadata.getDisplaySets();
const currentDisplaySet = allDisplaySets.find(
displaySet =>
displaySet.displaySetInstanceUID ===
activeViewport.displaySetInstanceUID
);
const frame = labelmap3D.labelmaps2D[closest];
onSegmentItemClick({
studies,
StudyInstanceUID: currentDisplaySet.StudyInstanceUID,
displaySetInstanceUID: currentDisplaySet.displaySetInstanceUID,
SOPClassUID: viewports[activeIndex].sopClassUIDs[0],
SOPInstanceUID: currentDisplaySet.SOPInstanceUID,
segmentNumber,
frameIndex: closest,
frame,
});
}
};
const isSegmentVisible = () => {
return !labelmap3D.segmentsHidden[segmentIndex];
};
const toggleSegmentVisibility = () => {
const segmentsHidden = labelmap3D.segmentsHidden;
segmentsHidden[segmentIndex] = !segmentsHidden[segmentIndex];
return !segmentsHidden[segmentIndex];
};
const cachedSegmentProperties =
state.cachedSegmentsProperties[segmentNumber];
let visible = isSegmentVisible();
if (
cachedSegmentProperties &&
cachedSegmentProperties.visible !== visible
) {
toggleSegmentVisibility();
}
segmentList.push(
<SegmentItem
key={segmentNumber}
itemClass={`segment-item ${sameSegment && 'selected'}`}
onClick={() => setCurrentSelectedSegment()}
label={segmentLabel}
index={segmentNumber}
color={color}
visible={visible}
onVisibilityChange={newVisibility => {
if (isCornerstone()) {
const enabledElements = cornerstone.getEnabledElements();
const element = enabledElements[activeIndex].element;
module.setters.toggleSegmentVisibility(
element,
segmentNumber,
brushStackState.activeLabelmapIndex
);
}
if (isVTK()) {
onSegmentVisibilityChange(segmentNumber, newVisibility);
}
updateCachedSegmentsProperties(segmentNumber, {
visible: newVisibility,
});
refreshViewports();
}}
/>
);
}
return segmentList;
/*
* Let's iterate over segmentIndexes ^ above
* If meta has a match, use it to show info
* If now, add "no-meta" class
* Show default name
*/
},
[activeIndex, onSegmentItemClick, state.selectedSegment, state.isLoading]
);
const updateCachedSegmentsProperties = (segmentNumber, properties) => {
const segmentsProperties = state.cachedSegmentsProperties;
const segmentProperties = state.cachedSegmentsProperties[segmentNumber];
segmentsProperties[segmentNumber] = segmentProperties
? { ...segmentProperties, ...properties }
: properties;
updateState('cachedSegmentsProperties', segmentsProperties);
}; };
useEffect(() => { const setCurrentSelectedSegment = segmentNumber => {
updateState('cachedSegmentsProperties', []); setActiveSegment(segmentNumber);
}, [activeContexts]);
const updateState = (field, value) => { const sameSegment = state.selectedSegment === segmentNumber;
setState(state => ({ ...state, [field]: value })); if (!sameSegment) {
setState(state => ({ ...state, selectedSegment: segmentNumber }));
}
const validIndexList = [];
getActiveLabelMaps2D().forEach((labelMap2D, index) => {
if (labelMap2D.segmentsOnLabelmap.includes(segmentNumber)) {
validIndexList.push(index);
}
});
const avg = array => array.reduce((a, b) => a + b) / array.length;
const average = avg(validIndexList);
const closest = validIndexList.reduce((prev, curr) => {
return Math.abs(curr - average) < Math.abs(prev - average) ? curr : prev;
});
if (isCornerstone()) {
const element = getEnabledElement();
const toolState = cornerstoneTools.getToolState(element, 'stack');
if (!toolState) return;
const imageIds = toolState.data[0].imageIds;
const imageId = imageIds[closest];
const frameIndex = imageIds.indexOf(imageId);
const SOPInstanceUID = cornerstone.metaData.get(
'SOPInstanceUID',
imageId
);
const StudyInstanceUID = cornerstone.metaData.get(
'StudyInstanceUID',
imageId
);
DICOMSegTempCrosshairsTool.addCrosshair(element, imageId, segmentNumber);
onSegmentItemClick({
StudyInstanceUID,
SOPInstanceUID,
frameIndex,
activeViewportIndex: activeIndex,
});
}
if (isVTK()) {
const labelMaps3D = getActiveLabelMaps3D();
const currentDisplaySet = getCurrentDisplaySet();
const frame = labelMaps3D.labelmaps2D[closest];
onSegmentItemClick({
studies,
StudyInstanceUID: currentDisplaySet.StudyInstanceUID,
displaySetInstanceUID: currentDisplaySet.displaySetInstanceUID,
SOPClassUID: getActiveViewport().sopClassUIDs[0],
SOPInstanceUID: currentDisplaySet.SOPInstanceUID,
segmentNumber,
frameIndex: closest,
frame,
});
}
};
const getColorLUTTable = () => {
const { state } = cornerstoneTools.getModule('segmentation');
const { colorLUTIndex } = getActiveLabelMaps3D();
return state.colorLutTables[colorLUTIndex];
};
const getEnabledElement = () => {
const enabledElements = cornerstone.getEnabledElements();
return enabledElements[activeIndex].element;
};
const onSegmentVisibilityChangeHandler = (isVisible, segmentNumber) => {
/** Get all labelmaps with this segmentNumber (overlapping segments) */
const { labelmaps3D } = getBrushStackState();
const possibleLabelMaps3D = labelmaps3D.filter(({ labelmaps2D }) => {
return labelmaps2D.some(({ segmentsOnLabelmap }) =>
segmentsOnLabelmap.includes(segmentNumber)
);
});
let segmentsHidden = [];
possibleLabelMaps3D.forEach(labelmap3D => {
labelmap3D.segmentsHidden[segmentNumber] = !isVisible;
segmentsHidden = [
...new Set([...segmentsHidden, ...labelmap3D.segmentsHidden]),
];
});
setState(state => ({ ...state, segmentsHidden }));
refreshSegmentations();
refreshViewports();
if (isVTK()) {
onSegmentVisibilityChange(segmentNumber, isVisible);
}
};
const getSegmentList = () => {
/*
* Newly created segments have no `meta`
* So we instead build a list of all segment indexes in use
* Then find any associated metadata
*/
const uniqueSegmentIndexes = getActiveLabelMaps2D()
.reduce((acc, labelmap2D) => {
if (labelmap2D) {
const segmentIndexes = labelmap2D.segmentsOnLabelmap;
for (let i = 0; i < segmentIndexes.length; i++) {
if (!acc.includes(segmentIndexes[i]) && segmentIndexes[i] !== 0) {
acc.push(segmentIndexes[i]);
}
}
}
return acc;
}, [])
.sort((a, b) => a - b);
const labelmap3D = getActiveLabelMaps3D();
const colorLutTable = getColorLUTTable();
const hasLabelmapMeta = labelmap3D.metadata && labelmap3D.metadata.data;
const segmentList = [];
const segmentNumbers = [];
for (let i = 0; i < uniqueSegmentIndexes.length; i++) {
const segmentIndex = uniqueSegmentIndexes[i];
const color = colorLutTable[segmentIndex];
let segmentLabel = '(unlabeled)';
let segmentNumber = segmentIndex;
/* Meta */
if (hasLabelmapMeta) {
const segmentMeta = labelmap3D.metadata.data[segmentIndex];
if (segmentMeta) {
segmentNumber = segmentMeta.SegmentNumber;
segmentLabel = segmentMeta.SegmentLabel;
}
}
const sameSegment = state.selectedSegment === segmentNumber;
segmentNumbers.push(segmentNumber);
segmentList.push(
<SegmentItem
key={segmentNumber}
itemClass={`segment-item ${sameSegment && 'selected'}`}
onClick={setCurrentSelectedSegment}
label={segmentLabel}
index={segmentNumber}
color={color}
visible={!labelmap3D.segmentsHidden[segmentIndex]}
onVisibilityChange={onSegmentVisibilityChangeHandler}
/>
);
}
return {
items: segmentList,
numbers: segmentNumbers,
segmentsHidden: labelmap3D.segmentsHidden,
};
/*
* Let's iterate over segmentIndexes ^ above
* If meta has a match, use it to show info
* If now, add "no-meta" class
* Show default name
*/
}; };
const updateBrushSize = evt => { const updateBrushSize = evt => {
const updatedRadius = Number(evt.target.value); const updatedRadius = Number(evt.target.value);
if (updatedRadius !== brushRadius) { if (updatedRadius !== brushRadius) {
updateState('brushRadius', updatedRadius); setState(state => ({ ...state, brushRadius: updatedRadius }));
const module = cornerstoneTools.getModule('segmentation'); const module = cornerstoneTools.getModule('segmentation');
module.setters.radius(updatedRadius); module.setters.radius(updatedRadius);
} }
}; };
const decrementSegment = event => { const decrementSegment = event => {
const activeSegmentIndex = getActiveSegmentIndex();
event.preventDefault(); event.preventDefault();
if (labelmap3D.activeSegmentIndex > 1) { if (activeSegmentIndex > 1) {
labelmap3D.activeSegmentIndex--; activeSegmentIndex--;
} }
updateState('selectedSegment', labelmap3D.activeSegmentIndex); setState(state => ({ ...state, selectedSegment: activeSegmentIndex }));
updateActiveSegmentColor(); updateActiveSegmentColor();
}; };
const incrementSegment = event => { const incrementSegment = event => {
const activeSegmentIndex = getActiveSegmentIndex();
event.preventDefault(); event.preventDefault();
labelmap3D.activeSegmentIndex++; activeSegmentIndex++;
updateState('selectedSegment', labelmap3D.activeSegmentIndex); setState(state => ({ ...state, selectedSegment: activeSegmentIndex }));
updateActiveSegmentColor(); updateActiveSegmentColor();
}; };
const updateActiveSegmentColor = () => { const updateActiveSegmentColor = () => {
const color = getActiveSegmentColor(); const color = getActiveSegmentColor();
updateState('brushColor', color); setState(state => ({ ...state, brushColor: color }));
};
const getBrushStackState = () => {
const module = cornerstoneTools.getModule('segmentation');
const firstImageId = getFirstImageId();
const brushStackState = module.state.series[firstImageId];
return brushStackState;
}; };
const getActiveSegmentColor = () => { const getActiveSegmentColor = () => {
if (!state.brushStackState) { const brushStackState = getBrushStackState();
if (!brushStackState) {
return 'rgba(255, 255, 255, 1)'; return 'rgba(255, 255, 255, 1)';
} }
const module = cornerstoneTools.getModule('segmentation'); const colorLutTable = getColorLUTTable();
const colorLutTable = module.state.colorLutTables[labelmap3D.colorLUTIndex];
const color = colorLutTable[labelmap3D.activeSegmentIndex]; const color = colorLutTable[labelmap3D.activeSegmentIndex];
return `rgba(${color.join(',')})`; return `rgba(${color.join(',')})`;
}; };
@@ -517,65 +560,103 @@ const SegmentationPanel = ({
refreshViewports(); refreshViewports();
}; };
const onVisibilityChangeHandler = isVisible => {
let segmentsHidden = [];
state.segmentNumbers.forEach(segmentNumber => {
if (isVTK()) {
onSegmentVisibilityChange(segmentNumber, isVisible);
}
/** Get all labelmaps with this segmentNumber (overlapping segments) */
const { labelmaps3D } = getBrushStackState();
const possibleLabelMaps3D = labelmaps3D.filter(({ labelmaps2D }) => {
return labelmaps2D.some(({ segmentsOnLabelmap }) =>
segmentsOnLabelmap.includes(segmentNumber)
);
});
possibleLabelMaps3D.forEach(labelmap3D => {
labelmap3D.segmentsHidden[segmentNumber] = !isVisible;
segmentsHidden = [
...new Set([...segmentsHidden, ...labelmap3D.segmentsHidden]),
];
});
});
setState(state => ({ ...state, segmentsHidden }));
refreshSegmentations();
refreshViewports();
};
const disabledConfigurationFields = [ const disabledConfigurationFields = [
'outlineAlpha', 'outlineAlpha',
'shouldRenderInactiveLabelmaps', 'shouldRenderInactiveLabelmaps',
]; ];
if (state.showSegmentationSettings) {
const selectedSegmentationOption = state.labelMapList.find(
i => i.value === state.selectedSegmentation
);
if (state.showSettings) {
return ( return (
<SegmentationSettings <SegmentationSettings
disabledFields={isVTK() ? disabledConfigurationFields : []} disabledFields={isVTK() ? disabledConfigurationFields : []}
configuration={configuration} configuration={configuration}
onBack={() => updateState('showSegmentationSettings', false)} onBack={() => setState(state => ({ ...state, showSettings: false }))}
onChange={updateConfiguration} onChange={updateConfiguration}
/> />
); );
} else { } else {
return ( return (
<div <div
className={`dcmseg-segmentation-panel ${state.isDisabled && className={classNames('dcmseg-segmentation-panel', {
'disabled'}`} disabled: state.isDisabled,
})}
> >
<Icon
className="cog-icon"
name="cog"
width="25px"
height="25px"
onClick={() => updateState('showSegmentationSettings', true)}
/>
{false && ( {false && (
<form className="selector-form"> <form className="selector-form">
<BrushColorSelector <BrushColorSelector
defaultColor={brushColor} defaultColor={state.brushColor}
index={state.selectedSegment} index={state.selectedSegment}
onNext={incrementSegment} onNext={incrementSegment}
onPrev={decrementSegment} onPrev={decrementSegment}
/> />
<BrushRadius <BrushRadius
value={brushRadius} value={state.brushRadius}
onChange={updateBrushSize} onChange={updateBrushSize}
min={configuration.minRadius} min={configuration.minRadius}
max={configuration.maxRadius} max={configuration.maxRadius}
/> />
</form> </form>
)} )}
<Icon
className="cog-icon"
name="cog"
width="25px"
height="25px"
onClick={() => setState(state => ({ ...state, showSettings: true }))}
/>
<h3>Segmentations</h3> <h3>Segmentations</h3>
<div className="segmentations"> <div className="segmentations">
<SegmentationSelect <SegmentationSelect
value={state.labelmapList.find( value={selectedSegmentationOption}
i => i.value === state.selectedSegmentation
)}
formatOptionLabel={SegmentationItem} formatOptionLabel={SegmentationItem}
options={state.labelmapList} options={state.labelMapList}
/> />
</div> </div>
<ScrollableArea> <SegmentsSection
<TableList count={state.segmentList.length}
customHeader={<SegmentsHeader count={state.segmentList.length} />} isVisible={
> state.segmentsHidden.filter(isHidden => isHidden === true).length <
{state.segmentList} state.segmentNumbers.length
</TableList> }
</ScrollableArea> onVisibilityChange={onVisibilityChangeHandler}
>
<ScrollableArea>
<TableList headless>{state.segmentList}</TableList>
</ScrollableArea>
</SegmentsSection>
</div> </div>
); );
} }
@@ -610,7 +691,7 @@ SegmentationPanel.propTypes = {
SegmentationPanel.defaultProps = {}; SegmentationPanel.defaultProps = {};
/** /**
* Returns SEG Displaysets that reference the target series, sorted by dateTime * Returns SEG DisplaySets that reference the target series, sorted by dateTime
* *
* @param {string} StudyInstanceUID * @param {string} StudyInstanceUID
* @param {string} SeriesInstanceUID * @param {string} SeriesInstanceUID
@@ -634,37 +715,48 @@ const _getReferencedSegDisplaysets = (StudyInstanceUID, SeriesInstanceUID) => {
return referencedDisplaysets; return referencedDisplaysets;
}; };
/** const SegmentsSection = ({
* count,
* @param {*} firstImageId children,
* @param {*} activeSegmentIndex isVisible: defaultVisibility,
* @returns onVisibilityChange,
*/ }) => {
const _setActiveSegment = (firstImageId, segmentIndex, activeSegmentIndex) => { const [isVisible, setIsVisible] = useState(defaultVisibility);
if (segmentIndex === activeSegmentIndex) {
log.info(`${activeSegmentIndex} is already the active segment`);
return;
}
const { state } = cornerstoneTools.getModule('segmentation'); const onVisibilityChangeHandler = () => {
const brushStackState = state.series[firstImageId]; const newVisibility = !isVisible;
setIsVisible(newVisibility);
onVisibilityChange(newVisibility);
};
const labelmap3D = useEffect(() => {
brushStackState.labelmaps3D[brushStackState.activeLabelmapIndex]; setIsVisible(defaultVisibility);
labelmap3D.activeSegmentIndex = segmentIndex; }, [defaultVisibility]);
refreshViewports();
return segmentIndex;
};
const SegmentsHeader = ({ count }) => {
return ( return (
<React.Fragment> <div className="SegmentsSection">
<div className="tableListHeaderTitle">Segments</div> <div className="header">
<div className="numberOfItems">{count}</div> <div>Segments</div>
</React.Fragment> <div className="icons">
<Icon
className={`eye-icon ${isVisible && 'expanded'}`}
name={isVisible ? 'eye' : 'eye-closed'}
width="20px"
height="20px"
onClick={onVisibilityChangeHandler}
/>
<div className="count">{count}</div>
</div>
</div>
{children}
</div>
); );
}; };
const noop = () => {};
SegmentsSection.defaultProps = {
onVisibilityChange: noop,
};
export default SegmentationPanel; export default SegmentationPanel;
@@ -1,6 +1,11 @@
import { MODULE_TYPES, utils } from '@ohif/core'; import { MODULE_TYPES, utils } from '@ohif/core';
import loadSegmentation from './loadSegmentation'; import loadSegmentation from './loadSegmentation';
import getSourceDisplaySet from './getSourceDisplaySet'; import getSourceDisplaySet from './getSourceDisplaySet';
import OHIF from '@ohif/core';
import dcmjs from 'dcmjs';
const { DicomLoaderService } = OHIF.utils;
const { DicomMessage, DicomMetaDictionary } = dcmjs.data;
// TODO: Should probably use dcmjs for this // TODO: Should probably use dcmjs for this
const SOP_CLASS_UIDS = { const SOP_CLASS_UIDS = {
@@ -46,11 +51,11 @@ export default function getSopClassHandlerModule({ servicesManager }) {
StudyInstanceUID, StudyInstanceUID,
FrameOfReferenceUID, FrameOfReferenceUID,
authorizationHeaders, authorizationHeaders,
metadata,
isDerived: true, isDerived: true,
referencedDisplaySetUID: null, // Assigned when loaded. referencedDisplaySetUID: null, // Assigned when loaded.
labelmapIndex: null, // Assigned when loaded. labelmapIndex: null, // Assigned when loaded.
isLoaded: false, isLoaded: false,
hasOverlapping: false,
SeriesDate, SeriesDate,
SeriesTime, SeriesTime,
SeriesNumber, SeriesNumber,
@@ -62,11 +67,110 @@ export default function getSopClassHandlerModule({ servicesManager }) {
return getSourceDisplaySet(studies, segDisplaySet); return getSourceDisplaySet(studies, segDisplaySet);
}; };
segDisplaySet.load = function(referencedDisplaySet, studies) { segDisplaySet.load = async function(referencedDisplaySet, studies) {
return loadSegmentation(segDisplaySet, referencedDisplaySet, studies); segDisplaySet.isLoaded = true;
const { StudyInstanceUID } = referencedDisplaySet;
const segArrayBuffer = await DicomLoaderService.findDicomDataPromise(
segDisplaySet,
studies
);
const dicomData = DicomMessage.readFile(segArrayBuffer);
const dataset = DicomMetaDictionary.naturalizeDataset(dicomData.dict);
dataset._meta = DicomMetaDictionary.namifyDataset(dicomData.meta);
const imageIds = _getImageIdsForDisplaySet(
studies,
StudyInstanceUID,
referencedDisplaySet.SeriesInstanceUID
);
return new Promise(async (resolve, reject) => {
let results;
try {
results = _parseSeg(segArrayBuffer, imageIds);
} catch (error) {
segDisplaySet.isLoaded = false;
segDisplaySet.loadError = true;
reject(error);
}
const {
labelmapBufferArray,
segMetadata,
segmentsOnFrame,
segmentsOnFrameArray,
} = results;
let labelmapIndex;
if (labelmapBufferArray.length > 1) {
let labelmapIndexes = [];
for (let i = 0; i < labelmapBufferArray.length; ++i) {
labelmapIndexes.push(
await loadSegmentation(
imageIds,
segDisplaySet,
labelmapBufferArray[i],
segMetadata,
segmentsOnFrame,
segmentsOnFrameArray[i]
)
);
}
/**
* Since overlapping segmentations have virtual labelmaps,
* originLabelMapIndex is used in the panel to select the correct dropdown value.
*/
segDisplaySet.hasOverlapping = true;
segDisplaySet.originLabelMapIndex = labelmapIndexes[0];
labelmapIndex = labelmapIndexes[0];
console.warn('Overlapping segmentations!');
} else {
labelmapIndex = await loadSegmentation(
imageIds,
segDisplaySet,
labelmapBufferArray[0],
segMetadata,
segmentsOnFrame,
[]
);
}
resolve(labelmapIndex);
});
}; };
return segDisplaySet; return segDisplaySet;
}, },
}; };
} }
function _parseSeg(arrayBuffer, imageIds) {
return dcmjs.adapters.Cornerstone.Segmentation.generateToolState(
imageIds,
arrayBuffer,
cornerstone.metaData
);
}
function _getImageIdsForDisplaySet(
studies,
StudyInstanceUID,
SeriesInstanceUID
) {
const study = studies.find(
study => study.StudyInstanceUID === StudyInstanceUID
);
const displaySets = study.displaySets.filter(displaySet => {
return displaySet.SeriesInstanceUID === SeriesInstanceUID;
});
if (displaySets.length > 1) {
console.warn(
'More than one display set with the same SeriesInstanceUID. This is not supported yet...'
);
// TODO -> We could make check the instance list and see if any match?
// Do we split the segmentation into two cornerstoneTools segmentations if there are images in both series?
// ^ Will that even happen?
}
const referencedDisplaySet = displaySets[0];
return referencedDisplaySet.images.map(image => image.getImageId());
}
@@ -1,92 +1,68 @@
import OHIF from '@ohif/core';
import dcmjs from 'dcmjs'; import dcmjs from 'dcmjs';
import cornerstone from 'cornerstone-core'; import cornerstone from 'cornerstone-core';
import cornerstoneTools from 'cornerstone-tools'; import cornerstoneTools from 'cornerstone-tools';
const { DicomLoaderService } = OHIF.utils;
export default async function loadSegmentation( export default async function loadSegmentation(
imageIds,
segDisplaySet, segDisplaySet,
referencedDisplaySet, labelmapBuffer,
studies segMetadata,
segmentsOnFrame,
labelmapSegments
) { ) {
const { StudyInstanceUID } = referencedDisplaySet; const { setters } = cornerstoneTools.getModule('segmentation');
// Set here is loading is asynchronous. // TODO: Could define a color LUT based on colors in the SEG.
// If this function throws its set back to false. const labelmapIndex = _getNextLabelmapIndex(imageIds[0]);
segDisplaySet.isLoaded = true; const colorLUTIndex = _makeColorLUTAndGetIndex(segMetadata);
const segArrayBuffer = await DicomLoaderService.findDicomDataPromise( setters.labelmap3DByFirstImageId(
segDisplaySet, imageIds[0],
studies labelmapBuffer,
labelmapIndex,
segMetadata,
imageIds.length,
segmentsOnFrame,
colorLUTIndex
); );
const dicomData = dcmjs.data.DicomMessage.readFile(segArrayBuffer); if (!segDisplaySet.labelmapSegments) {
const dataset = dcmjs.data.DicomMetaDictionary.naturalizeDataset( segDisplaySet.labelmapSegments = {};
dicomData.dict }
);
dataset._meta = dcmjs.data.DicomMetaDictionary.namifyDataset(dicomData.meta); /**
* Cache each labelmap segments.
* This data is used to determine the active label map when a given segment is activated/clicked.
*/
segDisplaySet.labelmapSegments[labelmapIndex] = labelmapSegments.length
? Array.from(
new Set(labelmapSegments.filter(a => !!a).reduce((a, b) => a.concat(b)))
)
: [];
segDisplaySet.labelmapIndex = labelmapIndex;
const imageIds = _getImageIdsForDisplaySet( /*
studies, * TODO: Improve the way we notify parts of the app that depends on segs to be loaded.
StudyInstanceUID, *
referencedDisplaySet.SeriesInstanceUID * Currently we are using a non-ideal implementation through a custom event to notify the segmentation panel
); * or other components that could rely on loaded segmentations that
* the segments were loaded so that e.g. when the user opens the panel
* before the segments are fully loaded, the panel can subscribe to this custom event
* and update itself with the new segments.
*
* This limitation is due to the fact that the cs segmentation module is an object (which will be
* updated after the segments are loaded) that React its not aware of its changes
* because the module object its not passed in to the panel component as prop but accessed externally.
*
* Improving this event approach to something reactive that can be tracked inside the react lifecycle,
* allows us to easily watch the module or the segmentations loading process in any other component
* without subscribing to external events.
*/
console.log('Segmentation loaded.');
const event = new CustomEvent('extensiondicomsegmentationsegloaded');
document.dispatchEvent(event);
return new Promise((resolve, reject) => { return labelmapIndex;
let results;
try {
results = _parseSeg(segArrayBuffer, imageIds);
} catch (error) {
segDisplaySet.isLoaded = false;
segDisplaySet.loadError = true;
reject(error);
}
const { labelmapBuffer, segMetadata, segmentsOnFrame } = results;
const { setters } = cornerstoneTools.getModule('segmentation');
// TODO: Could define a color LUT based on colors in the SEG.
const labelmapIndex = _getNextLabelmapIndex(imageIds[0]);
const colorLUTIndex = _makeColorLUTAndGetIndex(segMetadata);
setters.labelmap3DByFirstImageId(
imageIds[0],
labelmapBuffer,
labelmapIndex,
segMetadata,
imageIds.length,
segmentsOnFrame,
colorLUTIndex
);
segDisplaySet.labelmapIndex = labelmapIndex;
/*
* TODO: Improve the way we notify parts of the app that depends on segs to be loaded.
*
* Currently we are using a non-ideal implementation through a custom event to notify the segmentation panel
* or other components that could rely on loaded segmentations that
* the segments were loaded so that e.g. when the user opens the panel
* before the segments are fully loaded, the panel can subscribe to this custom event
* and update itself with the new segments.
*
* This limitation is due to the fact that the cs segmentation module is an object (which will be
* updated after the segments are loaded) that React its not aware of its changes
* because the module object its not passed in to the panel component as prop but accessed externally.
*
* Improving this event approach to something reactive that can be tracked inside the react lifecycle,
* allows us to easily watch the module or the segmentations loading process in any other component
* without subscribing to external events.
*/
console.log('Segmentation loaded.');
const event = new CustomEvent('extensiondicomsegmentationsegloaded');
document.dispatchEvent(event);
resolve(labelmapIndex);
});
} }
function _getNextLabelmapIndex(firstImageId) { function _getNextLabelmapIndex(firstImageId) {
@@ -172,38 +148,3 @@ function _getNextColorLUTIndex() {
return colorLUTIndex; return colorLUTIndex;
} }
function _parseSeg(arrayBuffer, imageIds) {
return dcmjs.adapters.Cornerstone.Segmentation.generateToolState(
imageIds,
arrayBuffer,
cornerstone.metaData
);
}
function _getImageIdsForDisplaySet(
studies,
StudyInstanceUID,
SeriesInstanceUID
) {
const study = studies.find(
study => study.StudyInstanceUID === StudyInstanceUID
);
const displaySets = study.displaySets.filter(displaySet => {
return displaySet.SeriesInstanceUID === SeriesInstanceUID;
});
if (displaySets.length > 1) {
console.warn(
'More than one display set with the same SeriesInstanceUID. This is not supported yet...'
);
// TODO -> We could make check the instance list and see if any match?
// Do we split the segmentation into two cornerstoneTools segmentations if there are images in both series?
// ^ Will that even happen?
}
const referencedDisplaySet = displaySets[0];
return referencedDisplaySet.images.map(image => image.getImageId());
}
@@ -37,9 +37,14 @@ export default async function setActiveLabelmap(
? brushStackState.activeLabelmapIndex ? brushStackState.activeLabelmapIndex
: undefined; : undefined;
if (displaySet.labelmapIndex === activeLabelmapIndex) { let labelmapIndex =
displaySet.hasOverlapping === true
? displaySet.originLabelMapIndex
: displaySet.labelmapIndex;
if (labelmapIndex === activeLabelmapIndex) {
log.warn(`${activeLabelmapIndex} is already the active labelmap`); log.warn(`${activeLabelmapIndex} is already the active labelmap`);
return displaySet.labelmapIndex; return labelmapIndex;
} }
if (!displaySet.isLoaded) { if (!displaySet.isLoaded) {
@@ -55,13 +60,18 @@ export default async function setActiveLabelmap(
await loadPromise; await loadPromise;
} }
labelmapIndex =
displaySet.hasOverlapping === true
? displaySet.originLabelMapIndex
: displaySet.labelmapIndex;
// This might have just been created, so need to use the non-cached value. // This might have just been created, so need to use the non-cached value.
state = cornerstoneTools.getModule('segmentation').state; state = cornerstoneTools.getModule('segmentation').state;
brushStackState = state.series[firstImageId]; brushStackState = state.series[firstImageId];
brushStackState.activeLabelmapIndex = displaySet.labelmapIndex; brushStackState.activeLabelmapIndex = labelmapIndex;
refreshViewports(); refreshViewports();
callback(); callback();
return displaySet.labelmapIndex; return labelmapIndex;
} }
+22
View File
@@ -3,6 +3,28 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [0.2.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-tag-browser@0.1.1...@ohif/extension-dicom-tag-browser@0.2.0) (2020-12-10)
### Features
* visualize overlapping segments in cornerstone ([#2185](https://github.com/OHIF/Viewers/issues/2185)) ([29fceac](https://github.com/OHIF/Viewers/commit/29fceacee97d51f1952a0f6b574c66596d32c201))
## [0.1.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-tag-browser@0.1.0...@ohif/extension-dicom-tag-browser@0.1.1) (2020-12-03)
### Bug Fixes
* fallback to original dataset VR for XS ([f57725a](https://github.com/OHIF/Viewers/commit/f57725ac8bfd59ea46ab334a1823882afadefba1))
# [0.1.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-tag-browser@0.0.5...@ohif/extension-dicom-tag-browser@0.1.0) (2020-12-03) # [0.1.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-tag-browser@0.0.5...@ohif/extension-dicom-tag-browser@0.1.0) (2020-12-03)
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@ohif/extension-dicom-tag-browser", "name": "@ohif/extension-dicom-tag-browser",
"version": "0.1.0", "version": "0.2.0",
"description": "OHIF extension for checking DICOM headers.", "description": "OHIF extension for checking DICOM headers.",
"author": "OHIF", "author": "OHIF",
"license": "MIT", "license": "MIT",
@@ -28,7 +28,7 @@
}, },
"peerDependencies": { "peerDependencies": {
"@ohif/core": "^2.6.0", "@ohif/core": "^2.6.0",
"dcmjs": "0.16.5", "dcmjs": "0.17.0",
"react": "^16.8.6" "react": "^16.8.6"
}, },
"dependencies": { "dependencies": {
@@ -1,5 +1,5 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { classes } from '@ohif/core'; import { classes, cornerstone as OHIFCornerstone } from '@ohif/core';
import dcmjs from 'dcmjs'; import dcmjs from 'dcmjs';
import DicomBrowserSelect from './DicomBrowserSelect'; import DicomBrowserSelect from './DicomBrowserSelect';
import moment from 'moment'; import moment from 'moment';
@@ -10,6 +10,8 @@ const { ImageSet } = classes;
const { DicomMetaDictionary } = dcmjs.data; const { DicomMetaDictionary } = dcmjs.data;
const { nameMap } = DicomMetaDictionary; const { nameMap } = DicomMetaDictionary;
const { metadataProvider } = OHIFCornerstone;
const DicomTagBrowser = ({ displaySets, displaySetInstanceUID }) => { const DicomTagBrowser = ({ displaySets, displaySetInstanceUID }) => {
const [ const [
activeDisplaySetInstanceUID, activeDisplaySetInstanceUID,
@@ -17,6 +19,7 @@ const DicomTagBrowser = ({ displaySets, displaySetInstanceUID }) => {
] = useState(displaySetInstanceUID); ] = useState(displaySetInstanceUID);
const [activeInstance, setActiveInstance] = useState(0); const [activeInstance, setActiveInstance] = useState(0);
const [tags, setTags] = useState([]); const [tags, setTags] = useState([]);
const [meta, setMeta] = useState('');
const [instanceList, setInstanceList] = useState([]); const [instanceList, setInstanceList] = useState([]);
const [displaySetList, setDisplaySetList] = useState([]); const [displaySetList, setDisplaySetList] = useState([]);
const [isImageStack, setIsImageStack] = useState(false); const [isImageStack, setIsImageStack] = useState(false);
@@ -83,6 +86,7 @@ const DicomTagBrowser = ({ displaySets, displaySetInstanceUID }) => {
} }
setTags(getSortedTags(metadata)); setTags(getSortedTags(metadata));
setMeta(metadata);
setInstanceList(instanceList); setInstanceList(instanceList);
setDisplaySetList(newDisplaySetList); setDisplaySetList(newDisplaySetList);
setIsImageStack(isImageStack); setIsImageStack(isImageStack);
@@ -114,41 +118,43 @@ const DicomTagBrowser = ({ displaySets, displaySetInstanceUID }) => {
options={displaySetList} options={displaySetList}
/> />
{instanceSelectList} {instanceSelectList}
<DicomTagTable tags={tags}></DicomTagTable> <DicomTagTable tags={tags} meta={meta}></DicomTagTable>
</div> </div>
); );
}; };
function DicomTagTable({ tags }) { function DicomTagTable({ tags, meta }) {
const rows = getFormattedRowsFromTags(tags); const rows = getFormattedRowsFromTags(tags, meta);
return ( return (
<div> <div>
<table className="dicom-tag-browser-table"> <table className="dicom-tag-browser-table">
<tr> <tbody>
<th className="dicom-tag-browser-table-left">Tag</th> <tr>
<th className="dicom-tag-browser-table-left">Value Representation</th> <th className="dicom-tag-browser-table-left">Tag</th>
<th className="dicom-tag-browser-table-left">Keyword</th> <th className="dicom-tag-browser-table-left">Value Representation</th>
<th className="dicom-tag-browser-table-left">Value</th> <th className="dicom-tag-browser-table-left">Keyword</th>
</tr> <th className="dicom-tag-browser-table-left">Value</th>
{rows.map(row => { </tr>
const className = row.className ? row.className : null; {rows.map((row, index) => {
const className = row.className ? row.className : null;
return ( return (
<tr className={className}> <tr className={className} key={`DICOMTagRow-${index}`}>
<td>{row[0]}</td> <td>{row[0]}</td>
<td className="dicom-tag-browser-table-center">{row[1]}</td> <td className="dicom-tag-browser-table-center">{row[1]}</td>
<td>{row[2]}</td> <td>{row[2]}</td>
<td>{row[3]}</td> <td>{row[3]}</td>
</tr> </tr>
); );
})} })}
</tbody>
</table> </table>
</div> </div>
); );
} }
function getFormattedRowsFromTags(tags) { function getFormattedRowsFromTags(tags, meta) {
const rows = []; const rows = [];
tags.forEach(tagInfo => { tags.forEach(tagInfo => {
@@ -175,6 +181,17 @@ function getFormattedRowsFromTags(tags) {
rows.push(...formatedRowsFromTags); rows.push(...formatedRowsFromTags);
}); });
} else { } else {
if (tagInfo.vr === 'xs') {
try {
const dataset = metadataProvider.getStudyDataset(meta.StudyInstanceUID);
const tag = dcmjs.data.Tag.fromPString(tagInfo.tag).toCleanString();
const originalTagInfo = dataset[tag];
tagInfo.vr = originalTagInfo.vr;
} catch (error) {
console.error(`Failed to parse value representation for tag '${tagInfo.keyword}'`);
}
}
rows.push([ rows.push([
`${tagInfo.tagIndent}${tagInfo.tag}`, `${tagInfo.tagIndent}${tagInfo.tag}`,
tagInfo.vr, tagInfo.vr,
+57
View File
@@ -3,6 +3,63 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.10.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.10.1...@ohif/extension-vtk@1.10.2) (2021-01-21)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.10.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.10.0...@ohif/extension-vtk@1.10.1) (2020-12-10)
### Bug Fixes
* panel issues and mpr button (IDC2122-IDC2117) ([32022f5](https://github.com/OHIF/Viewers/commit/32022f51f2d24f53f4c98188980db038a6dfe76c))
# [1.10.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.9.4...@ohif/extension-vtk@1.10.0) (2020-12-10)
### Features
* visualize overlapping segments in cornerstone ([#2185](https://github.com/OHIF/Viewers/issues/2185)) ([29fceac](https://github.com/OHIF/Viewers/commit/29fceacee97d51f1952a0f6b574c66596d32c201))
## [1.9.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.9.3...@ohif/extension-vtk@1.9.4) (2020-12-03)
### Bug Fixes
* reset VOI on mpr reset ([#2115](https://github.com/OHIF/Viewers/issues/2115)) ([0baf18d](https://github.com/OHIF/Viewers/commit/0baf18dcb2985378016236b735329346a851e14f))
## [1.9.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.9.2...@ohif/extension-vtk@1.9.3) (2020-12-03)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.9.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.9.1...@ohif/extension-vtk@1.9.2) (2020-12-03)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.9.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.9.0...@ohif/extension-vtk@1.9.1) (2020-12-03) ## [1.9.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.9.0...@ohif/extension-vtk@1.9.1) (2020-12-03)
**Note:** Version bump only for package @ohif/extension-vtk **Note:** Version bump only for package @ohif/extension-vtk
+4 -4
View File
@@ -1,6 +1,6 @@
{ {
"name": "@ohif/extension-vtk", "name": "@ohif/extension-vtk",
"version": "1.9.1", "version": "1.10.2",
"description": "OHIF extension for VTK.js", "description": "OHIF extension for VTK.js",
"author": "OHIF", "author": "OHIF",
"license": "MIT", "license": "MIT",
@@ -35,7 +35,7 @@
"cornerstone-core": "^2.2.8", "cornerstone-core": "^2.2.8",
"cornerstone-tools": "^4.20.1", "cornerstone-tools": "^4.20.1",
"cornerstone-wado-image-loader": "^3.1.0", "cornerstone-wado-image-loader": "^3.1.0",
"dcmjs": "0.16.5", "dcmjs": "0.17.0",
"dicom-parser": "^1.8.3", "dicom-parser": "^1.8.3",
"i18next": "^17.0.3", "i18next": "^17.0.3",
"i18next-browser-languagedetector": "^3.0.1", "i18next-browser-languagedetector": "^3.0.1",
@@ -53,8 +53,8 @@
"react-vtkjs-viewport": "^0.14.2" "react-vtkjs-viewport": "^0.14.2"
}, },
"devDependencies": { "devDependencies": {
"@ohif/core": "^2.11.0", "@ohif/core": "^2.12.1",
"@ohif/ui": "^1.7.0", "@ohif/ui": "^1.8.1",
"cornerstone-tools": "^4.20.1", "cornerstone-tools": "^4.20.1",
"cornerstone-wado-image-loader": "^3.1.0", "cornerstone-wado-image-loader": "^3.1.0",
"dicom-parser": "^1.8.3", "dicom-parser": "^1.8.3",
+81 -40
View File
@@ -138,6 +138,8 @@ class OHIFVTKViewport extends Component {
SOPInstanceUID, SOPInstanceUID,
frameIndex frameIndex
) => { ) => {
const { UINotificationService } = this.props.servicesManager.services;
const stack = OHIFVTKViewport.getCornerstoneStack( const stack = OHIFVTKViewport.getCornerstoneStack(
studies, studies,
StudyInstanceUID, StudyInstanceUID,
@@ -159,6 +161,18 @@ class OHIFVTKViewport extends Component {
const { activeLabelmapIndex } = brushStackState; const { activeLabelmapIndex } = brushStackState;
const labelmap3D = brushStackState.labelmaps3D[activeLabelmapIndex]; const labelmap3D = brushStackState.labelmaps3D[activeLabelmapIndex];
if (
brushStackState.labelmaps3D.length > 1 &&
this.props.viewportIndex === 0
) {
UINotificationService.show({
title: 'Overlapping Segmentation Found',
message:
'Overlapping segmentations cannot be displayed when in MPR mode',
type: 'info',
});
}
this.segmentsDefaultProperties = labelmap3D.segmentsHidden.map( this.segmentsDefaultProperties = labelmap3D.segmentsHidden.map(
isHidden => { isHidden => {
return { visible: !isHidden }; return { visible: !isHidden };
@@ -296,52 +310,79 @@ class OHIFVTKViewport extends Component {
seriesDescription: displaySet.seriesDescription, seriesDescription: displaySet.seriesDescription,
}; };
const { try {
imageDataObject, const {
labelmapDataObject, imageDataObject,
labelmapColorLUT, labelmapDataObject,
} = this.getViewportData( labelmapColorLUT,
studies, } = this.getViewportData(
StudyInstanceUID, studies,
displaySetInstanceUID, StudyInstanceUID,
SOPInstanceUID, displaySetInstanceUID,
frameIndex SOPInstanceUID,
); frameIndex
);
this.imageDataObject = imageDataObject; this.imageDataObject = imageDataObject;
/* TODO: Not currently used until we have drawing tools in vtkjs. /* TODO: Not currently used until we have drawing tools in vtkjs.
if (!labelmap) { if (!labelmap) {
labelmap = createLabelMapImageData(data); labelmap = createLabelMapImageData(data);
} */ } */
const volumeActor = this.getOrCreateVolume( const volumeActor = this.getOrCreateVolume(
imageDataObject, imageDataObject,
displaySetInstanceUID displaySetInstanceUID
); );
this.setState( this.setState(
{ {
percentComplete: 0, percentComplete: 0,
dataDetails, dataDetails,
}, },
() => { () => {
this.loadProgressively(imageDataObject); this.loadProgressively(imageDataObject);
// TODO: There must be a better way to do this. // TODO: There must be a better way to do this.
// We do this so that if all the data is available the react-vtkjs-viewport // We do this so that if all the data is available the react-vtkjs-viewport
// Will render _something_ before the volumes are set and the volume // Will render _something_ before the volumes are set and the volume
// Construction that happens in react-vtkjs-viewport locks up the CPU. // Construction that happens in react-vtkjs-viewport locks up the CPU.
setTimeout(() => { setTimeout(() => {
this.setState({ this.setState({
volumes: [volumeActor], volumes: [volumeActor],
paintFilterLabelMapImageData: labelmapDataObject, paintFilterLabelMapImageData: labelmapDataObject,
paintFilterBackgroundImageData: imageDataObject.vtkImageData, paintFilterBackgroundImageData: imageDataObject.vtkImageData,
labelmapColorLUT, labelmapColorLUT,
}); });
}, 200); }, 200);
}
);
} catch (error) {
const errorTitle = 'Failed to load 2D MPR';
console.error(errorTitle, error);
const { UINotificationService } = this.props.servicesManager.services;
if (this.props.viewportIndex === 0) {
const message = error.message.includes('buffer')
? 'Dataset is too big to display in MPR'
: error.message;
console.error(errorTitle, error);
UINotificationService.show({
title: errorTitle,
message,
type: 'error',
autoClose: false,
action: {
label: 'Exit 2D MPR',
onClick: ({ close }) => {
// context: 'ACTIVE_VIEWPORT::VTK',
close();
this.props.commandsManager.runCommand('setCornerstoneLayout');
},
},
});
} }
); this.setState({ isLoaded: true });
}
} }
componentDidMount() { componentDidMount() {
+8 -4
View File
@@ -17,6 +17,7 @@ const { BlendMode } = Constants;
const commandsModule = ({ commandsManager, UINotificationService }) => { const commandsModule = ({ commandsManager, UINotificationService }) => {
// TODO: Put this somewhere else // TODO: Put this somewhere else
let apis = {}; let apis = {};
let defaultVOI;
async function _getActiveViewportVTKApi(viewports) { async function _getActiveViewportVTKApi(viewports) {
const { const {
@@ -119,9 +120,11 @@ const commandsModule = ({ commandsManager, UINotificationService }) => {
return apis[index]; return apis[index];
}, },
resetMPRView() { resetMPRView() {
apis.forEach(api => { // Reset orientation
api.resetOrientation(); apis.forEach(api => api.resetOrientation());
});
// Reset VOI
if (defaultVOI) setVOI(defaultVOI);
// Reset the crosshairs // Reset the crosshairs
apis[0].svgWidgets.rotatableCrosshairsWidget.resetCrosshairs(apis, 0); apis[0].svgWidgets.rotatableCrosshairsWidget.resetCrosshairs(apis, 0);
@@ -172,7 +175,7 @@ const commandsModule = ({ commandsManager, UINotificationService }) => {
segmentNumber, segmentNumber,
frameIndex, frameIndex,
frame, frame,
done = () => {}, done = () => { },
}) => { }) => {
let api = apis[viewports.activeViewportIndex]; let api = apis[viewports.activeViewportIndex];
@@ -387,6 +390,7 @@ const commandsModule = ({ commandsManager, UINotificationService }) => {
// Get current VOI if cornerstone viewport. // Get current VOI if cornerstone viewport.
const cornerstoneVOI = getVOIFromCornerstoneViewport(); const cornerstoneVOI = getVOIFromCornerstoneViewport();
defaultVOI = cornerstoneVOI;
const viewportProps = [ const viewportProps = [
{ {
+8 -2
View File
@@ -9,7 +9,9 @@ import { version } from '../package.json';
// import loadLocales from './loadLocales'; // import loadLocales from './loadLocales';
const OHIFVTKViewport = asyncComponent(() => const OHIFVTKViewport = asyncComponent(() =>
retryImport(() => import(/* webpackChunkName: "OHIFVTKViewport" */ './OHIFVTKViewport.js')) retryImport(() =>
import(/* webpackChunkName: "OHIFVTKViewport" */ './OHIFVTKViewport.js')
)
); );
const vtkExtension = { const vtkExtension = {
@@ -21,7 +23,11 @@ const vtkExtension = {
getViewportModule({ commandsManager, servicesManager }) { getViewportModule({ commandsManager, servicesManager }) {
const ExtendedVTKViewport = props => ( const ExtendedVTKViewport = props => (
<OHIFVTKViewport {...props} servicesManager={servicesManager} /> <OHIFVTKViewport
{...props}
servicesManager={servicesManager}
commandsManager={commandsManager}
/>
); );
return withCommandsManager(ExtendedVTKViewport, commandsManager); return withCommandsManager(ExtendedVTKViewport, commandsManager);
}, },
@@ -31,7 +31,63 @@ const _isDisplaySetReconstructable = (viewportSpecificData = {}, activeViewportI
if (!displaySet) { if (!displaySet) {
return false; return false;
}; }
// 2D MPR is not currently available for 4D datasets.
// Assuming that slices at different time have the same position, here we just check if
// there are multiple slices for the same ImagePositionPatient and disable MPR.
// A better heuristic would be checking 4D tags, e.g. the presence of multiple TemporalPositionIdentifier values.
// However, some studies (e.g. https://github.com/OHIF/Viewers/issues/2113) do not have such tags.
for (let ii = 0; ii < displaySet.numImageFrames; ++ii) {
const image = displaySet.images[ii];
if (!image) continue;
const imageIdControl = image.getImageId()
const instanceMetadataControl = cornerstone.metaData.get('instance', imageIdControl)
if (!instanceMetadataControl ||
instanceMetadataControl === undefined ||
!instanceMetadataControl.ImagePositionPatient ||
instanceMetadataControl.ImagePositionPatient === undefined) {
// if ImagePositionPatient is missing, skip the 4D datasets check.
// do not return false, because it could be a 3D dataset.
continue;
}
let xImagePositionPatientControl = instanceMetadataControl.ImagePositionPatient[0];
let yImagePositionPatientControl = instanceMetadataControl.ImagePositionPatient[1];
let zImagePositionPatientControl = instanceMetadataControl.ImagePositionPatient[2];
for (let jj = ii + 1; jj < displaySet.numImageFrames; ++jj) {
const image = displaySet.images[jj];
if (!image) continue;
const imageId = image.getImageId()
const instanceMetadata = cornerstone.metaData.get('instance', imageId)
if (!instanceMetadata ||
instanceMetadata === undefined ||
!instanceMetadata.ImagePositionPatient ||
instanceMetadata.ImagePositionPatient === undefined) {
// if ImagePositionPatient is missing, skip the 4D datasets check.
// do not return false, because it could be a 3D dataset.
continue;
}
let xImagePositionPatient = instanceMetadata.ImagePositionPatient[0];
let yImagePositionPatient = instanceMetadata.ImagePositionPatient[1];
let zImagePositionPatient = instanceMetadata.ImagePositionPatient[2];
if (xImagePositionPatientControl === xImagePositionPatient &&
yImagePositionPatientControl === yImagePositionPatient &&
zImagePositionPatientControl === zImagePositionPatient) {
return false;
}
}
}
return displaySet.isReconstructable; return displaySet.isReconstructable;
}; };
+30
View File
@@ -3,6 +3,36 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [2.12.1](https://github.com/OHIF/Viewers/compare/@ohif/core@2.12.0...@ohif/core@2.12.1) (2021-01-21)
**Note:** Version bump only for package @ohif/core
# [2.12.0](https://github.com/OHIF/Viewers/compare/@ohif/core@2.11.1...@ohif/core@2.12.0) (2020-12-10)
### Features
* visualize overlapping segments in cornerstone ([#2185](https://github.com/OHIF/Viewers/issues/2185)) ([29fceac](https://github.com/OHIF/Viewers/commit/29fceacee97d51f1952a0f6b574c66596d32c201))
## [2.11.1](https://github.com/OHIF/Viewers/compare/@ohif/core@2.11.0...@ohif/core@2.11.1) (2020-12-03)
### Bug Fixes
* fallback to original dataset VR for XS ([f57725a](https://github.com/OHIF/Viewers/commit/f57725ac8bfd59ea46ab334a1823882afadefba1))
# [2.11.0](https://github.com/OHIF/Viewers/compare/@ohif/core@2.10.7...@ohif/core@2.11.0) (2020-12-03) # [2.11.0](https://github.com/OHIF/Viewers/compare/@ohif/core@2.10.7...@ohif/core@2.11.0) (2020-12-03)
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@ohif/core", "name": "@ohif/core",
"version": "2.11.0", "version": "2.12.1",
"description": "Generic business logic for web-based medical imaging applications", "description": "Generic business logic for web-based medical imaging applications",
"author": "OHIF Core Team", "author": "OHIF Core Team",
"license": "MIT", "license": "MIT",
@@ -39,7 +39,7 @@
"dependencies": { "dependencies": {
"@babel/runtime": "^7.5.5", "@babel/runtime": "^7.5.5",
"ajv": "^6.10.0", "ajv": "^6.10.0",
"dcmjs": "0.16.5", "dcmjs": "0.17.0",
"dicomweb-client": "^0.6.0", "dicomweb-client": "^0.6.0",
"immer": "6.0.2", "immer": "6.0.2",
"isomorphic-base64": "^1.0.2", "isomorphic-base64": "^1.0.2",
@@ -20,6 +20,7 @@ class MetadataProvider {
writable: false, writable: false,
value: new Map(), value: new Map(),
}); });
this.datasets = {};
} }
async addInstance(dicomJSONDatasetOrP10ArrayBuffer, options = {}) { async addInstance(dicomJSONDatasetOrP10ArrayBuffer, options = {}) {
@@ -52,6 +53,7 @@ class MetadataProvider {
SOPInstanceUID, SOPInstanceUID,
} = naturalizedDataset; } = naturalizedDataset;
this._getAndCacheStudyDataset(StudyInstanceUID, dicomJSONDataset);
const study = this._getAndCacheStudy(StudyInstanceUID); const study = this._getAndCacheStudy(StudyInstanceUID);
const series = this._getAndCacheSeriesFromStudy(study, SeriesInstanceUID); const series = this._getAndCacheSeriesFromStudy(study, SeriesInstanceUID);
const instance = this._getAndCacheInstanceFromStudy(series, SOPInstanceUID); const instance = this._getAndCacheInstanceFromStudy(series, SOPInstanceUID);
@@ -71,6 +73,16 @@ class MetadataProvider {
this.imageIdToUIDs.set(imageId, uids); this.imageIdToUIDs.set(imageId, uids);
} }
_getAndCacheStudyDataset(StudyInstanceUID, dataset) {
if (!this.datasets[StudyInstanceUID]) {
this.datasets[StudyInstanceUID] = dataset;
}
}
getStudyDataset(StudyInstanceUID) {
return this.datasets[StudyInstanceUID];
}
_getAndCacheStudy(StudyInstanceUID) { _getAndCacheStudy(StudyInstanceUID) {
const studies = this.studies; const studies = this.studies;
@@ -83,6 +95,7 @@ class MetadataProvider {
return study; return study;
} }
_getAndCacheSeriesFromStudy(study, SeriesInstanceUID) { _getAndCacheSeriesFromStudy(study, SeriesInstanceUID) {
let series = study.series.get(SeriesInstanceUID); let series = study.series.get(SeriesInstanceUID);
@@ -8,6 +8,7 @@
* @property {string} [position="bottomRight"] -"topLeft" | "topCenter | "topRight" | "bottomLeft" | "bottomCenter" | "bottomRight" * @property {string} [position="bottomRight"] -"topLeft" | "topCenter | "topRight" | "bottomLeft" | "bottomCenter" | "bottomRight"
* @property {string} [type="info"] - "info" | "error" | "warning" | "success" * @property {string} [type="info"] - "info" | "error" | "warning" | "success"
* @property {boolean} [autoClose=true] * @property {boolean} [autoClose=true]
* @property {object} [action=null]
*/ */
const name = 'UINotificationService'; const name = 'UINotificationService';
@@ -34,7 +35,7 @@ const serviceImplementation = {
* Create and show a new UI notification; returns the * Create and show a new UI notification; returns the
* ID of the created notification. * ID of the created notification.
* *
* @param {Notification} notification { title, message, duration, position, type, autoClose} * @param {Notification} notification { title, message, duration, position, type, autoClose, action}
* @returns {number} id * @returns {number} id
*/ */
function _show({ function _show({
@@ -44,6 +45,7 @@ function _show({
position = 'bottomRight', position = 'bottomRight',
type = 'info', type = 'info',
autoClose = true, autoClose = true,
action = null,
}) { }) {
return serviceImplementation._show({ return serviceImplementation._show({
title, title,
@@ -52,6 +54,7 @@ function _show({
position, position,
type, type,
autoClose, autoClose,
action,
}); });
} }
+30
View File
@@ -3,6 +3,36 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.8.1](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.8.0...@ohif/ui@1.8.1) (2021-01-21)
**Note:** Version bump only for package @ohif/ui
# [1.8.0](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.7.1...@ohif/ui@1.8.0) (2020-12-10)
### Features
* visualize overlapping segments in cornerstone ([#2185](https://github.com/OHIF/Viewers/issues/2185)) ([29fceac](https://github.com/OHIF/Viewers/commit/29fceacee97d51f1952a0f6b574c66596d32c201))
## [1.7.1](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.7.0...@ohif/ui@1.7.1) (2020-12-03)
### Bug Fixes
* fallback to original dataset VR for XS ([f57725a](https://github.com/OHIF/Viewers/commit/f57725ac8bfd59ea46ab334a1823882afadefba1))
# [1.7.0](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.6.0...@ohif/ui@1.7.0) (2020-12-03) # [1.7.0](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.6.0...@ohif/ui@1.7.0) (2020-12-03)
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@ohif/ui", "name": "@ohif/ui",
"version": "1.7.0", "version": "1.8.1",
"description": "A set of React components for Medical Imaging Viewers", "description": "A set of React components for Medical Imaging Viewers",
"author": "OHIF Contributors", "author": "OHIF Contributors",
"license": "MIT", "license": "MIT",
@@ -15,8 +15,8 @@ const ErrorFallback = ({ error, componentStack, resetErrorBoundary }) => {
const OHIFErrorBoundary = ({ const OHIFErrorBoundary = ({
context = 'OHIF', context = 'OHIF',
onReset = () => {}, onReset = () => { },
onError = () => {}, onError = () => { },
fallbackComponent, fallbackComponent,
children, children,
}) => { }) => {
@@ -45,7 +45,7 @@ OHIFErrorBoundary.propTypes = {
onReset: PropTypes.func, onReset: PropTypes.func,
onError: PropTypes.func, onError: PropTypes.func,
children: PropTypes.node.isRequired, children: PropTypes.node.isRequired,
fallbackComponent: PropTypes.element, fallbackComponent: PropTypes.oneOfType([PropTypes.node, PropTypes.func, PropTypes.element]),
}; };
export default OHIFErrorBoundary; export default OHIFErrorBoundary;
@@ -156,19 +156,50 @@
transition: all 300ms ease; transition: all 300ms ease;
} }
.sb-success { .sb-item .sb-action,
.sb-error .sb-action,
.sb-warning .sb-action,
.sb-info .sb-action,
.sb-success .sb-action {
margin-top: 10px;
display: inline-block;
padding: 6px 12px;
margin-bottom: 0;
font-size: 14px;
font-weight: 400;
line-height: 1.42857143;
text-align: center;
white-space: nowrap;
vertical-align: middle;
-ms-touch-action: manipulation;
touch-action: manipulation;
cursor: pointer;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
background-image: none;
border: none;
color: white;
}
.sb-success,
.sb-success .sb-action {
background-color: var(--snackbar-success); background-color: var(--snackbar-success);
} }
.sb-error { .sb-error,
.sb-error .sb-action {
background-color: var(--snackbar-error); background-color: var(--snackbar-error);
} }
.sb-warning { .sb-warning,
.sb-warning .sb-action {
background-color: var(--snackbar-warning); background-color: var(--snackbar-warning);
} }
.sb-info { .sb-info,
.sb-info .sb-action {
background-color: var(--snackbar-info); background-color: var(--snackbar-info);
} }
@@ -1,10 +1,14 @@
import React, { useState, useEffect } from 'react'; import React, { useEffect } from 'react';
const SnackbarItem = ({ options, onClose }) => { const SnackbarItem = ({ options, onClose }) => {
const handleClose = () => { const handleClose = () => {
onClose(options.id); onClose(options.id);
}; };
const handleClick = () => {
options.action.onClick({ ...options, close: handleClose });
};
useEffect(() => { useEffect(() => {
if (options.autoClose) { if (options.autoClose) {
setTimeout(() => { setTimeout(() => {
@@ -24,6 +28,11 @@ const SnackbarItem = ({ options, onClose }) => {
</span> </span>
{options.title && <div className="sb-title">{options.title}</div>} {options.title && <div className="sb-title">{options.title}</div>}
{options.message && <div className="sb-message">{options.message}</div>} {options.message && <div className="sb-message">{options.message}</div>}
{options.action && (
<button className="sb-action" onClick={handleClick}>
{options.action.label}
</button>
)}
</div> </div>
); );
}; };
@@ -22,6 +22,7 @@ const SnackbarProvider = ({ children, service }) => {
autoClose: true, autoClose: true,
position: 'bottomRight', position: 'bottomRight',
type: SnackbarTypes.INFO, type: SnackbarTypes.INFO,
action: null,
}; };
const [count, setCount] = useState(1); const [count, setCount] = useState(1);
@@ -37,6 +38,10 @@ const SnackbarProvider = ({ children, service }) => {
return null; return null;
} }
if (options.type === 'error') {
console.error(options.error);
}
const newItem = { const newItem = {
...DEFAULT_OPTIONS, ...DEFAULT_OPTIONS,
...options, ...options,
+102
View File
@@ -3,6 +3,108 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [4.8.6](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.8.5...@ohif/viewer@4.8.6) (2021-01-26)
**Note:** Version bump only for package @ohif/viewer
## [4.8.5](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.8.4...@ohif/viewer@4.8.5) (2021-01-21)
**Note:** Version bump only for package @ohif/viewer
## [4.8.4](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.8.3...@ohif/viewer@4.8.4) (2021-01-21)
**Note:** Version bump only for package @ohif/viewer
## [4.8.3](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.8.2...@ohif/viewer@4.8.3) (2021-01-21)
**Note:** Version bump only for package @ohif/viewer
## [4.8.2](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.8.1...@ohif/viewer@4.8.2) (2021-01-21)
**Note:** Version bump only for package @ohif/viewer
## [4.8.1](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.8.0...@ohif/viewer@4.8.1) (2020-12-10)
**Note:** Version bump only for package @ohif/viewer
# [4.8.0](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.7.2...@ohif/viewer@4.8.0) (2020-12-10)
### Features
* visualize overlapping segments in cornerstone ([#2185](https://github.com/OHIF/Viewers/issues/2185)) ([29fceac](https://github.com/OHIF/Viewers/commit/29fceacee97d51f1952a0f6b574c66596d32c201))
## [4.7.2](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.7.1...@ohif/viewer@4.7.2) (2020-12-03)
**Note:** Version bump only for package @ohif/viewer
## [4.7.1](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.7.0...@ohif/viewer@4.7.1) (2020-12-03)
**Note:** Version bump only for package @ohif/viewer
# [4.7.0](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.6.3...@ohif/viewer@4.7.0) (2020-12-03)
### Features
* Update approach to better handle rtdose instead of disabling panel ([f3cecf1](https://github.com/OHIF/Viewers/commit/f3cecf129b16f8d24d239aa44fdff7f46e6ba683))
## [4.6.3](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.6.2...@ohif/viewer@4.6.3) (2020-12-03)
**Note:** Version bump only for package @ohif/viewer
## [4.6.2](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.6.1...@ohif/viewer@4.6.2) (2020-12-03)
**Note:** Version bump only for package @ohif/viewer
## [4.6.1](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.6.0...@ohif/viewer@4.6.1) (2020-12-03) ## [4.6.1](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.6.0...@ohif/viewer@4.6.1) (2020-12-03)
**Note:** Version bump only for package @ohif/viewer **Note:** Version bump only for package @ohif/viewer
+10 -10
View File
@@ -1,6 +1,6 @@
{ {
"name": "@ohif/viewer", "name": "@ohif/viewer",
"version": "4.6.1", "version": "4.8.6",
"description": "OHIF Viewer", "description": "OHIF Viewer",
"author": "OHIF Contributors", "author": "OHIF Contributors",
"license": "MIT", "license": "MIT",
@@ -49,18 +49,18 @@
}, },
"dependencies": { "dependencies": {
"@babel/runtime": "^7.5.5", "@babel/runtime": "^7.5.5",
"@ohif/core": "^2.11.0", "@ohif/core": "^2.12.1",
"@ohif/extension-cornerstone": "^2.9.6", "@ohif/extension-cornerstone": "^2.10.0",
"@ohif/extension-debugging": "^0.1.6", "@ohif/extension-debugging": "^0.1.6",
"@ohif/extension-dicom-html": "^1.2.9", "@ohif/extension-dicom-html": "^1.3.0",
"@ohif/extension-dicom-microscopy": "^0.51.3", "@ohif/extension-dicom-microscopy": "^0.51.4",
"@ohif/extension-dicom-pdf": "^1.0.6", "@ohif/extension-dicom-pdf": "^1.0.6",
"@ohif/extension-dicom-rt": "^0.4.9", "@ohif/extension-dicom-rt": "^0.6.3",
"@ohif/extension-dicom-segmentation": "^0.4.7", "@ohif/extension-dicom-segmentation": "^0.5.1",
"@ohif/extension-lesion-tracker": "^0.2.1", "@ohif/extension-lesion-tracker": "^0.2.1",
"@ohif/extension-vtk": "^1.9.1", "@ohif/extension-vtk": "^1.10.2",
"@ohif/i18n": "^0.53.0", "@ohif/i18n": "^0.53.0",
"@ohif/ui": "^1.7.0", "@ohif/ui": "^1.8.1",
"@tanem/react-nprogress": "^1.1.25", "@tanem/react-nprogress": "^1.1.25",
"classnames": "^2.2.6", "classnames": "^2.2.6",
"core-js": "^3.2.1", "core-js": "^3.2.1",
@@ -68,7 +68,7 @@
"cornerstone-math": "^0.1.8", "cornerstone-math": "^0.1.8",
"cornerstone-tools": "^4.20.1", "cornerstone-tools": "^4.20.1",
"cornerstone-wado-image-loader": "^3.1.0", "cornerstone-wado-image-loader": "^3.1.0",
"dcmjs": "0.16.5", "dcmjs": "0.17.0",
"dicom-parser": "^1.8.3", "dicom-parser": "^1.8.3",
"dicomweb-client": "^0.4.4", "dicomweb-client": "^0.4.4",
"hammerjs": "^2.0.8", "hammerjs": "^2.0.8",
+26 -31
View File
@@ -1,41 +1,36 @@
import './SidePanel.css'; import './SidePanel.css';
import React, { Component } from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import classNames from 'classnames'; import classNames from 'classnames';
class SidePanel extends Component { const SidePanel = ({ from, isOpen, children, width }) => {
static propTypes = { const fromSideClass = from === 'right' ? 'from-right' : 'from-left';
from: PropTypes.string.isRequired,
isOpen: PropTypes.bool.isRequired,
children: PropTypes.node,
width: PropTypes.string,
};
render() { const styles = width
const fromSideClass = ? {
this.props.from === 'right' ? 'from-right' : 'from-left'; maxWidth: width,
marginRight: isOpen ? '0' : Number.parseInt(width) * -1,
}
: {};
const styles = this.props.width return (
? { <section
maxWidth: this.props.width, style={styles}
marginRight: this.props.isOpen className={classNames('sidepanel', fromSideClass, {
? '0' 'is-open': isOpen,
: Number.parseInt(this.props.width) * -1, })}
} >
: {}; {children}
</section>
);
};
return ( SidePanel.propTypes = {
<section from: PropTypes.string.isRequired,
style={styles} isOpen: PropTypes.bool.isRequired,
className={classNames('sidepanel', fromSideClass, { children: PropTypes.node,
'is-open': this.props.isOpen, width: PropTypes.string,
})} };
>
{this.props.children}
</section>
);
}
}
export default SidePanel; export default SidePanel;
@@ -12,7 +12,7 @@ import EmptyViewport from './EmptyViewport.js';
const { loadAndCacheDerivedDisplaySets } = utils; const { loadAndCacheDerivedDisplaySets } = utils;
const ViewportGrid = function(props) { const ViewportGrid = function (props) {
const { const {
activeViewportIndex, activeViewportIndex,
availablePlugins, availablePlugins,
@@ -48,6 +48,7 @@ const ViewportGrid = function(props) {
title: 'Error loading derived display set:', title: 'Error loading derived display set:',
message: error.message, message: error.message,
type: 'error', type: 'error',
error,
autoClose: false, autoClose: false,
}); });
}); });
@@ -86,7 +86,7 @@ class ToolbarRow extends Component {
// Note: This does not cleanly handle `studies` prop updating with panel open // Note: This does not cleanly handle `studies` prop updating with panel open
const isDisabled = const isDisabled =
typeof menuOption.isDisabled === 'function' && typeof menuOption.isDisabled === 'function' &&
menuOption.isDisabled(this.props.studies); menuOption.isDisabled(this.props.studies, this.props.activeViewport);
if (hasActiveContext && !isDisabled) { if (hasActiveContext && !isDisabled) {
const menuOptionEntry = { const menuOptionEntry = {
@@ -114,25 +114,30 @@ class ToolbarRow extends Component {
prevProps.activeContexts !== this.props.activeContexts; prevProps.activeContexts !== this.props.activeContexts;
const prevStudies = prevProps.studies; const prevStudies = prevProps.studies;
const prevActiveViewport = prevProps.activeViewport;
const activeViewport = this.props.activeViewport;
const studies = this.props.studies; const studies = this.props.studies;
const seriesPerStudyCount = this.seriesPerStudyCount; const seriesPerStudyCount = this.seriesPerStudyCount;
let studiesUpdated = false; let shouldUpdate = false;
if (prevStudies.length !== studies.length) { if (
studiesUpdated = true; prevStudies.length !== studies.length ||
prevActiveViewport !== activeViewport
) {
shouldUpdate = true;
} else { } else {
for (let i = 0; i < studies.length; i++) { for (let i = 0; i < studies.length; i++) {
if (studies[i].series.length !== seriesPerStudyCount[i]) { if (studies[i].series.length !== seriesPerStudyCount[i]) {
seriesPerStudyCount[i] = studies[i].series.length; seriesPerStudyCount[i] = studies[i].series.length;
studiesUpdated = true; shouldUpdate = true;
break; break;
} }
} }
} }
if (studiesUpdated) { if (shouldUpdate) {
this.updateButtonGroups(); this.updateButtonGroups();
} }
@@ -83,6 +83,8 @@ class Viewer extends Component {
disassociate: this.disassociateStudy, disassociate: this.disassociateStudy,
}, },
}); });
this._getActiveViewport = this._getActiveViewport.bind(this);
} }
state = { state = {
@@ -214,6 +216,10 @@ class Viewer extends Component {
} }
} }
_getActiveViewport() {
return this.props.viewports[this.props.activeViewportIndex];
}
render() { render() {
let VisiblePanelLeft, VisiblePanelRight; let VisiblePanelLeft, VisiblePanelRight;
const panelExtensions = extensionManager.modules[MODULE_TYPES.PANEL]; const panelExtensions = extensionManager.modules[MODULE_TYPES.PANEL];
@@ -262,6 +268,9 @@ class Viewer extends Component {
{/* TOOLBAR */} {/* TOOLBAR */}
<ErrorBoundaryDialog context="ToolbarRow"> <ErrorBoundaryDialog context="ToolbarRow">
<ToolbarRow <ToolbarRow
activeViewport={
this.props.viewports[this.props.activeViewportIndex]
}
isLeftSidePanelOpen={this.state.isLeftSidePanelOpen} isLeftSidePanelOpen={this.state.isLeftSidePanelOpen}
isRightSidePanelOpen={this.state.isRightSidePanelOpen} isRightSidePanelOpen={this.state.isRightSidePanelOpen}
selectedLeftSidePanel={ selectedLeftSidePanel={
@@ -341,6 +350,10 @@ class Viewer extends Component {
viewports={this.props.viewports} viewports={this.props.viewports}
studies={this.props.studies} studies={this.props.studies}
activeIndex={this.props.activeViewportIndex} activeIndex={this.props.activeViewportIndex}
activeViewport={
this.props.viewports[this.props.activeViewportIndex]
}
getActiveViewport={this._getActiveViewport}
/> />
)} )}
</SidePanel> </SidePanel>
@@ -336,12 +336,6 @@ async function getStudyList(
} = filters; } = filters;
const sortFieldName = sort.fieldName || 'PatientName'; const sortFieldName = sort.fieldName || 'PatientName';
const sortDirection = sort.direction || 'desc'; const sortDirection = sort.direction || 'desc';
const studyDateFrom =
filters.studyDateFrom ||
moment()
.subtract(25000, 'days')
.toDate();
const studyDateTo = filters.studyDateTo || new Date();
const mappedFilters = { const mappedFilters = {
PatientID: filters.PatientID, PatientID: filters.PatientID,
@@ -350,8 +344,8 @@ async function getStudyList(
StudyDescription: filters.StudyDescription, StudyDescription: filters.StudyDescription,
ModalitiesInStudy: filters.modalities, ModalitiesInStudy: filters.modalities,
// NEVER CHANGE // NEVER CHANGE
studyDateFrom, studyDateFrom: filters.studyDateFrom,
studyDateTo, studyDateTo: filters.studyDateTo,
limit: rowsPerPage, limit: rowsPerPage,
offset: pageNumber * rowsPerPage, offset: pageNumber * rowsPerPage,
fuzzymatching: server.supportsFuzzyMatching === true, fuzzymatching: server.supportsFuzzyMatching === true,
+5 -4
View File
@@ -6619,14 +6619,15 @@ dateformat@^3.0.0:
resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae" resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae"
integrity sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q== integrity sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==
dcmjs@0.16.5: dcmjs@0.17.0:
version "0.16.5" version "0.17.0"
resolved "https://registry.yarnpkg.com/dcmjs/-/dcmjs-0.16.5.tgz#05aa0c8d539a4d5777f996b9b796932d1f848993" resolved "https://registry.yarnpkg.com/dcmjs/-/dcmjs-0.17.0.tgz#dbfd6db3efd2936f9bead9c902b7db396f64e9f5"
integrity sha512-Y92KAx4JSfev79xjYhaukQZjWgyJlaHnXSTdbtkGkJAHERjIakgHcCIPmACuVmtW/nHViTu04zIGAB0pmZ4GaA== integrity sha512-7JnNqNWXqjGyspLRwKM4nn+TU/+w3iMl9boU4eHrr831M4crMWWIiSQDejpOnfDT9+opqyeaYcNkiekr7p6TFg==
dependencies: dependencies:
"@babel/polyfill" "^7.8.3" "@babel/polyfill" "^7.8.3"
"@babel/runtime" "^7.8.4" "@babel/runtime" "^7.8.4"
gl-matrix "^3.1.0" gl-matrix "^3.1.0"
lodash.clonedeep "^4.5.0"
loglevelnext "^3.0.1" loglevelnext "^3.0.1"
ndarray "^1.0.19" ndarray "^1.0.19"