Compare commits
76
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bdecbfeee3 | ||
|
|
5a970056ae | ||
|
|
8581405352 | ||
|
|
df8c4db2ed | ||
|
|
ea62bdaddf | ||
|
|
e983b02677 | ||
|
|
1e4b78d78d | ||
|
|
ee6131f98e | ||
|
|
a56bdd035b | ||
|
|
04901c296e | ||
|
|
59cadad98c | ||
|
|
14d6454eaf | ||
|
|
64f87d2c93 | ||
|
|
cfd66e5ef7 | ||
|
|
6480389778 | ||
|
|
0e736e7a56 | ||
|
|
d246cd9842 | ||
|
|
4fa1eb683d | ||
|
|
ea6842a276 | ||
|
|
d97270f995 | ||
|
|
0ef76f8b66 | ||
|
|
05b5a10af9 | ||
|
|
e1150acaf6 | ||
|
|
4abdbe0584 | ||
|
|
9223a52af9 | ||
|
|
06f58d18bf | ||
|
|
7c5f9c44b6 | ||
|
|
ef341d1518 | ||
|
|
d956963ef0 | ||
|
|
83b7d9e8b4 | ||
|
|
12e73228d1 | ||
|
|
4002401c66 | ||
|
|
18a8e059b6 | ||
|
|
f3582e514e | ||
|
|
c76bfd4a58 | ||
|
|
7f8cf5f5da | ||
|
|
5ef926477c | ||
|
|
87f5b97983 | ||
|
|
4c7b2b4ae0 | ||
|
|
d1e52dd6a6 | ||
|
|
cb147dfc69 | ||
|
|
a5fdeddfb4 | ||
|
|
e0e5f6000f | ||
|
|
cffb7d6f2f | ||
|
|
360616be13 | ||
|
|
d8fe8929b6 | ||
|
|
6b99a21227 | ||
|
|
16631e1fc4 | ||
|
|
587de94cea | ||
|
|
0ebe8a67d2 | ||
|
|
39a988dac7 | ||
|
|
f95928bcdd | ||
|
|
0f736bd294 | ||
|
|
6cbd9839c6 | ||
|
|
649bc5825a | ||
|
|
8038009afd | ||
|
|
f07f21117d | ||
|
|
77c7463c53 | ||
|
|
449e8f25f6 | ||
|
|
437c01511e | ||
|
|
00fddc1550 | ||
|
|
00b5cccae5 | ||
|
|
e545dd5b28 | ||
|
|
efde6ed20e | ||
|
|
32022f51f2 | ||
|
|
d282f72d75 | ||
|
|
29fceacee9 | ||
|
|
a7a5a359dc | ||
|
|
e362e895d4 | ||
|
|
45a07dfa19 | ||
|
|
7323e63608 | ||
|
|
0baf18dcb2 | ||
|
|
cca3d6bff2 | ||
|
|
f3cecf129b | ||
|
|
fe03fc6f55 | ||
|
|
b59187c74d |
No files matched your search
@@ -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 || ''),
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -259,6 +258,22 @@ Cancer Institute, Informatics Technology for Cancer Research (ITCR) program,
|
|||||||
under a
|
under a
|
||||||
[grant to Dr. Gordon Harris at Massachusetts General Hospital (U24 CA199460)](https://projectreporter.nih.gov/project_info_description.cfm?aid=8971104).
|
[grant to Dr. Gordon Harris at Massachusetts General Hospital (U24 CA199460)](https://projectreporter.nih.gov/project_info_description.cfm?aid=8971104).
|
||||||
|
|
||||||
|
## Projects that contributed to OHIF Viewer
|
||||||
|
|
||||||
|
The following is a (partial) list of projects that contributed resources towards
|
||||||
|
development of OHIF Viewer:
|
||||||
|
|
||||||
|
- [NCI Imaging Data Commons (IDC) project](https://imaging.datacommons.cancer.gov/)
|
||||||
|
supported development of new features and bug fixes marked with
|
||||||
|
["IDC:priority"](https://github.com/OHIF/Viewers/issues?q=is%3Aissue+is%3Aopen+label%3AIDC%3Apriority),
|
||||||
|
["IDC:candidate"](https://github.com/OHIF/Viewers/issues?q=is%3Aissue+is%3Aopen+label%3AIDC%3Acandidate)
|
||||||
|
or
|
||||||
|
["IDC:collaboration"](https://github.com/OHIF/Viewers/issues?q=is%3Aissue+is%3Aopen+label%3AIDC%3Acollaboration).
|
||||||
|
NCI Imaging Data Commons is supported by the contract number 19X037Q from
|
||||||
|
Leidos Biomedical Research under Task Order HHSN26100071 from NCI.
|
||||||
|
[IDC Viewer](https://learn.canceridc.dev/portal/visualization) is a customized
|
||||||
|
version of the OHIF Viewer.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
MIT © [OHIF](https://github.com/OHIF)
|
MIT © [OHIF](https://github.com/OHIF)
|
||||||
|
|||||||
@@ -3,6 +3,41 @@
|
|||||||
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.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.10.2...@ohif/extension-cornerstone@2.10.3) (2021-03-03)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [2.10.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.10.1...@ohif/extension-cornerstone@2.10.2) (2021-02-25)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [2.10.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.10.0...@ohif/extension-cornerstone@2.10.1) (2021-02-05)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [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
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@ohif/extension-cornerstone",
|
"name": "@ohif/extension-cornerstone",
|
||||||
"version": "2.9.6",
|
"version": "2.10.3",
|
||||||
"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.18.2",
|
||||||
"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",
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|
||||||
|
# [0.2.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-debugging@0.1.6...@ohif/extension-debugging@0.2.0) (2021-03-04)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **log:** add new log service ([14d6454](https://github.com/OHIF/Viewers/commit/14d6454eafaa2ccb50e133c2945c9558052ea27e))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [0.1.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-debugging@0.1.5...@ohif/extension-debugging@0.1.6) (2020-10-06)
|
## [0.1.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-debugging@0.1.5...@ohif/extension-debugging@0.1.6) (2020-10-06)
|
||||||
|
|
||||||
**Note:** Version bump only for package @ohif/extension-debugging
|
**Note:** Version bump only for package @ohif/extension-debugging
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@ohif/extension-debugging",
|
"name": "@ohif/extension-debugging",
|
||||||
"version": "0.1.6",
|
"version": "0.2.0",
|
||||||
"description": "OHIF extension for debugging.",
|
"description": "OHIF extension for debugging.",
|
||||||
"author": "OHIF",
|
"author": "OHIF",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -12,3 +12,15 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.debug-report-modal-container .errors {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.debug-report-modal-container .errors-container {
|
||||||
|
border: 1px solid var(--active-color);
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 10px;
|
||||||
|
overflow: scroll;
|
||||||
|
max-height: 300px;
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ const DubugReportModal = ({
|
|||||||
extensionManager,
|
extensionManager,
|
||||||
mailTo,
|
mailTo,
|
||||||
debugModalMessage,
|
debugModalMessage,
|
||||||
|
errors = [],
|
||||||
}) => {
|
}) => {
|
||||||
const copyDebugDataToClipboard = () => {
|
const copyDebugDataToClipboard = () => {
|
||||||
const body = getEmailBody();
|
const body = getEmailBody();
|
||||||
@@ -131,6 +132,19 @@ const DubugReportModal = ({
|
|||||||
{getLayout(viewports)}
|
{getLayout(viewports)}
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="errors">
|
||||||
|
<h3>Errors ({errors.length})</h3>
|
||||||
|
<div className="errors-container">
|
||||||
|
{errors.map(error => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<pre>Message: {error.message}</pre>
|
||||||
|
{error.error && <pre>Stack: {error.error.stack}</pre>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import OHIF from '@ohif/core';
|
import OHIF from '@ohif/core';
|
||||||
|
import { ToolbarButton, useLogger } from '@ohif/ui';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
save,
|
save,
|
||||||
upload,
|
upload,
|
||||||
@@ -95,6 +97,7 @@ export function getCommands(context, servicesManager, extensionManager) {
|
|||||||
const { UIModalService } = servicesManager.services;
|
const { UIModalService } = servicesManager.services;
|
||||||
|
|
||||||
const WrappedDebugReportModal = function() {
|
const WrappedDebugReportModal = function() {
|
||||||
|
const { state } = useLogger();
|
||||||
return (
|
return (
|
||||||
<DebugReportModal
|
<DebugReportModal
|
||||||
viewports={viewports}
|
viewports={viewports}
|
||||||
@@ -103,6 +106,7 @@ export function getCommands(context, servicesManager, extensionManager) {
|
|||||||
extensionManager={extensionManager}
|
extensionManager={extensionManager}
|
||||||
mailTo={state.mailTo}
|
mailTo={state.mailTo}
|
||||||
debugModalMessage={state.debugModalMessage}
|
debugModalMessage={state.debugModalMessage}
|
||||||
|
errors={state.errors}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,6 +3,41 @@
|
|||||||
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.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.3.2...@ohif/extension-dicom-html@1.3.3) (2021-03-03)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-dicom-html
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [1.3.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.3.1...@ohif/extension-dicom-html@1.3.2) (2021-02-25)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-dicom-html
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [1.3.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.3.0...@ohif/extension-dicom-html@1.3.1) (2021-02-05)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-dicom-html
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [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
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@ohif/extension-dicom-html",
|
"name": "@ohif/extension-dicom-html",
|
||||||
"version": "1.2.9",
|
"version": "1.3.3",
|
||||||
"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.18.2",
|
||||||
"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"
|
||||||
|
|||||||
@@ -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.52.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-microscopy@0.51.4...@ohif/extension-dicom-microscopy@0.52.0) (2021-03-04)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **log:** add new log service ([14d6454](https://github.com/OHIF/Viewers/commit/14d6454eafaa2ccb50e133c2945c9558052ea27e))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [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,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@ohif/extension-dicom-microscopy",
|
"name": "@ohif/extension-dicom-microscopy",
|
||||||
"version": "0.51.3",
|
"version": "0.52.0",
|
||||||
"description": "OHIF extension for Dicom Microscopy",
|
"description": "OHIF extension for Dicom Microscopy",
|
||||||
"author": "OHIF",
|
"author": "OHIF",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -62,11 +62,30 @@ 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,
|
||||||
|
LoggerService,
|
||||||
|
} = this.props.servicesManager.services;
|
||||||
|
if (UINotificationService) {
|
||||||
|
const message =
|
||||||
|
'Failed to load viewport. Please check that you have hardware acceleration enabled.';
|
||||||
|
LoggerService.error({ error, message });
|
||||||
|
UINotificationService.show({
|
||||||
|
autoClose: false,
|
||||||
|
title: 'Microscopy Viewport',
|
||||||
|
message,
|
||||||
|
type: 'error',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.viewer.render({ container });
|
this.viewer.render({ container });
|
||||||
});
|
});
|
||||||
@@ -92,8 +111,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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -3,6 +3,79 @@
|
|||||||
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.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.6.5...@ohif/extension-dicom-rt@0.6.6) (2021-03-03)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-dicom-rt
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [0.6.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.6.4...@ohif/extension-dicom-rt@0.6.5) (2021-02-25)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-dicom-rt
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [0.6.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.6.3...@ohif/extension-dicom-rt@0.6.4) (2021-02-05)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-dicom-rt
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [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
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@ohif/extension-dicom-rt",
|
"name": "@ohif/extension-dicom-rt",
|
||||||
"version": "0.4.9",
|
"version": "0.6.6",
|
||||||
"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.18.2",
|
||||||
"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
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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,60 @@
|
|||||||
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.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.5.4...@ohif/extension-dicom-segmentation@0.6.0) (2021-03-04)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **log:** add new log service ([14d6454](https://github.com/OHIF/Viewers/commit/14d6454eafaa2ccb50e133c2945c9558052ea27e))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [0.5.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.5.3...@ohif/extension-dicom-segmentation@0.5.4) (2021-03-03)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [0.5.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.5.2...@ohif/extension-dicom-segmentation@0.5.3) (2021-02-25)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [0.5.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.5.1...@ohif/extension-dicom-segmentation@0.5.2) (2021-02-05)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [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
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@ohif/extension-dicom-segmentation",
|
"name": "@ohif/extension-dicom-segmentation",
|
||||||
"version": "0.4.7",
|
"version": "0.6.0",
|
||||||
"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.18.2",
|
||||||
"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>}
|
||||||
|
|||||||
+64
-5
@@ -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;
|
||||||
|
}
|
||||||
+445
-353
@@ -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());
|
||||||
|
}
|
||||||
@@ -25,12 +25,13 @@ export default {
|
|||||||
return toolbarModule;
|
return toolbarModule;
|
||||||
},
|
},
|
||||||
getPanelModule({ commandsManager, api, servicesManager }) {
|
getPanelModule({ commandsManager, api, servicesManager }) {
|
||||||
const { UINotificationService } = servicesManager.services;
|
const { UINotificationService, LoggerService } = servicesManager.services;
|
||||||
|
|
||||||
const ExtendedSegmentationPanel = props => {
|
const ExtendedSegmentationPanel = props => {
|
||||||
const { activeContexts } = api.hooks.useAppContext();
|
const { activeContexts } = api.hooks.useAppContext();
|
||||||
|
|
||||||
const onDisplaySetLoadFailureHandler = error => {
|
const onDisplaySetLoadFailureHandler = error => {
|
||||||
|
LoggerService.error({ error, message: error.message });
|
||||||
UINotificationService.show({
|
UINotificationService.show({
|
||||||
title: 'DICOM Segmentation Loader',
|
title: 'DICOM Segmentation Loader',
|
||||||
message: error.message,
|
message: error.message,
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
@@ -3,6 +3,49 @@
|
|||||||
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.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-tag-browser@0.2.3...@ohif/extension-dicom-tag-browser@0.2.4) (2021-03-03)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-dicom-tag-browser
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [0.2.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-tag-browser@0.2.2...@ohif/extension-dicom-tag-browser@0.2.3) (2021-02-25)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-dicom-tag-browser
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [0.2.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-tag-browser@0.2.1...@ohif/extension-dicom-tag-browser@0.2.2) (2021-02-05)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-dicom-tag-browser
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [0.2.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-tag-browser@0.2.0...@ohif/extension-dicom-tag-browser@0.2.1) (2021-02-05)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-dicom-tag-browser
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [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)
|
## [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)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@ohif/extension-dicom-tag-browser",
|
"name": "@ohif/extension-dicom-tag-browser",
|
||||||
"version": "0.1.1",
|
"version": "0.2.4",
|
||||||
"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.18.2",
|
||||||
"react": "^16.8.6"
|
"react": "^16.8.6"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -3,6 +3,11 @@
|
|||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dicom-tag-browser-table-wrapper {
|
||||||
|
height: 500px;
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
|
||||||
.dicom-tag-browser-table tr {
|
.dicom-tag-browser-table tr {
|
||||||
padding-left: 10px;
|
padding-left: 10px;
|
||||||
padding-right: 10px;
|
padding-right: 10px;
|
||||||
@@ -11,6 +16,20 @@
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dicom-tag-browser-content {
|
||||||
|
overflow: hidden;
|
||||||
|
width: 100%;
|
||||||
|
padding-bottom: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dicom-tag-browser-instance-range .range {
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dicom-tag-browser-instance-range {
|
||||||
|
padding: 20px 0 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
.dicom-tag-browser-table td.dicom-tag-browser-table-center {
|
.dicom-tag-browser-table td.dicom-tag-browser-table-center {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { classes, cornerstone as OHIFCornerstone } from '@ohif/core';
|
import { classes, cornerstone as OHIFCornerstone } from '@ohif/core';
|
||||||
|
import { Range } from '@ohif/ui';
|
||||||
import dcmjs from 'dcmjs';
|
import dcmjs from 'dcmjs';
|
||||||
import DicomBrowserSelect from './DicomBrowserSelect';
|
import DicomBrowserSelect from './DicomBrowserSelect';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
@@ -99,26 +100,35 @@ const DicomTagBrowser = ({ displaySets, displaySetInstanceUID }) => {
|
|||||||
let instanceSelectList = null;
|
let instanceSelectList = null;
|
||||||
|
|
||||||
if (isImageStack) {
|
if (isImageStack) {
|
||||||
const selectedInstanceValue = instanceList[activeInstance];
|
|
||||||
|
|
||||||
instanceSelectList = (
|
instanceSelectList = (
|
||||||
<DicomBrowserSelect
|
<div className="dicom-tag-browser-instance-range">
|
||||||
value={selectedInstanceValue}
|
<Range
|
||||||
formatOptionLabel={DicomBrowserSelectItem}
|
showValue
|
||||||
options={instanceList}
|
step={1}
|
||||||
/>
|
min={1}
|
||||||
|
max={instanceList.length - 1}
|
||||||
|
value={activeInstance}
|
||||||
|
valueRenderer={value => <p>Instance Number: {value}</p>}
|
||||||
|
onChange={({ target }) => {
|
||||||
|
const instanceIndex = parseInt(target.value);
|
||||||
|
setActiveInstance(instanceIndex);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="dicom-tag-browser-content">
|
||||||
<DicomBrowserSelect
|
<DicomBrowserSelect
|
||||||
value={selectedDisplaySetValue}
|
value={selectedDisplaySetValue}
|
||||||
formatOptionLabel={DicomBrowserSelectItem}
|
formatOptionLabel={DicomBrowserSelectItem}
|
||||||
options={displaySetList}
|
options={displaySetList}
|
||||||
/>
|
/>
|
||||||
{instanceSelectList}
|
{instanceSelectList}
|
||||||
<DicomTagTable tags={tags} meta={meta}></DicomTagTable>
|
<div className="dicom-tag-browser-table-wrapper">
|
||||||
|
<DicomTagTable tags={tags} meta={meta}></DicomTagTable>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -127,30 +137,28 @@ function DicomTagTable({ tags, meta }) {
|
|||||||
const rows = getFormattedRowsFromTags(tags, meta);
|
const rows = getFormattedRowsFromTags(tags, meta);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<table className="dicom-tag-browser-table">
|
||||||
<table className="dicom-tag-browser-table">
|
<tbody>
|
||||||
<tbody>
|
<tr>
|
||||||
<tr>
|
<th className="dicom-tag-browser-table-left">Tag</th>
|
||||||
<th className="dicom-tag-browser-table-left">Tag</th>
|
<th className="dicom-tag-browser-table-left">Value Representation</th>
|
||||||
<th className="dicom-tag-browser-table-left">Value Representation</th>
|
<th className="dicom-tag-browser-table-left">Keyword</th>
|
||||||
<th className="dicom-tag-browser-table-left">Keyword</th>
|
<th className="dicom-tag-browser-table-left">Value</th>
|
||||||
<th className="dicom-tag-browser-table-left">Value</th>
|
</tr>
|
||||||
</tr>
|
{rows.map((row, index) => {
|
||||||
{rows.map((row, index) => {
|
const className = row.className ? row.className : null;
|
||||||
const className = row.className ? row.className : null;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<tr className={className} key={`DICOMTagRow-${index}`}>
|
<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>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,12 +191,16 @@ function getFormattedRowsFromTags(tags, meta) {
|
|||||||
} else {
|
} else {
|
||||||
if (tagInfo.vr === 'xs') {
|
if (tagInfo.vr === 'xs') {
|
||||||
try {
|
try {
|
||||||
const dataset = metadataProvider.getStudyDataset(meta.StudyInstanceUID);
|
const dataset = metadataProvider.getStudyDataset(
|
||||||
|
meta.StudyInstanceUID
|
||||||
|
);
|
||||||
const tag = dcmjs.data.Tag.fromPString(tagInfo.tag).toCleanString();
|
const tag = dcmjs.data.Tag.fromPString(tagInfo.tag).toCleanString();
|
||||||
const originalTagInfo = dataset[tag];
|
const originalTagInfo = dataset[tag];
|
||||||
tagInfo.vr = originalTagInfo.vr;
|
tagInfo.vr = originalTagInfo.vr;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Failed to parse value representation for tag '${tagInfo.keyword}'`);
|
console.error(
|
||||||
|
`Failed to parse value representation for tag '${tagInfo.keyword}'`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export default function getCommandsModule(servicesManager) {
|
|||||||
|
|
||||||
const { UIModalService } = servicesManager.services;
|
const { UIModalService } = servicesManager.services;
|
||||||
|
|
||||||
const WrappedDicomTagBrowser = function () {
|
const WrappedDicomTagBrowser = function() {
|
||||||
return (
|
return (
|
||||||
<DicomTagBrowser
|
<DicomTagBrowser
|
||||||
displaySets={displaySets}
|
displaySets={displaySets}
|
||||||
@@ -34,7 +34,7 @@ export default function getCommandsModule(servicesManager) {
|
|||||||
content: WrappedDicomTagBrowser,
|
content: WrappedDicomTagBrowser,
|
||||||
title: `DICOM Tag Browser`,
|
title: `DICOM Tag Browser`,
|
||||||
fullscreen: true,
|
fullscreen: true,
|
||||||
showScrollbar: true
|
noScroll: true,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,6 +3,98 @@
|
|||||||
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.11.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.10.6...@ohif/extension-vtk@1.11.0) (2021-03-04)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **log:** add new log service ([14d6454](https://github.com/OHIF/Viewers/commit/14d6454eafaa2ccb50e133c2945c9558052ea27e))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [1.10.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.10.5...@ohif/extension-vtk@1.10.6) (2021-03-03)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-vtk
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [1.10.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.10.4...@ohif/extension-vtk@1.10.5) (2021-02-25)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-vtk
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [1.10.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.10.3...@ohif/extension-vtk@1.10.4) (2021-02-05)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-vtk
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [1.10.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.10.2...@ohif/extension-vtk@1.10.3) (2021-02-05)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-vtk
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [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)
|
## [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
|
**Note:** Version bump only for package @ohif/extension-vtk
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@ohif/extension-vtk",
|
"name": "@ohif/extension-vtk",
|
||||||
"version": "1.9.2",
|
"version": "1.11.0",
|
||||||
"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.18.2",
|
||||||
"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.1",
|
"@ohif/core": "^2.13.0",
|
||||||
"@ohif/ui": "^1.7.1",
|
"@ohif/ui": "^1.9.0",
|
||||||
"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",
|
||||||
|
|||||||
@@ -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,82 @@ 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,
|
||||||
|
LoggerService,
|
||||||
|
} = 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;
|
||||||
|
LoggerService.error({ error, message });
|
||||||
|
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() {
|
||||||
@@ -387,11 +431,15 @@ class OHIFVTKViewport extends Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onPixelDataInsertedErrorCallback = error => {
|
const onPixelDataInsertedErrorCallback = error => {
|
||||||
const { UINotificationService } = this.props.servicesManager.services;
|
const {
|
||||||
|
UINotificationService,
|
||||||
|
LoggerService,
|
||||||
|
} = this.props.servicesManager.services;
|
||||||
|
|
||||||
if (!this.hasError) {
|
if (!this.hasError) {
|
||||||
if (this.props.viewportIndex === 0) {
|
if (this.props.viewportIndex === 0) {
|
||||||
// Only show the notification from one viewport 1 in MPR2D.
|
// Only show the notification from one viewport 1 in MPR2D.
|
||||||
|
LoggerService.error({ error, message: error.message });
|
||||||
UINotificationService.show({
|
UINotificationService.show({
|
||||||
title: 'MPR Load Error',
|
title: 'MPR Load Error',
|
||||||
message: error.message,
|
message: error.message,
|
||||||
|
|||||||
@@ -14,9 +14,12 @@ import OHIFVTKViewport from './OHIFVTKViewport';
|
|||||||
|
|
||||||
const { BlendMode } = Constants;
|
const { BlendMode } = Constants;
|
||||||
|
|
||||||
const commandsModule = ({ commandsManager, UINotificationService }) => {
|
const commandsModule = ({ commandsManager, servicesManager }) => {
|
||||||
|
const { UINotificationService, LoggerService } = servicesManager.services;
|
||||||
|
|
||||||
// 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 +122,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);
|
||||||
@@ -387,6 +392,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 = [
|
||||||
{
|
{
|
||||||
@@ -469,10 +475,12 @@ const commandsModule = ({ commandsManager, UINotificationService }) => {
|
|||||||
const volumeLength = dimensions[0] * dimensions[1] * dimensions[2];
|
const volumeLength = dimensions[0] * dimensions[1] * dimensions[2];
|
||||||
|
|
||||||
if (volumeLength > maxBufferLengthFloat32) {
|
if (volumeLength > maxBufferLengthFloat32) {
|
||||||
|
const message =
|
||||||
|
'This volume is too large to fit in WebGL 1 textures and will display incorrectly. Please use a different browser to view this data';
|
||||||
|
LoggerService.error({ message });
|
||||||
UINotificationService.show({
|
UINotificationService.show({
|
||||||
title: 'Browser does not support WebGL 2',
|
title: 'Browser does not support WebGL 2',
|
||||||
message:
|
message,
|
||||||
'This volume is too large to fit in WebGL 1 textures and will display incorrectly. Please use a different browser to view this data',
|
|
||||||
type: 'error',
|
type: 'error',
|
||||||
autoClose: false,
|
autoClose: false,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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);
|
||||||
},
|
},
|
||||||
@@ -29,8 +35,7 @@ const vtkExtension = {
|
|||||||
return toolbarModule;
|
return toolbarModule;
|
||||||
},
|
},
|
||||||
getCommandsModule({ commandsManager, servicesManager }) {
|
getCommandsModule({ commandsManager, servicesManager }) {
|
||||||
const { UINotificationService } = servicesManager.services;
|
return commandsModule({ commandsManager, servicesManager });
|
||||||
return commandsModule({ commandsManager, UINotificationService });
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,6 +3,68 @@
|
|||||||
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.13.0](https://github.com/OHIF/Viewers/compare/@ohif/core@2.12.5...@ohif/core@2.13.0) (2021-03-04)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **log:** add new log service ([14d6454](https://github.com/OHIF/Viewers/commit/14d6454eafaa2ccb50e133c2945c9558052ea27e))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [2.12.5](https://github.com/OHIF/Viewers/compare/@ohif/core@2.12.4...@ohif/core@2.12.5) (2021-03-03)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/core
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [2.12.4](https://github.com/OHIF/Viewers/compare/@ohif/core@2.12.3...@ohif/core@2.12.4) (2021-02-25)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/core
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [2.12.3](https://github.com/OHIF/Viewers/compare/@ohif/core@2.12.2...@ohif/core@2.12.3) (2021-02-05)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/core
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [2.12.2](https://github.com/OHIF/Viewers/compare/@ohif/core@2.12.1...@ohif/core@2.12.2) (2021-02-05)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/core
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [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)
|
## [2.11.1](https://github.com/OHIF/Viewers/compare/@ohif/core@2.11.0...@ohif/core@2.11.1) (2020-12-03)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@ohif/core",
|
"name": "@ohif/core",
|
||||||
"version": "2.11.1",
|
"version": "2.13.0",
|
||||||
"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.18.2",
|
||||||
"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",
|
||||||
|
|||||||
@@ -63,10 +63,15 @@ export class HotkeysManager {
|
|||||||
|
|
||||||
definitions.forEach(definition => this.registerHotkeys(definition));
|
definitions.forEach(definition => this.registerHotkeys(definition));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const { UINotificationService } = this._servicesManager.services;
|
const {
|
||||||
|
UINotificationService,
|
||||||
|
LoggerService,
|
||||||
|
} = this._servicesManager.services;
|
||||||
|
const message = 'Erro while setting hotkeys';
|
||||||
|
LoggerService.error({ error, message });
|
||||||
UINotificationService.show({
|
UINotificationService.show({
|
||||||
title: 'Hotkeys Manager',
|
title: 'Hotkeys Manager',
|
||||||
message: 'Erro while setting hotkeys',
|
message,
|
||||||
type: 'error',
|
type: 'error',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import {
|
|||||||
UIModalService,
|
UIModalService,
|
||||||
UIDialogService,
|
UIDialogService,
|
||||||
MeasurementService,
|
MeasurementService,
|
||||||
|
LoggerService,
|
||||||
} from './services';
|
} from './services';
|
||||||
|
|
||||||
const OHIF = {
|
const OHIF = {
|
||||||
@@ -60,6 +61,7 @@ const OHIF = {
|
|||||||
UIModalService,
|
UIModalService,
|
||||||
UIDialogService,
|
UIDialogService,
|
||||||
MeasurementService,
|
MeasurementService,
|
||||||
|
LoggerService,
|
||||||
};
|
};
|
||||||
|
|
||||||
export {
|
export {
|
||||||
@@ -93,6 +95,7 @@ export {
|
|||||||
UIModalService,
|
UIModalService,
|
||||||
UIDialogService,
|
UIDialogService,
|
||||||
MeasurementService,
|
MeasurementService,
|
||||||
|
LoggerService,
|
||||||
};
|
};
|
||||||
|
|
||||||
export { OHIF };
|
export { OHIF };
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ describe('Top level exports', () => {
|
|||||||
'UIModalService',
|
'UIModalService',
|
||||||
'UIDialogService',
|
'UIDialogService',
|
||||||
'MeasurementService',
|
'MeasurementService',
|
||||||
|
'LoggerService',
|
||||||
//
|
//
|
||||||
'utils',
|
'utils',
|
||||||
'hotkeys',
|
'hotkeys',
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
const name = 'LoggerService';
|
||||||
|
|
||||||
|
const publicAPI = {
|
||||||
|
name,
|
||||||
|
info: _info,
|
||||||
|
error: _error,
|
||||||
|
setServiceImplementation,
|
||||||
|
};
|
||||||
|
|
||||||
|
const serviceImplementation = {
|
||||||
|
_info: () => console.warn('info() NOT IMPLEMENTED'),
|
||||||
|
_error: () => console.warn('error() NOT IMPLEMENTED'),
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs an info
|
||||||
|
*
|
||||||
|
* @param {object} props { message, displayOnConsole }
|
||||||
|
*/
|
||||||
|
function _info({ message, displayOnConsole }) {
|
||||||
|
return serviceImplementation._info({
|
||||||
|
message,
|
||||||
|
displayOnConsole,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs an error
|
||||||
|
*
|
||||||
|
* @param {object} props { error, stack, message, displayOnConsole }
|
||||||
|
* @returns void
|
||||||
|
*/
|
||||||
|
function _error({ error, stack, message, displayOnConsole }) {
|
||||||
|
return serviceImplementation._error({
|
||||||
|
error,
|
||||||
|
stack,
|
||||||
|
message,
|
||||||
|
displayOnConsole,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @param {*} {
|
||||||
|
* info: infoImplementation,
|
||||||
|
* error: errorImplementation,
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
function setServiceImplementation({
|
||||||
|
info: infoImplementation,
|
||||||
|
error: errorImplementation,
|
||||||
|
}) {
|
||||||
|
if (infoImplementation) {
|
||||||
|
serviceImplementation._info = infoImplementation;
|
||||||
|
}
|
||||||
|
if (errorImplementation) {
|
||||||
|
serviceImplementation._error = errorImplementation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name,
|
||||||
|
create: ({ configuration = {} }) => {
|
||||||
|
return publicAPI;
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -9,7 +9,8 @@
|
|||||||
* @property {boolean} [closeButton=true] Should the modal body render the close button.
|
* @property {boolean} [closeButton=true] Should the modal body render the close button.
|
||||||
* @property {string} [title=null] Should the modal render the title independently of the body content.
|
* @property {string} [title=null] Should the modal render the title independently of the body content.
|
||||||
* @property {string} [customClassName=null] The custom class to style the modal.
|
* @property {string} [customClassName=null] The custom class to style the modal.
|
||||||
* @property {boolean} [showScrollbar=null] Show or hide scrollbar.
|
* @property {boolean} [showScrollbar=false] Show or hide scrollbar.
|
||||||
|
* @property {boolean} [noScroll=false] Disable or not the scrollbar.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const name = 'UIModalService';
|
const name = 'UIModalService';
|
||||||
@@ -29,7 +30,7 @@ const serviceImplementation = {
|
|||||||
/**
|
/**
|
||||||
* Show a new UI modal;
|
* Show a new UI modal;
|
||||||
*
|
*
|
||||||
* @param {ModalProps} props { content, contentProps, shouldCloseOnEsc, isOpen, onClose, closeButton, title, customClassName, showScrollbar }
|
* @param {ModalProps} props { content, contentProps, shouldCloseOnEsc, isOpen, onClose, closeButton, title, customClassName, showScrollbar, noScroll }
|
||||||
*/
|
*/
|
||||||
function _show({
|
function _show({
|
||||||
content = null,
|
content = null,
|
||||||
@@ -41,7 +42,8 @@ function _show({
|
|||||||
title = null,
|
title = null,
|
||||||
fullscreen = false,
|
fullscreen = false,
|
||||||
customClassName = null,
|
customClassName = null,
|
||||||
showScrollbar = false
|
showScrollbar = false,
|
||||||
|
noScroll = false,
|
||||||
}) {
|
}) {
|
||||||
return serviceImplementation._show({
|
return serviceImplementation._show({
|
||||||
content,
|
content,
|
||||||
@@ -53,7 +55,8 @@ function _show({
|
|||||||
title,
|
title,
|
||||||
fullscreen,
|
fullscreen,
|
||||||
customClassName,
|
customClassName,
|
||||||
showScrollbar
|
showScrollbar,
|
||||||
|
noScroll,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import UINotificationService from './UINotificationService';
|
|||||||
import UIModalService from './UIModalService';
|
import UIModalService from './UIModalService';
|
||||||
import UIDialogService from './UIDialogService';
|
import UIDialogService from './UIDialogService';
|
||||||
import MeasurementService from './MeasurementService';
|
import MeasurementService from './MeasurementService';
|
||||||
|
import LoggerService from './LoggerService';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
UINotificationService,
|
UINotificationService,
|
||||||
@@ -10,4 +11,5 @@ export {
|
|||||||
UIDialogService,
|
UIDialogService,
|
||||||
ServicesManager,
|
ServicesManager,
|
||||||
MeasurementService,
|
MeasurementService,
|
||||||
|
LoggerService,
|
||||||
};
|
};
|
||||||
@@ -3,6 +3,44 @@
|
|||||||
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.9.0](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.8.2...@ohif/ui@1.9.0) (2021-03-04)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **log:** add new log service ([14d6454](https://github.com/OHIF/Viewers/commit/14d6454eafaa2ccb50e133c2945c9558052ea27e))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [1.8.2](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.8.1...@ohif/ui@1.8.2) (2021-02-05)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/ui
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [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)
|
## [1.7.1](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.7.0...@ohif/ui@1.7.1) (2020-12-03)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@ohif/ui",
|
"name": "@ohif/ui",
|
||||||
"version": "1.7.1",
|
"version": "1.9.0",
|
||||||
"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",
|
||||||
|
|||||||
@@ -32,6 +32,10 @@
|
|||||||
max-height: 90vh;
|
max-height: 90vh;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
|
||||||
|
&.noScroll &__content {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
&:not(.visibleScrollbar) &__content
|
&:not(.visibleScrollbar) &__content
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
scrollbar-width: none;
|
scrollbar-width: none;
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,104 @@
|
|||||||
|
import React, { useState, createContext, useContext, useEffect } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
const LoggerContext = createContext(null);
|
||||||
|
const { Provider } = LoggerContext;
|
||||||
|
|
||||||
|
export const useLogger = () => useContext(LoggerContext);
|
||||||
|
|
||||||
|
const LoggerProvider = ({ children, service }) => {
|
||||||
|
const [state, setState] = useState({
|
||||||
|
errors: [],
|
||||||
|
infos: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const onErrorHandler = ({ error: errorObject, message }) => {
|
||||||
|
error({ error: errorObject, message });
|
||||||
|
};
|
||||||
|
window.addEventListener('error', onErrorHandler);
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('error', onErrorHandler);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs an error
|
||||||
|
*
|
||||||
|
* @param {object} props { error, stack, message, displayOnConsole }
|
||||||
|
* @returns void
|
||||||
|
*/
|
||||||
|
const error = ({
|
||||||
|
error = {},
|
||||||
|
stack = '',
|
||||||
|
message = '',
|
||||||
|
displayOnConsole = true,
|
||||||
|
}) => {
|
||||||
|
const errorObject = { error, stack, message, displayOnConsole };
|
||||||
|
setState(state => ({ ...state, errors: [...state.errors, errorObject] }));
|
||||||
|
|
||||||
|
if (displayOnConsole) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs an info
|
||||||
|
*
|
||||||
|
* @param {object} props { message, displayOnConsole }
|
||||||
|
* @returns void
|
||||||
|
*/
|
||||||
|
const info = ({ message = '', displayOnConsole = true }) => {
|
||||||
|
setState(state => ({
|
||||||
|
...state,
|
||||||
|
infos: state.infos.push({ message, displayOnConsole }),
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (displayOnConsole) {
|
||||||
|
console.info(message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the implementation of a log service that can be used by extensions
|
||||||
|
*
|
||||||
|
* @returns void
|
||||||
|
*/
|
||||||
|
useEffect(() => {
|
||||||
|
if (service) {
|
||||||
|
service.setServiceImplementation({ error, info });
|
||||||
|
}
|
||||||
|
}, [error, service, info]);
|
||||||
|
|
||||||
|
return <Provider value={{ info, error, state }}>{children}</Provider>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Higher Order Component to use the log methods through a Class Component
|
||||||
|
*
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const withLogger = Component => {
|
||||||
|
return function WrappedComponent(props) {
|
||||||
|
const { error, info, state } = useLogger();
|
||||||
|
return <Component {...props} logger={{ error, info, state }} />;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
LoggerProvider.defaultProps = {
|
||||||
|
service: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
LoggerProvider.propTypes = {
|
||||||
|
children: PropTypes.oneOfType([
|
||||||
|
PropTypes.arrayOf(PropTypes.node),
|
||||||
|
PropTypes.node,
|
||||||
|
]).isRequired,
|
||||||
|
service: PropTypes.shape({
|
||||||
|
setServiceImplementation: PropTypes.func,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LoggerProvider;
|
||||||
|
|
||||||
|
export const LogConsumer = LoggerContext.Consumer;
|
||||||
@@ -82,14 +82,20 @@ const ModalProvider = ({ children, modal: Modal, service }) => {
|
|||||||
shouldCloseOnEsc,
|
shouldCloseOnEsc,
|
||||||
fullscreen,
|
fullscreen,
|
||||||
closeButton,
|
closeButton,
|
||||||
showScrollbar
|
showScrollbar,
|
||||||
|
noScroll,
|
||||||
} = options;
|
} = options;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Provider value={{ show, hide }}>
|
<Provider value={{ show, hide }}>
|
||||||
{ModalContent && (
|
{ModalContent && (
|
||||||
<Modal
|
<Modal
|
||||||
className={classNames(customClassName, ModalContent.className, { 'visibleScrollbar': showScrollbar })}
|
className={classNames(
|
||||||
|
customClassName,
|
||||||
|
ModalContent.className,
|
||||||
|
{ visibleScrollbar: showScrollbar },
|
||||||
|
{ noScroll }
|
||||||
|
)}
|
||||||
shouldCloseOnEsc={shouldCloseOnEsc}
|
shouldCloseOnEsc={shouldCloseOnEsc}
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
title={title}
|
title={title}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -18,3 +18,8 @@ export {
|
|||||||
withDialog,
|
withDialog,
|
||||||
useDialog,
|
useDialog,
|
||||||
} from './DialogProvider.js';
|
} from './DialogProvider.js';
|
||||||
|
export {
|
||||||
|
default as LoggerProvider,
|
||||||
|
withLogger,
|
||||||
|
useLogger,
|
||||||
|
} from './LoggerProvider.js';
|
||||||
@@ -28,7 +28,13 @@ class Range extends Component {
|
|||||||
className="range"
|
className="range"
|
||||||
/>
|
/>
|
||||||
{this.props.showPercentage && <span>{`${this.state.value}%`}</span>}
|
{this.props.showPercentage && <span>{`${this.state.value}%`}</span>}
|
||||||
{this.props.showValue && <span>{this.state.value}</span>}
|
{this.props.showValue && (
|
||||||
|
<span>
|
||||||
|
{this.props.valueRenderer
|
||||||
|
? this.props.valueRenderer(this.state.value)
|
||||||
|
: this.state.value}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -40,6 +46,7 @@ Range.propTypes = {
|
|||||||
max: PropTypes.number.isRequired,
|
max: PropTypes.number.isRequired,
|
||||||
step: PropTypes.number,
|
step: PropTypes.number,
|
||||||
id: PropTypes.string,
|
id: PropTypes.string,
|
||||||
|
valueRenderer: PropTypes.func,
|
||||||
onChange: PropTypes.func,
|
onChange: PropTypes.func,
|
||||||
showPercentage: PropTypes.bool,
|
showPercentage: PropTypes.bool,
|
||||||
showValue: PropTypes.bool,
|
showValue: PropTypes.bool,
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ import {
|
|||||||
AboutContent,
|
AboutContent,
|
||||||
OHIFModal,
|
OHIFModal,
|
||||||
ErrorBoundary,
|
ErrorBoundary,
|
||||||
ErrorPage
|
ErrorPage,
|
||||||
} from './components';
|
} from './components';
|
||||||
import { useDebounce, useMedia } from './hooks';
|
import { useDebounce, useMedia } from './hooks';
|
||||||
|
|
||||||
@@ -66,6 +66,9 @@ import {
|
|||||||
ModalConsumer,
|
ModalConsumer,
|
||||||
useModal,
|
useModal,
|
||||||
withModal,
|
withModal,
|
||||||
|
LoggerProvider,
|
||||||
|
withLogger,
|
||||||
|
useLogger,
|
||||||
} from './contextProviders';
|
} from './contextProviders';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
@@ -127,11 +130,14 @@ export {
|
|||||||
useDialog,
|
useDialog,
|
||||||
ErrorBoundary,
|
ErrorBoundary,
|
||||||
ErrorPage,
|
ErrorPage,
|
||||||
|
LoggerProvider,
|
||||||
|
withLogger,
|
||||||
|
useLogger,
|
||||||
// Hooks
|
// Hooks
|
||||||
useDebounce,
|
useDebounce,
|
||||||
useMedia,
|
useMedia,
|
||||||
// Utils
|
// Utils
|
||||||
ViewerbaseDragDropContext,
|
ViewerbaseDragDropContext,
|
||||||
asyncComponent,
|
asyncComponent,
|
||||||
retryImport
|
retryImport,
|
||||||
};
|
};
|
||||||
@@ -3,6 +3,162 @@
|
|||||||
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.9.0](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.8.12...@ohif/viewer@4.9.0) (2021-03-04)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **log:** add new log service ([14d6454](https://github.com/OHIF/Viewers/commit/14d6454eafaa2ccb50e133c2945c9558052ea27e))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [4.8.12](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.8.11...@ohif/viewer@4.8.12) (2021-03-03)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [4.8.11](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.8.10...@ohif/viewer@4.8.11) (2021-02-25)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [4.8.10](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.8.9...@ohif/viewer@4.8.10) (2021-02-08)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [4.8.9](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.8.8...@ohif/viewer@4.8.9) (2021-02-05)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [4.8.8](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.8.7...@ohif/viewer@4.8.8) (2021-02-05)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [4.8.7](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.8.6...@ohif/viewer@4.8.7) (2021-01-28)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **config:** Update Google Healthcare API endpoint from v1beta1 to v1 ([#2256](https://github.com/OHIF/Viewers/issues/2256)) ([83b7d9e](https://github.com/OHIF/Viewers/commit/83b7d9e8b4240a928eb43735280000e97f186d90))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [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)
|
## [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
|
**Note:** Version bump only for package @ohif/viewer
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@ohif/viewer",
|
"name": "@ohif/viewer",
|
||||||
"version": "4.6.2",
|
"version": "4.9.0",
|
||||||
"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.1",
|
"@ohif/core": "^2.13.0",
|
||||||
"@ohif/extension-cornerstone": "^2.9.6",
|
"@ohif/extension-cornerstone": "^2.10.3",
|
||||||
"@ohif/extension-debugging": "^0.1.6",
|
"@ohif/extension-debugging": "^0.2.0",
|
||||||
"@ohif/extension-dicom-html": "^1.2.9",
|
"@ohif/extension-dicom-html": "^1.3.3",
|
||||||
"@ohif/extension-dicom-microscopy": "^0.51.3",
|
"@ohif/extension-dicom-microscopy": "^0.52.0",
|
||||||
"@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.6",
|
||||||
"@ohif/extension-dicom-segmentation": "^0.4.7",
|
"@ohif/extension-dicom-segmentation": "^0.6.0",
|
||||||
"@ohif/extension-lesion-tracker": "^0.2.1",
|
"@ohif/extension-lesion-tracker": "^0.2.1",
|
||||||
"@ohif/extension-vtk": "^1.9.2",
|
"@ohif/extension-vtk": "^1.11.0",
|
||||||
"@ohif/i18n": "^0.53.0",
|
"@ohif/i18n": "^0.53.0",
|
||||||
"@ohif/ui": "^1.7.1",
|
"@ohif/ui": "^1.9.0",
|
||||||
"@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.18.2",
|
||||||
"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",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
window.config = {
|
window.config = {
|
||||||
routerBasename: '/',
|
routerBasename: '/',
|
||||||
enableGoogleCloudAdapter: true,
|
enableGoogleCloudAdapter: true,
|
||||||
healthcareApiEndpoint: 'https://healthcare.googleapis.com/v1beta1',
|
healthcareApiEndpoint: 'https://healthcare.googleapis.com/v1',
|
||||||
servers: {
|
servers: {
|
||||||
// This is an array, but we'll only use the first entry for now
|
// This is an array, but we'll only use the first entry for now
|
||||||
dicomWeb: [],
|
dicomWeb: [],
|
||||||
|
|||||||
+30
-19
@@ -13,6 +13,7 @@ import {
|
|||||||
ModalProvider,
|
ModalProvider,
|
||||||
DialogProvider,
|
DialogProvider,
|
||||||
OHIFModal,
|
OHIFModal,
|
||||||
|
LoggerProvider,
|
||||||
ErrorBoundary,
|
ErrorBoundary,
|
||||||
} from '@ohif/ui';
|
} from '@ohif/ui';
|
||||||
|
|
||||||
@@ -24,6 +25,7 @@ import {
|
|||||||
UINotificationService,
|
UINotificationService,
|
||||||
UIModalService,
|
UIModalService,
|
||||||
UIDialogService,
|
UIDialogService,
|
||||||
|
LoggerService,
|
||||||
MeasurementService,
|
MeasurementService,
|
||||||
utils,
|
utils,
|
||||||
redux as reduxOHIF,
|
redux as reduxOHIF,
|
||||||
@@ -141,6 +143,7 @@ class App extends Component {
|
|||||||
UIModalService,
|
UIModalService,
|
||||||
UIDialogService,
|
UIDialogService,
|
||||||
MeasurementService,
|
MeasurementService,
|
||||||
|
LoggerService,
|
||||||
]);
|
]);
|
||||||
_initExtensions(
|
_initExtensions(
|
||||||
[...defaultExtensions, ...extensions],
|
[...defaultExtensions, ...extensions],
|
||||||
@@ -164,6 +167,7 @@ class App extends Component {
|
|||||||
UIDialogService,
|
UIDialogService,
|
||||||
UIModalService,
|
UIModalService,
|
||||||
MeasurementService,
|
MeasurementService,
|
||||||
|
LoggerService,
|
||||||
} = servicesManager.services;
|
} = servicesManager.services;
|
||||||
|
|
||||||
if (this._userManager) {
|
if (this._userManager) {
|
||||||
@@ -176,18 +180,20 @@ class App extends Component {
|
|||||||
<UserManagerContext.Provider value={this._userManager}>
|
<UserManagerContext.Provider value={this._userManager}>
|
||||||
<Router basename={routerBasename}>
|
<Router basename={routerBasename}>
|
||||||
<WhiteLabelingContext.Provider value={whiteLabeling}>
|
<WhiteLabelingContext.Provider value={whiteLabeling}>
|
||||||
<SnackbarProvider service={UINotificationService}>
|
<LoggerProvider service={LoggerService}>
|
||||||
<DialogProvider service={UIDialogService}>
|
<SnackbarProvider service={UINotificationService}>
|
||||||
<ModalProvider
|
<DialogProvider service={UIDialogService}>
|
||||||
modal={OHIFModal}
|
<ModalProvider
|
||||||
service={UIModalService}
|
modal={OHIFModal}
|
||||||
>
|
service={UIModalService}
|
||||||
<OHIFStandaloneViewer
|
>
|
||||||
userManager={this._userManager}
|
<OHIFStandaloneViewer
|
||||||
/>
|
userManager={this._userManager}
|
||||||
</ModalProvider>
|
/>
|
||||||
</DialogProvider>
|
</ModalProvider>
|
||||||
</SnackbarProvider>
|
</DialogProvider>
|
||||||
|
</SnackbarProvider>
|
||||||
|
</LoggerProvider>
|
||||||
</WhiteLabelingContext.Provider>
|
</WhiteLabelingContext.Provider>
|
||||||
</Router>
|
</Router>
|
||||||
</UserManagerContext.Provider>
|
</UserManagerContext.Provider>
|
||||||
@@ -206,13 +212,18 @@ class App extends Component {
|
|||||||
<I18nextProvider i18n={i18n}>
|
<I18nextProvider i18n={i18n}>
|
||||||
<Router basename={routerBasename}>
|
<Router basename={routerBasename}>
|
||||||
<WhiteLabelingContext.Provider value={whiteLabeling}>
|
<WhiteLabelingContext.Provider value={whiteLabeling}>
|
||||||
<SnackbarProvider service={UINotificationService}>
|
<LoggerProvider service={LoggerService}>
|
||||||
<DialogProvider service={UIDialogService}>
|
<SnackbarProvider service={UINotificationService}>
|
||||||
<ModalProvider modal={OHIFModal} service={UIModalService}>
|
<DialogProvider service={UIDialogService}>
|
||||||
<OHIFStandaloneViewer />
|
<ModalProvider
|
||||||
</ModalProvider>
|
modal={OHIFModal}
|
||||||
</DialogProvider>
|
service={UIModalService}
|
||||||
</SnackbarProvider>
|
>
|
||||||
|
<OHIFStandaloneViewer />
|
||||||
|
</ModalProvider>
|
||||||
|
</DialogProvider>
|
||||||
|
</SnackbarProvider>
|
||||||
|
</LoggerProvider>
|
||||||
</WhiteLabelingContext.Provider>
|
</WhiteLabelingContext.Provider>
|
||||||
</Router>
|
</Router>
|
||||||
</I18nextProvider>
|
</I18nextProvider>
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -4,7 +4,7 @@ import React, { useEffect } from 'react';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { utils } from '@ohif/core';
|
import { utils } from '@ohif/core';
|
||||||
import { useSnackbarContext } from '@ohif/ui';
|
import { useSnackbarContext, useLogger } from '@ohif/ui';
|
||||||
//
|
//
|
||||||
import ViewportPane from './ViewportPane.js';
|
import ViewportPane from './ViewportPane.js';
|
||||||
import DefaultViewport from './DefaultViewport.js';
|
import DefaultViewport from './DefaultViewport.js';
|
||||||
@@ -36,18 +36,20 @@ const ViewportGrid = function(props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const snackbar = useSnackbarContext();
|
const snackbar = useSnackbarContext();
|
||||||
|
const logger = useLogger();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isStudyLoaded) {
|
if (isStudyLoaded) {
|
||||||
viewportData.forEach(displaySet => {
|
viewportData.forEach(displaySet => {
|
||||||
const promises = loadAndCacheDerivedDisplaySets(displaySet, studies);
|
const promises = loadAndCacheDerivedDisplaySets(displaySet, studies);
|
||||||
|
|
||||||
promises.forEach(promise => {
|
promises.forEach(promise => {
|
||||||
promise.catch(error => {
|
promise.catch(error => {
|
||||||
|
logger.error({ error, message: error.message });
|
||||||
snackbar.show({
|
snackbar.show({
|
||||||
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>
|
||||||
|
|||||||
@@ -5,10 +5,11 @@ import usePrevious from '../customHooks/usePrevious';
|
|||||||
import ConnectedViewer from './ConnectedViewer.js';
|
import ConnectedViewer from './ConnectedViewer.js';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { extensionManager } from './../App.js';
|
import { extensionManager } from './../App.js';
|
||||||
import { useSnackbarContext } from '@ohif/ui';
|
import { useSnackbarContext, ErrorPage } from '@ohif/ui';
|
||||||
|
|
||||||
// Contexts
|
// Contexts
|
||||||
import AppContext from '../context/AppContext';
|
import AppContext from '../context/AppContext';
|
||||||
|
import NotFound from '../routes/NotFound';
|
||||||
|
|
||||||
const { OHIFStudyMetadata, OHIFSeriesMetadata } = metadata;
|
const { OHIFStudyMetadata, OHIFSeriesMetadata } = metadata;
|
||||||
const { retrieveStudiesMetadata, deleteStudyMetadataPromise } = studies;
|
const { retrieveStudiesMetadata, deleteStudyMetadataPromise } = studies;
|
||||||
@@ -291,7 +292,7 @@ function ViewerRetrieveStudyData({
|
|||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
if (error && !error.isCanceled) {
|
if (error && !error.isCanceled) {
|
||||||
setError(true);
|
setError(error);
|
||||||
log.error(error);
|
log.error(error);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -358,13 +359,13 @@ function ViewerRetrieveStudyData({
|
|||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
if (error && !error.isCanceled) {
|
if (error && !error.isCanceled) {
|
||||||
setError(true);
|
setError(error);
|
||||||
log.error(error);
|
log.error(error);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error) {
|
if (error) {
|
||||||
setError(true);
|
setError(error);
|
||||||
log.error(error);
|
log.error(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -411,7 +412,12 @@ function ViewerRetrieveStudyData({
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return <div>Error: {JSON.stringify(error)}</div>;
|
const content = JSON.stringify(error);
|
||||||
|
if (content.includes('404') || content.includes('NOT_FOUND')) {
|
||||||
|
return <NotFound />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <NotFound message="Failed to retrieve study data" />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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.18.2:
|
||||||
version "0.16.5"
|
version "0.18.2"
|
||||||
resolved "https://registry.yarnpkg.com/dcmjs/-/dcmjs-0.16.5.tgz#05aa0c8d539a4d5777f996b9b796932d1f848993"
|
resolved "https://registry.yarnpkg.com/dcmjs/-/dcmjs-0.18.2.tgz#b8b20b29ec91749aaf08048bb54c2170a354e500"
|
||||||
integrity sha512-Y92KAx4JSfev79xjYhaukQZjWgyJlaHnXSTdbtkGkJAHERjIakgHcCIPmACuVmtW/nHViTu04zIGAB0pmZ4GaA==
|
integrity sha512-RH6pyMPdNpeZtlGqVuq5OI2cGyWJEvZ/hQ7nNgz8TWZrN5Pt0njnWqCR9SZJDnxS+Poubq3uo1A1sjP2RF0Bog==
|
||||||
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"
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user