Compare commits
60
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2a647fd618 | ||
|
|
f8fc31bcac | ||
|
|
b7ab88b0f1 | ||
|
|
57a30d7c34 | ||
|
|
cd90665b0e | ||
|
|
98c657231e | ||
|
|
14da247318 | ||
|
|
6a77d6c778 | ||
|
|
8f1785446b | ||
|
|
41225eefc0 | ||
|
|
7772fee21a | ||
|
|
d870ac914a | ||
|
|
451f7eab92 | ||
|
|
029f8af4c7 | ||
|
|
b1b4c8c861 | ||
|
|
cdde2af224 | ||
|
|
4a2922dda0 | ||
|
|
b6b5a8414f | ||
|
|
e7cc735c03 | ||
|
|
6121bdcf63 | ||
|
|
da70079675 | ||
|
|
9089875ca7 | ||
|
|
b4627ecfa3 | ||
|
|
4ede8a525a | ||
|
|
2c23818c86 | ||
|
|
fa4b03d62d | ||
|
|
698e900b85 | ||
|
|
b7d47383c5 | ||
|
|
6123741765 | ||
|
|
ab25be3520 | ||
|
|
950a54c704 | ||
|
|
4293c073f0 | ||
|
|
62a2cd5ff3 | ||
|
|
2d827c2a49 | ||
|
|
a8305772d0 | ||
|
|
eab115e70c | ||
|
|
c02b232b0c | ||
|
|
f01640d009 | ||
|
|
63fd65690c | ||
|
|
944afe4a3e | ||
|
|
b1678ce639 | ||
|
|
39937cca08 | ||
|
|
f7b8b6a41c | ||
|
|
79fe442293 | ||
|
|
875238e5b0 | ||
|
|
d2d3ca2828 | ||
|
|
cebea832a3 | ||
|
|
c4f84b1174 | ||
|
|
f9fe4232e7 | ||
|
|
42c22df1b6 | ||
|
|
19a8e71317 | ||
|
|
fa95bae6ef | ||
|
|
8a20a0526e | ||
|
|
5b20d8f323 | ||
|
|
9ea37a2201 | ||
|
|
2a66e175df | ||
|
|
77a421cdc0 | ||
|
|
7033886697 | ||
|
|
256563d7ee | ||
|
|
f7162ce617 |
No files matched your search
@@ -21,6 +21,19 @@ mv platform/viewer/dist/* .netlify/www/pwa -v
|
||||
# Build && Move script output
|
||||
# yarn run build:package
|
||||
|
||||
# Build && Move Docz Output
|
||||
# Using local yarn install to prevent Gatsby from needing to access
|
||||
# node_modules above the platform/ui folder
|
||||
cd platform/ui
|
||||
yarn install
|
||||
yarn run build
|
||||
cd ../..
|
||||
mkdir -p ./.netlify/www/ui
|
||||
mv platform/ui/.docz/dist/* .netlify/www/ui -v
|
||||
|
||||
# Cache all of the node_module dependencies in
|
||||
# extensions, modules, and platform packages
|
||||
yarn run lerna:cache
|
||||
echo 'Nothing left to see here. Go home, folks.'
|
||||
|
||||
# Build using react-scripts
|
||||
|
||||
@@ -122,7 +122,7 @@ window.config = {
|
||||
```
|
||||
|
||||
- Install the viewer:
|
||||
`window.OHIFStandaloneViewer.installViewer(window.config);`
|
||||
`window.OHIFViewer.installViewer(window.config);`
|
||||
|
||||
This exact setup is demonstrated in this
|
||||
[CodeSandbox](https://codesandbox.io/s/viewer-script-tag-tprch) and in our
|
||||
|
||||
@@ -3,6 +3,96 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [2.8.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.8.4...@ohif/extension-cornerstone@2.8.5) (2020-08-05)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.8.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.8.3...@ohif/extension-cornerstone@2.8.4) (2020-07-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 🐛 - Put guards in all places that a cornerstone re-render ([#1899](https://github.com/OHIF/Viewers/issues/1899)) ([451f7ea](https://github.com/OHIF/Viewers/commit/451f7eab9258e7a193eb362e0926b13aedc4b3c9))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.8.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.8.2...@ohif/extension-cornerstone@2.8.3) (2020-07-13)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.8.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.8.1...@ohif/extension-cornerstone@2.8.2) (2020-06-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 🐛 Disable seg panel when data for seg unavailable ([#1732](https://github.com/OHIF/Viewers/issues/1732)) ([698e900](https://github.com/OHIF/Viewers/commit/698e900b85121d3c2a46747c443ef69fb7a8c95b)), closes [#1728](https://github.com/OHIF/Viewers/issues/1728)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.8.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.8.0...@ohif/extension-cornerstone@2.8.1) (2020-06-04)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 🐛 Infinite frame index change when quickly jumping between ([#1734](https://github.com/OHIF/Viewers/issues/1734)) ([a830577](https://github.com/OHIF/Viewers/commit/a8305772d0fbec506549ad7ea08504a8bc3a4f39)), closes [#1733](https://github.com/OHIF/Viewers/issues/1733)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [2.8.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.7.3...@ohif/extension-cornerstone@2.8.0) (2020-06-04)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* 🎸 1729 - error boundary wrapper ([#1764](https://github.com/OHIF/Viewers/issues/1764)) ([c02b232](https://github.com/OHIF/Viewers/commit/c02b232b0cc24f38af5d5e3831d987d048e60ada))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.7.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.7.2...@ohif/extension-cornerstone@2.7.3) (2020-05-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 🐛 Fix seg color load ([#1724](https://github.com/OHIF/Viewers/issues/1724)) ([c4f84b1](https://github.com/OHIF/Viewers/commit/c4f84b1174d04ba84d37ed89b6d7ab541be28181))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.7.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.7.1...@ohif/extension-cornerstone@2.7.2) (2020-05-04)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 🐛 Proper error handling for derived display sets ([#1708](https://github.com/OHIF/Viewers/issues/1708)) ([5b20d8f](https://github.com/OHIF/Viewers/commit/5b20d8f323e4b3ef9988f2f2ab672d697b6da409))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.7.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.7.0...@ohif/extension-cornerstone@2.7.1) (2020-05-04)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [2.7.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.6.1...@ohif/extension-cornerstone@2.7.0) (2020-04-24)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/extension-cornerstone",
|
||||
"version": "2.7.0",
|
||||
"version": "2.8.5",
|
||||
"description": "OHIF extension for Cornerstone",
|
||||
"author": "OHIF",
|
||||
"license": "MIT",
|
||||
@@ -34,9 +34,9 @@
|
||||
"@ohif/ui": "^0.50.0",
|
||||
"cornerstone-core": "^2.2.8",
|
||||
"cornerstone-math": "^0.1.8",
|
||||
"cornerstone-tools": "4.12.5",
|
||||
"cornerstone-tools": "4.15.1",
|
||||
"cornerstone-wado-image-loader": "^3.1.0",
|
||||
"dcmjs": "^0.12.2",
|
||||
"dcmjs": "0.16.0",
|
||||
"dicom-parser": "^1.8.3",
|
||||
"hammerjs": "^2.0.8",
|
||||
"prop-types": "^15.6.2",
|
||||
@@ -51,6 +51,6 @@
|
||||
"classnames": "^2.2.6",
|
||||
"lodash.merge": "^4.6.2",
|
||||
"lodash.throttle": "^4.1.1",
|
||||
"react-cornerstone-viewport": "^2.3.6"
|
||||
"react-cornerstone-viewport": "4.0.1"
|
||||
}
|
||||
}
|
||||
@@ -179,7 +179,7 @@ class OHIFCornerstoneViewport extends Component {
|
||||
|
||||
if (
|
||||
displaySet.displaySetInstanceUID !==
|
||||
prevDisplaySet.displaySetInstanceUID ||
|
||||
prevDisplaySet.displaySetInstanceUID ||
|
||||
displaySet.SOPInstanceUID !== prevDisplaySet.SOPInstanceUID ||
|
||||
displaySet.frameIndex !== prevDisplaySet.frameIndex
|
||||
) {
|
||||
@@ -215,12 +215,28 @@ class OHIFCornerstoneViewport extends Component {
|
||||
});
|
||||
}
|
||||
|
||||
const newImageHandler = ({ currentImageIdIndex, sopInstanceUid }) => {
|
||||
const { displaySet } = this.props.viewportData;
|
||||
const { StudyInstanceUID } = displaySet;
|
||||
|
||||
if (currentImageIdIndex > 0) {
|
||||
this.props.onNewImage({
|
||||
StudyInstanceUID,
|
||||
SOPInstanceUID: sopInstanceUid,
|
||||
frameIndex: currentImageIdIndex,
|
||||
activeViewportIndex: viewportIndex,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<ConnectedCornerstoneViewport
|
||||
viewportIndex={viewportIndex}
|
||||
imageIds={imageIds}
|
||||
imageIdIndex={currentImageIdIndex}
|
||||
onNewImage={newImageHandler}
|
||||
onNewImageDebounceTime={700}
|
||||
// ~~ Connected (From REDUX)
|
||||
// frameRate={frameRate}
|
||||
// isPlaying={false}
|
||||
|
||||
@@ -10,6 +10,14 @@ const scroll = cornerstoneTools.import('util/scroll');
|
||||
const { studyMetadataManager } = OHIF.utils;
|
||||
const { setViewportSpecificData } = OHIF.redux.actions;
|
||||
|
||||
const refreshCornerstoneViewports = () => {
|
||||
cornerstone.getEnabledElements().forEach(enabledElement => {
|
||||
if (enabledElement.image) {
|
||||
cornerstone.updateImage(enabledElement.element);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const commandsModule = ({ servicesManager }) => {
|
||||
const actions = {
|
||||
rotateViewport: ({ viewports, rotation }) => {
|
||||
@@ -180,10 +188,7 @@ const commandsModule = ({ servicesManager }) => {
|
||||
|
||||
measurementApi.syncMeasurementsAndToolData();
|
||||
|
||||
// Update images in all active viewports
|
||||
cornerstone.getEnabledElements().forEach(enabledElement => {
|
||||
cornerstone.updateImage(enabledElement.element);
|
||||
});
|
||||
refreshCornerstoneViewports();
|
||||
},
|
||||
getNearbyToolData({ element, canvasCoordinates, availableToolTypes }) {
|
||||
const nearbyTool = {};
|
||||
@@ -262,23 +267,26 @@ const commandsModule = ({ servicesManager }) => {
|
||||
StudyInstanceUID,
|
||||
SOPInstanceUID,
|
||||
frameIndex,
|
||||
activeViewportIndex
|
||||
activeViewportIndex,
|
||||
}) => {
|
||||
const study = studyMetadataManager.get(StudyInstanceUID);
|
||||
|
||||
const displaySet = study.findDisplaySet(ds => {
|
||||
return ds.images && ds.images.find(i => i.getSOPInstanceUID() === SOPInstanceUID)
|
||||
return (
|
||||
ds.images &&
|
||||
ds.images.find(i => i.getSOPInstanceUID() === SOPInstanceUID)
|
||||
);
|
||||
});
|
||||
|
||||
displaySet.SOPInstanceUID = SOPInstanceUID;
|
||||
displaySet.frameIndex = frameIndex;
|
||||
|
||||
window.store.dispatch(setViewportSpecificData(activeViewportIndex, displaySet));
|
||||
window.store.dispatch(
|
||||
setViewportSpecificData(activeViewportIndex, displaySet)
|
||||
);
|
||||
|
||||
cornerstone.getEnabledElements().forEach(enabledElement => {
|
||||
cornerstone.updateImage(enabledElement.element);
|
||||
});
|
||||
}
|
||||
refreshCornerstoneViewports();
|
||||
},
|
||||
};
|
||||
|
||||
const definitions = {
|
||||
|
||||
@@ -34,8 +34,17 @@ export default {
|
||||
preRegistration({ servicesManager, configuration = {} }) {
|
||||
init({ servicesManager, configuration });
|
||||
},
|
||||
getViewportModule() {
|
||||
return OHIFCornerstoneViewport;
|
||||
getViewportModule({ commandsManager }) {
|
||||
const ExtendedOHIFCornerstoneViewport = props => {
|
||||
const onNewImageHandler = jumpData => {
|
||||
commandsManager.runCommand('jumpToImage', jumpData);
|
||||
};
|
||||
return (
|
||||
<OHIFCornerstoneViewport {...props} onNewImage={onNewImageHandler} />
|
||||
);
|
||||
};
|
||||
|
||||
return ExtendedOHIFCornerstoneViewport;
|
||||
},
|
||||
getToolbarModule() {
|
||||
return toolbarModule;
|
||||
|
||||
@@ -3,6 +3,36 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.2.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.2.3...@ohif/extension-dicom-html@1.2.4) (2020-07-13)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-html
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.2.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.2.2...@ohif/extension-dicom-html@1.2.3) (2020-06-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 🐛 Disable seg panel when data for seg unavailable ([#1732](https://github.com/OHIF/Viewers/issues/1732)) ([698e900](https://github.com/OHIF/Viewers/commit/698e900b85121d3c2a46747c443ef69fb7a8c95b)), closes [#1728](https://github.com/OHIF/Viewers/issues/1728)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.2.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.2.1...@ohif/extension-dicom-html@1.2.2) (2020-05-04)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 🐛 Proper error handling for derived display sets ([#1708](https://github.com/OHIF/Viewers/issues/1708)) ([5b20d8f](https://github.com/OHIF/Viewers/commit/5b20d8f323e4b3ef9988f2f2ab672d697b6da409))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.2.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.2.0...@ohif/extension-dicom-html@1.2.1) (2020-04-28)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-html
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/extension-dicom-html",
|
||||
"version": "1.2.1",
|
||||
"version": "1.2.4",
|
||||
"description": "OHIF extension for rendering structured reports to HTML",
|
||||
"author": "OHIF",
|
||||
"license": "MIT",
|
||||
@@ -29,7 +29,7 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ohif/core": "^0.50.0",
|
||||
"dcmjs": "^0.12.2",
|
||||
"dcmjs": "0.16.0",
|
||||
"prop-types": "^15.6.2",
|
||||
"react": "^16.8.6",
|
||||
"react-dom": "^16.8.6"
|
||||
|
||||
@@ -3,6 +3,17 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [0.51.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-microscopy@0.50.9...@ohif/extension-dicom-microscopy@0.51.0) (2020-06-04)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* 🎸 1729 - error boundary wrapper ([#1764](https://github.com/OHIF/Viewers/issues/1764)) ([c02b232](https://github.com/OHIF/Viewers/commit/c02b232b0cc24f38af5d5e3831d987d048e60ada))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.50.9](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-microscopy@0.50.8...@ohif/extension-dicom-microscopy@0.50.9) (2020-04-02)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-microscopy
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/extension-dicom-microscopy",
|
||||
"version": "0.50.9",
|
||||
"version": "0.51.0",
|
||||
"description": "OHIF extension for Dicom Microscopy",
|
||||
"author": "OHIF",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -92,8 +92,8 @@ class DicomMicroscopyViewport extends Component {
|
||||
{this.state.error ? (
|
||||
<h2>{JSON.stringify(this.state.error)}</h2>
|
||||
) : (
|
||||
<div style={style} ref={this.container} />
|
||||
)}
|
||||
<div style={style} ref={this.container} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,22 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [0.1.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-p10-downloader@0.1.1...@ohif/extension-dicom-p10-downloader@0.1.2) (2020-06-18)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-p10-downloader
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.1.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-p10-downloader@0.1.0...@ohif/extension-dicom-p10-downloader@0.1.1) (2020-06-04)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-p10-downloader
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [0.1.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-p10-downloader@0.0.2...@ohif/extension-dicom-p10-downloader@0.1.0) (2020-04-23)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/extension-dicom-p10-downloader",
|
||||
"version": "0.1.0",
|
||||
"version": "0.1.2",
|
||||
"description": "OHIF extension for downloading DICOM P10 files",
|
||||
"author": "OHIF",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import OHIF from '@ohif/core';
|
||||
import {
|
||||
save,
|
||||
upload,
|
||||
getDicomWebClientFromContext,
|
||||
getStudyInstanceUIDFromStudies,
|
||||
getSOPInstanceReferenceFromActiveViewport,
|
||||
getSOPInstanceReferencesFromViewports,
|
||||
} from './utils';
|
||||
import _downloadAndZip from './downloadAndZip';
|
||||
import _downloadAndZip, { downloadInstances } from './downloadAndZip';
|
||||
|
||||
const {
|
||||
utils: {
|
||||
Queue,
|
||||
},
|
||||
utils: { Queue },
|
||||
} = OHIF;
|
||||
|
||||
export function getCommands(context) {
|
||||
@@ -41,6 +41,14 @@ export function getCommands(context) {
|
||||
listOfUIDs
|
||||
);
|
||||
},
|
||||
downloadAndZipStudy({ servers, studies, progress }) {
|
||||
const dicomWebClient = getDicomWebClientFromContext(context, servers);
|
||||
const listOfUIDs = getStudyInstanceUIDFromStudies(studies);
|
||||
return save(
|
||||
_downloadAndZip(dicomWebClient, listOfUIDs, { progress }),
|
||||
listOfUIDs
|
||||
);
|
||||
},
|
||||
downloadAndZipSeriesOnViewports({ servers, viewports, progress }) {
|
||||
const dicomWebClient = getDicomWebClientFromContext(context, servers);
|
||||
const listOfUIDs = getSOPInstanceReferencesFromViewports(viewports);
|
||||
@@ -57,6 +65,28 @@ export function getCommands(context) {
|
||||
listOfUIDs
|
||||
);
|
||||
},
|
||||
downloadAndUploadStudy({ servers, studies, progress, serverConfig }) {
|
||||
const dicomWebClient = getDicomWebClientFromContext(context, servers);
|
||||
const listOfUIDs = getStudyInstanceUIDFromStudies(studies);
|
||||
return upload(
|
||||
downloadInstances(dicomWebClient, listOfUIDs, { progress }),
|
||||
/**
|
||||
* serverConfig is an object with the values used to create a new
|
||||
* instance of DICOMwebClient.
|
||||
*
|
||||
* Basic Structure:
|
||||
*
|
||||
* const config = {
|
||||
* url,
|
||||
* headers,
|
||||
* errorInterceptor
|
||||
* }
|
||||
*
|
||||
* const dicomWeb = new api.DICOMwebClient(config);
|
||||
*/
|
||||
serverConfig
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
const definitions = {
|
||||
@@ -64,6 +94,11 @@ export function getCommands(context) {
|
||||
commandFn: queue.bindSafe(actions.downloadAndZip, error),
|
||||
storeContexts: ['servers'],
|
||||
},
|
||||
downloadAndZipStudy: {
|
||||
commandFn: queue.bindSafe(actions.downloadAndZipStudy, error),
|
||||
storeContexts: ['servers', 'studies'],
|
||||
options: { progress },
|
||||
},
|
||||
downloadAndZipSeriesOnViewports: {
|
||||
commandFn: queue.bindSafe(actions.downloadAndZipSeriesOnViewports, error),
|
||||
storeContexts: ['servers', 'viewports'],
|
||||
@@ -77,6 +112,11 @@ export function getCommands(context) {
|
||||
storeContexts: ['servers', 'viewports'],
|
||||
options: { progress },
|
||||
},
|
||||
downloadAndUploadStudy: {
|
||||
commandFn: queue.bindSafe(actions.downloadAndUploadStudy, error),
|
||||
storeContexts: ['servers', 'studies'],
|
||||
options: { progress },
|
||||
},
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
@@ -79,13 +79,7 @@ async function downloadAndZip(dicomWebClient, listOfUIDs, options) {
|
||||
if (dicomWebClient instanceof api.DICOMwebClient) {
|
||||
const settings = buildSettings(listOfUIDs, options);
|
||||
const { compression } = settings.tasks;
|
||||
// Register user-provided progress handler as a task list observer
|
||||
progressUtils.addObserver(settings.taskList, settings.options.progress);
|
||||
const buffers = await downloadAll(dicomWebClient, settings).catch(error => {
|
||||
// Reject promise from compression task on download failure
|
||||
compression.deferred.reject(error);
|
||||
throw error;
|
||||
});
|
||||
const buffers = await downloadBuffers(settings, dicomWebClient);
|
||||
compression.deferred.resolve(zipAll(buffers, settings));
|
||||
const url = await compression.deferred.promise;
|
||||
return url;
|
||||
@@ -93,6 +87,27 @@ async function downloadAndZip(dicomWebClient, listOfUIDs, options) {
|
||||
throw new Error('A valid DICOM Web Client instance is expected');
|
||||
}
|
||||
|
||||
async function downloadInstances(dicomWebClient, listOfUIDs, options) {
|
||||
if (dicomWebClient instanceof api.DICOMwebClient) {
|
||||
const settings = buildSettings(listOfUIDs, options);
|
||||
const buffers = await downloadBuffers(settings, dicomWebClient);
|
||||
return buffers;
|
||||
}
|
||||
throw new Error('A valid DICOM Web Client instance is expected');
|
||||
}
|
||||
|
||||
async function downloadBuffers(settings, dicomWebClient) {
|
||||
const { compression } = settings.tasks;
|
||||
// Register user-provided progress handler as a task list observer
|
||||
progressUtils.addObserver(settings.taskList, settings.options.progress);
|
||||
const buffers = await downloadAll(dicomWebClient, settings).catch(error => {
|
||||
// Reject promise from compression task on download failure
|
||||
compression.deferred.reject(error);
|
||||
throw error;
|
||||
});
|
||||
return buffers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utils
|
||||
*/
|
||||
@@ -241,4 +256,4 @@ async function download(
|
||||
* Exports
|
||||
*/
|
||||
|
||||
export { downloadAndZip as default, downloadAndZip };
|
||||
export { downloadAndZip as default, downloadAndZip, downloadInstances };
|
||||
@@ -100,11 +100,59 @@ function save(promise, listOfUIDs) {
|
||||
});
|
||||
}
|
||||
|
||||
function upload(promise, serverConfig) {
|
||||
return Promise.resolve(promise)
|
||||
.then(async instances => {
|
||||
const instancesAmount = instances.length;
|
||||
OHIF.log.info(`Uploading study to ${serverConfig.url}`);
|
||||
OHIF.log.info(
|
||||
`${instancesAmount} instances are being uploaded. Don't close your browser.`
|
||||
);
|
||||
|
||||
try {
|
||||
const dicomWeb = new api.DICOMwebClient(serverConfig);
|
||||
let progress = 0;
|
||||
|
||||
const getProgress = () => {
|
||||
return ((progress * 100) / instancesAmount).toFixed();
|
||||
};
|
||||
|
||||
for (const instance of instances) {
|
||||
const options = {
|
||||
datasets: [instance],
|
||||
};
|
||||
|
||||
await dicomWeb.storeInstances(options);
|
||||
|
||||
progress++;
|
||||
|
||||
OHIF.log.info(`Progress: ${getProgress()}%`);
|
||||
}
|
||||
|
||||
OHIF.log.info('Successfully uploaded!');
|
||||
} catch (error) {
|
||||
OHIF.log.error(`Failed to upload: ${error}`);
|
||||
}
|
||||
|
||||
return instances;
|
||||
})
|
||||
.catch(error => {
|
||||
OHIF.log.error(`Failed to upload: ${error}`);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
function getStudyInstanceUIDFromStudies(studies) {
|
||||
return Object.keys(Object(Object(studies).studyData)).slice(0, 1);
|
||||
}
|
||||
|
||||
export {
|
||||
save,
|
||||
upload,
|
||||
validDicomUid,
|
||||
getDicomWebClientFromConfig,
|
||||
getDicomWebClientFromContext,
|
||||
getStudyInstanceUIDFromStudies,
|
||||
getSOPInstanceReferenceFromActiveViewport,
|
||||
getSOPInstanceReferencesFromViewports,
|
||||
};
|
||||
@@ -3,6 +3,115 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [0.2.7](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.2.6...@ohif/extension-dicom-rt@0.2.7) (2020-08-10)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.2.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.2.5...@ohif/extension-dicom-rt@0.2.6) (2020-08-05)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.2.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.2.4...@ohif/extension-dicom-rt@0.2.5) (2020-07-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 🐛 - Put guards in all places that a cornerstone re-render ([#1899](https://github.com/OHIF/Viewers/issues/1899)) ([451f7ea](https://github.com/OHIF/Viewers/commit/451f7eab9258e7a193eb362e0926b13aedc4b3c9))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.2.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.2.3...@ohif/extension-dicom-rt@0.2.4) (2020-07-13)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.2.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.2.2...@ohif/extension-dicom-rt@0.2.3) (2020-07-13)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.2.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.2.1...@ohif/extension-dicom-rt@0.2.2) (2020-07-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 🐛 Fix RT Panel hide/show and Fix looping load errors ([#1877](https://github.com/OHIF/Viewers/issues/1877)) ([e7cc735](https://github.com/OHIF/Viewers/commit/e7cc735c03d02eeb0d3af4ba02c15ed4f81bbec2))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.2.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.2.0...@ohif/extension-dicom-rt@0.2.1) (2020-06-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 🐛 Disable seg panel when data for seg unavailable ([#1732](https://github.com/OHIF/Viewers/issues/1732)) ([698e900](https://github.com/OHIF/Viewers/commit/698e900b85121d3c2a46747c443ef69fb7a8c95b)), closes [#1728](https://github.com/OHIF/Viewers/issues/1728)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [0.2.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.1.4...@ohif/extension-dicom-rt@0.2.0) (2020-06-04)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* 🎸 1729 - error boundary wrapper ([#1764](https://github.com/OHIF/Viewers/issues/1764)) ([c02b232](https://github.com/OHIF/Viewers/commit/c02b232b0cc24f38af5d5e3831d987d048e60ada))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.1.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.1.3...@ohif/extension-dicom-rt@0.1.4) (2020-05-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 🐛 Load default display set when no time metadata ([#1684](https://github.com/OHIF/Viewers/issues/1684)) ([f7b8b6a](https://github.com/OHIF/Viewers/commit/f7b8b6a41c4626084ef56b0fdf7363e914b143c4)), closes [#1683](https://github.com/OHIF/Viewers/issues/1683)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.1.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.1.2...@ohif/extension-dicom-rt@0.1.3) (2020-05-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 🐛 Fix seg color load ([#1724](https://github.com/OHIF/Viewers/issues/1724)) ([c4f84b1](https://github.com/OHIF/Viewers/commit/c4f84b1174d04ba84d37ed89b6d7ab541be28181))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.1.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.1.1...@ohif/extension-dicom-rt@0.1.2) (2020-05-04)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 🐛 Proper error handling for derived display sets ([#1708](https://github.com/OHIF/Viewers/issues/1708)) ([5b20d8f](https://github.com/OHIF/Viewers/commit/5b20d8f323e4b3ef9988f2f2ab672d697b6da409))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.1.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.1.0...@ohif/extension-dicom-rt@0.1.1) (2020-04-28)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-rt
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/extension-dicom-rt",
|
||||
"version": "0.1.1",
|
||||
"version": "0.2.7",
|
||||
"description": "OHIF extension for rendering DICOM RTSTRUCTs on top of cornerstone images.",
|
||||
"author": "OHIF",
|
||||
"license": "MIT",
|
||||
@@ -30,8 +30,8 @@
|
||||
"peerDependencies": {
|
||||
"@ohif/core": "^0.50.0",
|
||||
"cornerstone-core": "^2.2.8",
|
||||
"cornerstone-tools": "^4.0.9",
|
||||
"dcmjs": "^0.12.2",
|
||||
"cornerstone-tools": "4.15.1",
|
||||
"dcmjs": "0.16.0",
|
||||
"prop-types": "^15.6.2",
|
||||
"react": "^16.8.6",
|
||||
"react-dom": "^16.8.6"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { MODULE_TYPES, utils, DICOMWeb } from '@ohif/core';
|
||||
import loadRTStruct from './loadRTStruct';
|
||||
import getSourceDisplaySet from './getSourceDisplaySet';
|
||||
|
||||
import id from './id';
|
||||
|
||||
@@ -14,7 +15,7 @@ const OHIFDicomRTStructSopClassHandler = {
|
||||
id: 'OHIFDicomRTStructSopClassHandler',
|
||||
type: MODULE_TYPES.SOP_CLASS_HANDLER,
|
||||
sopClassUIDs,
|
||||
getDisplaySetFromSeries: function (
|
||||
getDisplaySetFromSeries: function(
|
||||
series,
|
||||
study,
|
||||
dicomWebClient,
|
||||
@@ -67,13 +68,18 @@ const OHIFDicomRTStructSopClassHandler = {
|
||||
}
|
||||
}
|
||||
|
||||
rtStructDisplaySet.load = function (referencedDisplaySet, studies) {
|
||||
rtStructDisplaySet.getSourceDisplaySet = function(studies) {
|
||||
return getSourceDisplaySet(studies, rtStructDisplaySet);
|
||||
};
|
||||
|
||||
rtStructDisplaySet.load = function(referencedDisplaySet, studies) {
|
||||
return loadRTStruct(
|
||||
rtStructDisplaySet,
|
||||
referencedDisplaySet,
|
||||
studies
|
||||
).catch(error => {
|
||||
rtStructDisplaySet.isLoaded = false;
|
||||
rtStructDisplaySet.loadError = true;
|
||||
throw new Error(error);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -17,7 +17,9 @@ const { studyMetadataManager } = utils;
|
||||
|
||||
const refreshViewport = () => {
|
||||
cornerstone.getEnabledElements().forEach(enabledElement => {
|
||||
cornerstone.updateImage(enabledElement.element);
|
||||
if (enabledElement.image) {
|
||||
cornerstone.updateImage(enabledElement.element);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -31,7 +33,18 @@ const refreshViewport = () => {
|
||||
* @param {number} props.isOpen - isOpen
|
||||
* @returns component
|
||||
*/
|
||||
const RTPanel = ({ studies, viewports, activeIndex, isOpen, onContourItemClick }) => {
|
||||
const RTPanel = ({
|
||||
studies,
|
||||
viewports,
|
||||
activeIndex,
|
||||
isOpen,
|
||||
onContourItemClick,
|
||||
activeContexts = [],
|
||||
contexts = {},
|
||||
}) => {
|
||||
const isVTK = () => activeContexts.includes(contexts.VTK);
|
||||
const isCornerstone = () => activeContexts.includes(contexts.CORNERSTONE);
|
||||
|
||||
const [selectedContour, setSelectedContour] = useState();
|
||||
const DEFAULT_SET_INDEX = 0;
|
||||
const DEFAULT_STATE = {
|
||||
@@ -65,7 +78,10 @@ const RTPanel = ({ studies, viewports, activeIndex, isOpen, onContourItemClick }
|
||||
document.addEventListener('extensiondicomrtrtloaded', updateStructureSets);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('extensiondicomrtrtloaded', updateStructureSets);
|
||||
document.removeEventListener(
|
||||
'extensiondicomrtrtloaded',
|
||||
updateStructureSets
|
||||
);
|
||||
};
|
||||
}, []);
|
||||
|
||||
@@ -78,7 +94,9 @@ const RTPanel = ({ studies, viewports, activeIndex, isOpen, onContourItemClick }
|
||||
activeViewport.SeriesInstanceUID
|
||||
);
|
||||
|
||||
const studyMetadata = studyMetadataManager.get(activeViewport.StudyInstanceUID);
|
||||
const studyMetadata = studyMetadataManager.get(
|
||||
activeViewport.StudyInstanceUID
|
||||
);
|
||||
const referencedDisplaysets = studyMetadata.getDerivedDatasets({
|
||||
referencedSeriesInstanceUID: activeViewport.SeriesInstanceUID,
|
||||
Modality: 'RTSTRUCT',
|
||||
@@ -89,7 +107,7 @@ const RTPanel = ({ studies, viewports, activeIndex, isOpen, onContourItemClick }
|
||||
setState({
|
||||
referencedDisplaysets,
|
||||
selectedSet: defaultSet,
|
||||
sets: viewportSets
|
||||
sets: viewportSets,
|
||||
});
|
||||
} else {
|
||||
setState(DEFAULT_STATE);
|
||||
@@ -105,7 +123,10 @@ const RTPanel = ({ studies, viewports, activeIndex, isOpen, onContourItemClick }
|
||||
setShowSettings(showSettings && !isOpen);
|
||||
}, [isOpen]);
|
||||
|
||||
const toContourItem = ({ ROINumber, ROIName, RTROIObservations, colorArray, visible }, loadedSet) => {
|
||||
const toContourItem = (
|
||||
{ ROINumber, ROIName, RTROIObservations, colorArray, visible, isSupported },
|
||||
loadedSet
|
||||
) => {
|
||||
let interpretedType = '';
|
||||
if (RTROIObservations && RTROIObservations.RTROIInterpretedType) {
|
||||
interpretedType = `(${RTROIObservations.RTROIInterpretedType})`;
|
||||
@@ -116,36 +137,45 @@ const RTPanel = ({ studies, viewports, activeIndex, isOpen, onContourItemClick }
|
||||
<StructureSetItem
|
||||
key={ROINumber}
|
||||
selected={isSameContour}
|
||||
isDisabled={!isSupported}
|
||||
onClick={() => {
|
||||
setSelectedContour(isSameContour ? null : ROINumber);
|
||||
|
||||
const enabledElements = cornerstone.getEnabledElements();
|
||||
const element = enabledElements[activeIndex].element;
|
||||
const toolState = cornerstoneTools.getToolState(element, 'stack');
|
||||
if (isCornerstone()) {
|
||||
const enabledElements = cornerstone.getEnabledElements();
|
||||
const element = enabledElements[activeIndex].element;
|
||||
const toolState = cornerstoneTools.getToolState(element, 'stack');
|
||||
|
||||
if (!toolState) {
|
||||
return;
|
||||
if (!toolState) {
|
||||
return;
|
||||
}
|
||||
|
||||
const imageIds = toolState.data[0].imageIds;
|
||||
|
||||
const module = cornerstoneTools.getModule('rtstruct');
|
||||
const imageId = module.getters.imageIdOfCenterFrameOfROIContour(
|
||||
state.selectedSet.SeriesInstanceUID,
|
||||
ROINumber,
|
||||
imageIds
|
||||
);
|
||||
|
||||
const frameIndex = imageIds.indexOf(imageId);
|
||||
const SOPInstanceUID = cornerstone.metaData.get(
|
||||
'SOPInstanceUID',
|
||||
imageId
|
||||
);
|
||||
const StudyInstanceUID = cornerstone.metaData.get(
|
||||
'StudyInstanceUID',
|
||||
imageId
|
||||
);
|
||||
|
||||
onContourItemClick({
|
||||
StudyInstanceUID,
|
||||
SOPInstanceUID,
|
||||
frameIndex,
|
||||
activeViewportIndex: activeIndex,
|
||||
});
|
||||
}
|
||||
|
||||
const imageIds = toolState.data[0].imageIds;
|
||||
|
||||
const module = cornerstoneTools.getModule('rtstruct');
|
||||
const imageId = module.getters.imageIdOfCenterFrameOfROIContour(
|
||||
state.selectedSet.SeriesInstanceUID,
|
||||
ROINumber,
|
||||
imageIds
|
||||
);
|
||||
|
||||
const frameIndex = imageIds.indexOf(imageId);
|
||||
const SOPInstanceUID = cornerstone.metaData.get('SOPInstanceUID', imageId);
|
||||
const StudyInstanceUID = cornerstone.metaData.get('StudyInstanceUID', imageId);
|
||||
|
||||
onContourItemClick({
|
||||
StudyInstanceUID,
|
||||
SOPInstanceUID,
|
||||
frameIndex,
|
||||
activeViewportIndex: activeIndex
|
||||
});
|
||||
}}
|
||||
label={`${ROIName} ${interpretedType}`}
|
||||
index={ROINumber}
|
||||
@@ -153,7 +183,10 @@ const RTPanel = ({ studies, viewports, activeIndex, isOpen, onContourItemClick }
|
||||
visible={visible}
|
||||
onVisibilityChange={() => {
|
||||
const module = cornerstoneTools.getModule('rtstruct');
|
||||
module.setters.toggleROIContour(state.selectedSet.SeriesInstanceUID, ROINumber);
|
||||
module.setters.toggleROIContour(
|
||||
state.selectedSet.SeriesInstanceUID,
|
||||
ROINumber
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
@@ -189,44 +222,70 @@ const RTPanel = ({ studies, viewports, activeIndex, isOpen, onContourItemClick }
|
||||
onClick={() => setShowSettings(true)}
|
||||
/>
|
||||
</div>
|
||||
{!state.referencedDisplaysets.length && <LoadingIndicator expand height="70px" width="70px" />}
|
||||
{state.referencedDisplaysets.map(displaySet => {
|
||||
const { SeriesInstanceUID, metadata, isLoaded } = displaySet;
|
||||
{!state.referencedDisplaysets.length && (
|
||||
<LoadingIndicator expand height="70px" width="70px" />
|
||||
)}
|
||||
{state.sets &&
|
||||
state.referencedDisplaysets.map(displaySet => {
|
||||
const { SeriesInstanceUID, metadata, isLoaded } = displaySet;
|
||||
|
||||
const module = cornerstoneTools.getModule('rtstruct');
|
||||
const sets = module.getters.structuresSetsWhichReferenceSeriesInstanceUid(viewports[activeIndex].SeriesInstanceUID);
|
||||
const module = cornerstoneTools.getModule('rtstruct');
|
||||
const sets = module.getters.structuresSetsWhichReferenceSeriesInstanceUid(
|
||||
viewports[activeIndex].SeriesInstanceUID
|
||||
);
|
||||
|
||||
const loadedSet = sets.find(set => set.SeriesInstanceUID === SeriesInstanceUID);
|
||||
return (
|
||||
<PanelSection
|
||||
key={SeriesInstanceUID}
|
||||
title={metadata.StructureSetLabel}
|
||||
loading={!isLoaded || !loadedSet}
|
||||
visible={isLoaded && loadedSet.visible}
|
||||
hideVisibleButton={!isLoaded}
|
||||
expanded={isLoaded && loadedSet.SeriesInstanceUID === state.selectedSet.SeriesInstanceUID}
|
||||
onVisibilityChange={() => {
|
||||
const module = cornerstoneTools.getModule('rtstruct');
|
||||
module.setters.toggleStructureSet(SeriesInstanceUID);
|
||||
}}
|
||||
onExpandChange={async () => {
|
||||
if (!isLoaded) {
|
||||
await displaySet.load(viewports[activeIndex], studies);
|
||||
const module = cornerstoneTools.getModule('rtstruct');
|
||||
const sets = module.getters.structuresSetsWhichReferenceSeriesInstanceUid(viewports[activeIndex].SeriesInstanceUID);
|
||||
const selectedSet = sets.find(set => set.SeriesInstanceUID === SeriesInstanceUID);
|
||||
setState(state => ({ ...state, selectedSet, sets }));
|
||||
const loadedSet = sets.find(
|
||||
set => set.SeriesInstanceUID === SeriesInstanceUID
|
||||
);
|
||||
return (
|
||||
<PanelSection
|
||||
key={SeriesInstanceUID}
|
||||
title={metadata.StructureSetLabel}
|
||||
loading={!isLoaded || !loadedSet}
|
||||
visible={isLoaded && loadedSet && loadedSet.visible}
|
||||
hideVisibleButton={!isLoaded}
|
||||
expanded={
|
||||
isLoaded &&
|
||||
loadedSet.SeriesInstanceUID ===
|
||||
state.selectedSet.SeriesInstanceUID
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ScrollableArea>
|
||||
<TableList headless>
|
||||
{isLoaded && loadedSet.ROIContours.map(c => toContourItem(c, loadedSet))}
|
||||
</TableList>
|
||||
</ScrollableArea>
|
||||
</PanelSection>
|
||||
);
|
||||
})}
|
||||
onVisibilityChange={newVisibility => {
|
||||
const module = cornerstoneTools.getModule('rtstruct');
|
||||
|
||||
if (newVisibility) {
|
||||
module.setters.showStructureSet(loadedSet.SeriesInstanceUID);
|
||||
} else {
|
||||
module.setters.hideStructureSet(loadedSet.SeriesInstanceUID);
|
||||
}
|
||||
const sets = module.getters.structuresSetsWhichReferenceSeriesInstanceUid(
|
||||
viewports[activeIndex].SeriesInstanceUID
|
||||
);
|
||||
setState(state => ({ ...state, sets }));
|
||||
refreshViewport();
|
||||
}}
|
||||
onExpandChange={async () => {
|
||||
if (!isLoaded) {
|
||||
await displaySet.load(viewports[activeIndex], studies);
|
||||
const module = cornerstoneTools.getModule('rtstruct');
|
||||
const sets = module.getters.structuresSetsWhichReferenceSeriesInstanceUid(
|
||||
viewports[activeIndex].SeriesInstanceUID
|
||||
);
|
||||
const selectedSet = sets.find(
|
||||
set => set.SeriesInstanceUID === SeriesInstanceUID
|
||||
);
|
||||
setState(state => ({ ...state, selectedSet, sets }));
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ScrollableArea>
|
||||
<TableList headless>
|
||||
{isLoaded &&
|
||||
loadedSet.ROIContours.map(c => toContourItem(c, loadedSet))}
|
||||
</TableList>
|
||||
</ScrollableArea>
|
||||
</PanelSection>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -22,11 +22,6 @@
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.dcmrt-structure-set-item.selected .item-actions {
|
||||
height: 35px;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.dcmrt-structure-set-item .item-actions {
|
||||
margin-left: -1px;
|
||||
background-color: var(--ui-gray-darker);
|
||||
@@ -47,7 +42,8 @@
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.dcmrt-structure-set-item .item-actions .btnAction:hover, .dcmrt-structure-set-item .item-actions .btnAction:active {
|
||||
.dcmrt-structure-set-item .item-actions .btnAction:hover,
|
||||
.dcmrt-structure-set-item .item-actions .btnAction:active {
|
||||
color: var(--text-primary-color);
|
||||
}
|
||||
|
||||
@@ -86,3 +82,12 @@
|
||||
.dcmrt-structure-set-item .item-label .eye-icon.--visible {
|
||||
color: var(--default-color);
|
||||
}
|
||||
|
||||
.dcmrt-structure-set-item.isDisabled .item-color-section {
|
||||
background-color: #e29e4a;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.dcmrt-structure-set-item.isDisabled .item-label {
|
||||
color: var(--text-disabled-color);
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useState } from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { TableListItem, Icon } from '@ohif/ui';
|
||||
import { TableListItem, Icon, Tooltip, OverlayTrigger } from '@ohif/ui';
|
||||
|
||||
import './StructureSetItem.css';
|
||||
|
||||
@@ -20,6 +20,7 @@ ColoredCircle.propTypes = {
|
||||
const StructureSetItem = ({
|
||||
index,
|
||||
label,
|
||||
isDisabled,
|
||||
onClick,
|
||||
itemClass,
|
||||
color,
|
||||
@@ -28,20 +29,47 @@ const StructureSetItem = ({
|
||||
selected = false,
|
||||
}) => {
|
||||
const [isVisible, setIsVisible] = useState(visible);
|
||||
return (
|
||||
<div className={`dcmrt-structure-set-item ${selected && 'selected'}`}>
|
||||
<TableListItem
|
||||
key={index}
|
||||
itemKey={index}
|
||||
itemIndex={index}
|
||||
itemClass={itemClass}
|
||||
itemMeta={<ColoredCircle color={color} />}
|
||||
itemMetaClass="item-color-section"
|
||||
onItemClick={onClick}
|
||||
>
|
||||
<div>
|
||||
<div className="item-label" style={{ marginBottom: 4 }}>
|
||||
<span>{label}</span>
|
||||
|
||||
useEffect(() => {
|
||||
setIsVisible(visible);
|
||||
}, [visible]);
|
||||
|
||||
let dcmrtClassNames = `dcmrt-structure-set-item`;
|
||||
|
||||
if (selected) {
|
||||
dcmrtClassNames += ' selected';
|
||||
}
|
||||
|
||||
if (isDisabled) {
|
||||
dcmrtClassNames += ' isDisabled';
|
||||
}
|
||||
|
||||
const warningIcon = (
|
||||
<span>
|
||||
<Icon name="exclamation-triangle" />
|
||||
</span>
|
||||
);
|
||||
|
||||
const tableListItem = (
|
||||
<TableListItem
|
||||
key={index}
|
||||
itemKey={index}
|
||||
itemIndex={index}
|
||||
itemClass={itemClass}
|
||||
itemMeta={isDisabled ? warningIcon : <ColoredCircle color={color} />}
|
||||
itemMetaClass="item-color-section"
|
||||
onItemClick={() => {
|
||||
if (isDisabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
onClick();
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<div className="item-label" style={{ marginBottom: 4 }}>
|
||||
<span>{label}</span>
|
||||
{!isDisabled && (
|
||||
<Icon
|
||||
className={`eye-icon ${isVisible && '--visible'}`}
|
||||
name={isVisible ? 'eye' : 'eye-closed'}
|
||||
@@ -49,37 +77,72 @@ const StructureSetItem = ({
|
||||
height="20px"
|
||||
onClick={event => {
|
||||
event.stopPropagation();
|
||||
|
||||
if (isDisabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const newVisibility = !isVisible;
|
||||
setIsVisible(newVisibility);
|
||||
onVisibilityChange(newVisibility);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{false && <div className="item-info">{'...'}</div>}
|
||||
{false && (
|
||||
<div className="item-actions">
|
||||
<button
|
||||
className="btnAction"
|
||||
onClick={() => console.log('Relabelling...')}
|
||||
>
|
||||
<span style={{ marginRight: '4px' }}>
|
||||
<Icon name="edit" width="14px" height="14px" />
|
||||
</span>
|
||||
Relabel
|
||||
</button>
|
||||
<button
|
||||
className="btnAction"
|
||||
onClick={() => console.log('Editing description...')}
|
||||
>
|
||||
<span style={{ marginRight: '4px' }}>
|
||||
<Icon name="edit" width="14px" height="14px" />
|
||||
</span>
|
||||
Description
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</TableListItem>
|
||||
|
||||
{false && <div className="item-info">{'...'}</div>}
|
||||
{false && (
|
||||
<div className="item-actions">
|
||||
<button
|
||||
className="btnAction"
|
||||
onClick={() => console.log('Relabelling...')}
|
||||
>
|
||||
<span style={{ marginRight: '4px' }}>
|
||||
<Icon name="edit" width="14px" height="14px" />
|
||||
</span>
|
||||
Relabel
|
||||
</button>
|
||||
<button
|
||||
className="btnAction"
|
||||
onClick={() => console.log('Editing description...')}
|
||||
>
|
||||
<span style={{ marginRight: '4px' }}>
|
||||
<Icon name="edit" width="14px" height="14px" />
|
||||
</span>
|
||||
Description
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</TableListItem>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={dcmrtClassNames}>
|
||||
<React.Fragment>
|
||||
{isDisabled ? (
|
||||
<OverlayTrigger
|
||||
key={index}
|
||||
placement="left"
|
||||
overlay={
|
||||
<Tooltip
|
||||
placement="left"
|
||||
className="in tooltip-warning"
|
||||
id="tooltip-left"
|
||||
>
|
||||
<div className="warningTitle">Unsupported Region</div>
|
||||
<div className="warningContent">
|
||||
Contour type currently unsupported.
|
||||
</div>
|
||||
</Tooltip>
|
||||
}
|
||||
>
|
||||
<div>{tableListItem}</div>
|
||||
</OverlayTrigger>
|
||||
) : (
|
||||
<React.Fragment>{tableListItem} </React.Fragment>
|
||||
)}
|
||||
</React.Fragment>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -94,7 +157,7 @@ StructureSetItem.propTypes = {
|
||||
|
||||
StructureSetItem.defaultProps = {
|
||||
itemClass: '',
|
||||
onClick: () => { },
|
||||
onClick: () => {},
|
||||
};
|
||||
|
||||
export default StructureSetItem;
|
||||
@@ -0,0 +1,38 @@
|
||||
export default function getSourceDisplaySet(studies, rtStructDisplaySet) {
|
||||
const referencedDisplaySet = _getReferencedDisplaySet(
|
||||
rtStructDisplaySet,
|
||||
studies
|
||||
);
|
||||
|
||||
rtStructDisplaySet.load(referencedDisplaySet, studies);
|
||||
|
||||
return referencedDisplaySet;
|
||||
}
|
||||
|
||||
const _getReferencedDisplaySet = (rtStructDisplaySet, studies) => {
|
||||
let allDisplaySets = [];
|
||||
|
||||
studies.forEach(study => {
|
||||
allDisplaySets = allDisplaySets.concat(study.displaySets);
|
||||
});
|
||||
|
||||
const otherDisplaySets = allDisplaySets.filter(
|
||||
ds => ds.displaySetInstanceUID !== rtStructDisplaySet.displaySetInstanceUID
|
||||
);
|
||||
|
||||
const ReferencedSeriesSequence = Array.isArray(
|
||||
rtStructDisplaySet.metadata.ReferencedSeriesSequence
|
||||
)
|
||||
? rtStructDisplaySet.metadata.ReferencedSeriesSequence
|
||||
: [rtStructDisplaySet.metadata.ReferencedSeriesSequence];
|
||||
|
||||
const referencedSeriesInstanceUIDs = ReferencedSeriesSequence.map(
|
||||
ReferencedSeries => ReferencedSeries.SeriesInstanceUID
|
||||
);
|
||||
|
||||
const referencedDisplaySet = otherDisplaySets.find(ds =>
|
||||
referencedSeriesInstanceUIDs.includes(ds.SeriesInstanceUID)
|
||||
);
|
||||
|
||||
return referencedDisplaySet;
|
||||
};
|
||||
@@ -20,14 +20,21 @@ export default {
|
||||
preRegistration({ servicesManager, configuration = {} }) {
|
||||
init({ servicesManager, configuration });
|
||||
},
|
||||
getPanelModule({ commandsManager }) {
|
||||
getPanelModule({ commandsManager, servicesManager, api }) {
|
||||
const ExtendedRTPanel = props => {
|
||||
const { activeContexts } = api.hooks.useAppContext();
|
||||
|
||||
const contourItemClickHandler = contourData => {
|
||||
commandsManager.runCommand('jumpToImage', contourData);
|
||||
};
|
||||
|
||||
return (
|
||||
<RTPanel {...props} onContourItemClick={contourItemClickHandler} />
|
||||
<RTPanel
|
||||
{...props}
|
||||
onContourItemClick={contourItemClickHandler}
|
||||
activeContexts={activeContexts}
|
||||
contexts={api.contexts}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@ export default async function loadRTStruct(
|
||||
studies
|
||||
) {
|
||||
const rtStructModule = cornerstoneTools.getModule('rtstruct');
|
||||
|
||||
// Set here is loading is asynchronous.
|
||||
// If this function throws its set back to false.
|
||||
rtStructDisplaySet.isLoaded = true;
|
||||
@@ -68,7 +67,6 @@ export default async function loadRTStruct(
|
||||
const rtStructDisplayToolName = TOOL_NAMES.RTSTRUCT_DISPLAY_TOOL;
|
||||
|
||||
for (let i = 0; i < ROIContourSequence.length; i++) {
|
||||
|
||||
const ROIContour = ROIContourSequence[i];
|
||||
const { ReferencedROINumber, ContourSequence } = ROIContour;
|
||||
|
||||
@@ -76,12 +74,7 @@ export default async function loadRTStruct(
|
||||
continue;
|
||||
}
|
||||
|
||||
_setROIContourMetadata(
|
||||
structureSet,
|
||||
StructureSetROISequence,
|
||||
RTROIObservationsSequence,
|
||||
ROIContour
|
||||
);
|
||||
const isSupported = false;
|
||||
|
||||
for (let c = 0; c < ContourSequence.length; c++) {
|
||||
const {
|
||||
@@ -93,10 +86,12 @@ export default async function loadRTStruct(
|
||||
|
||||
if (ContourGeometricType !== 'CLOSED_PLANAR') {
|
||||
// TODO: Do we want to visualise types other than closed planar?
|
||||
// We could easily do open planar.
|
||||
// We could easily do open planar and point.
|
||||
continue;
|
||||
}
|
||||
|
||||
isSupported = true;
|
||||
|
||||
const sopInstanceUID = ContourImageSequence.ReferencedSOPInstanceUID;
|
||||
const imageId = _getImageId(imageIdSopInstanceUidPairs, sopInstanceUID);
|
||||
const imageIdSpecificToolData = _getOrCreateImageIdSpecificToolData(
|
||||
@@ -128,6 +123,14 @@ export default async function loadRTStruct(
|
||||
|
||||
imageIdSpecificToolData.push(measurementData);
|
||||
}
|
||||
|
||||
_setROIContourMetadata(
|
||||
structureSet,
|
||||
StructureSetROISequence,
|
||||
RTROIObservationsSequence,
|
||||
ROIContour,
|
||||
isSupported
|
||||
);
|
||||
}
|
||||
|
||||
_setToolEnabledIfNotEnabled(rtStructDisplayToolName);
|
||||
@@ -157,7 +160,8 @@ function _setROIContourMetadata(
|
||||
structureSet,
|
||||
StructureSetROISequence,
|
||||
RTROIObservationsSequence,
|
||||
ROIContour
|
||||
ROIContour,
|
||||
isSupported
|
||||
) {
|
||||
const StructureSetROI = StructureSetROISequence.find(
|
||||
structureSetROI =>
|
||||
@@ -169,6 +173,7 @@ function _setROIContourMetadata(
|
||||
ROIName: StructureSetROI.ROIName,
|
||||
ROIGenerationAlgorithm: StructureSetROI.ROIGenerationAlgorithm,
|
||||
ROIDescription: StructureSetROI.ROIDescription,
|
||||
isSupported,
|
||||
visible: true,
|
||||
};
|
||||
|
||||
@@ -238,7 +243,7 @@ function _setROIContourRTROIObservations(
|
||||
|
||||
function _setToolEnabledIfNotEnabled(toolName) {
|
||||
cornerstone.getEnabledElements().forEach(enabledElement => {
|
||||
const { element } = enabledElement;
|
||||
const { element, image } = enabledElement;
|
||||
const tool = cornerstoneTools.getToolForElement(element, toolName);
|
||||
|
||||
if (tool.mode !== 'enabled') {
|
||||
@@ -246,7 +251,9 @@ function _setToolEnabledIfNotEnabled(toolName) {
|
||||
cornerstoneTools.setToolEnabled(toolName);
|
||||
}
|
||||
|
||||
cornerstone.updateImage(element);
|
||||
if (image) {
|
||||
cornerstone.updateImage(element);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,9 @@ import structureSetReferencesSeriesInstanceUid from './lib/structureSetReference
|
||||
// We should put this as a helper somewhere as we are using it in mutliple places.
|
||||
function refreshViewport() {
|
||||
cornerstone.getEnabledElements().forEach(enabledElement => {
|
||||
cornerstone.updateImage(enabledElement.element);
|
||||
if (enabledElement.image) {
|
||||
cornerstone.updateImage(enabledElement.element);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -81,7 +83,9 @@ function _setStructureSetVisible(SeriesInstanceUID, visible = true) {
|
||||
const StructureSet = getStructureSet(SeriesInstanceUID);
|
||||
|
||||
if (StructureSet) {
|
||||
StructureSet.visible = visible;
|
||||
StructureSet.ROIContours.forEach(ROIContour => {
|
||||
ROIContour.visible = visible;
|
||||
});
|
||||
|
||||
refreshViewport();
|
||||
}
|
||||
@@ -154,15 +158,10 @@ function setToggleROIContour(SeriesInstanceUID, ROINumber) {
|
||||
* Returns an array of StructureSets which reference the given SeriesInstanceUID.
|
||||
* @param {string} SeriesInstanceUID The SeriesInstanceUID to check.
|
||||
*/
|
||||
function getStructuresSetsWhichReferenceSeriesInstanceUid(
|
||||
SeriesInstanceUID
|
||||
) {
|
||||
function getStructuresSetsWhichReferenceSeriesInstanceUid(SeriesInstanceUID) {
|
||||
const { StructureSets } = state;
|
||||
return StructureSets.filter(StructureSet =>
|
||||
structureSetReferencesSeriesInstanceUid(
|
||||
StructureSet,
|
||||
SeriesInstanceUID
|
||||
)
|
||||
structureSetReferencesSeriesInstanceUid(StructureSet, SeriesInstanceUID)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,93 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [0.3.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.3.3...@ohif/extension-dicom-segmentation@0.3.4) (2020-08-10)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.3.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.3.2...@ohif/extension-dicom-segmentation@0.3.3) (2020-07-13)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.3.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.3.1...@ohif/extension-dicom-segmentation@0.3.2) (2020-07-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 🐛 Fix RT Panel hide/show and Fix looping load errors ([#1877](https://github.com/OHIF/Viewers/issues/1877)) ([e7cc735](https://github.com/OHIF/Viewers/commit/e7cc735c03d02eeb0d3af4ba02c15ed4f81bbec2))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.3.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.3.0...@ohif/extension-dicom-segmentation@0.3.1) (2020-06-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 🐛 Disable seg panel when data for seg unavailable ([#1732](https://github.com/OHIF/Viewers/issues/1732)) ([698e900](https://github.com/OHIF/Viewers/commit/698e900b85121d3c2a46747c443ef69fb7a8c95b)), closes [#1728](https://github.com/OHIF/Viewers/issues/1728)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [0.3.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.2.6...@ohif/extension-dicom-segmentation@0.3.0) (2020-06-04)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* 🎸 1729 - error boundary wrapper ([#1764](https://github.com/OHIF/Viewers/issues/1764)) ([c02b232](https://github.com/OHIF/Viewers/commit/c02b232b0cc24f38af5d5e3831d987d048e60ada))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.2.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.2.5...@ohif/extension-dicom-segmentation@0.2.6) (2020-05-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 🐛 Fix seg color load ([#1724](https://github.com/OHIF/Viewers/issues/1724)) ([c4f84b1](https://github.com/OHIF/Viewers/commit/c4f84b1174d04ba84d37ed89b6d7ab541be28181))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.2.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.2.4...@ohif/extension-dicom-segmentation@0.2.5) (2020-05-06)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.2.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.2.3...@ohif/extension-dicom-segmentation@0.2.4) (2020-05-04)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.2.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.2.2...@ohif/extension-dicom-segmentation@0.2.3) (2020-05-04)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 🐛 Proper error handling for derived display sets ([#1708](https://github.com/OHIF/Viewers/issues/1708)) ([5b20d8f](https://github.com/OHIF/Viewers/commit/5b20d8f323e4b3ef9988f2f2ab672d697b6da409))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.2.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.2.1...@ohif/extension-dicom-segmentation@0.2.2) (2020-04-28)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/extension-dicom-segmentation",
|
||||
"version": "0.2.2",
|
||||
"version": "0.3.4",
|
||||
"description": "OHIF extension for viewing segmentations in the 2D MPR view",
|
||||
"author": "OHIF",
|
||||
"license": "MIT",
|
||||
@@ -30,14 +30,15 @@
|
||||
"peerDependencies": {
|
||||
"@ohif/core": "^0.50.0",
|
||||
"cornerstone-core": "^2.2.8",
|
||||
"cornerstone-tools": "4.12.5",
|
||||
"dcmjs": "^0.12.2",
|
||||
"cornerstone-tools": "4.15.1",
|
||||
"dcmjs": "0.16.0",
|
||||
"prop-types": "^15.6.2",
|
||||
"react": "^16.8.6",
|
||||
"react-dom": "^16.8.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.5.5",
|
||||
"gl-matrix": "^3.3.0",
|
||||
"react-select": "^3.0.8"
|
||||
}
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
import { MODULE_TYPES, utils } from '@ohif/core';
|
||||
import loadSegmentation from './loadSegmentation';
|
||||
|
||||
// TODO: Should probably use dcmjs for this
|
||||
const SOP_CLASS_UIDS = {
|
||||
DICOM_SEG: '1.2.840.10008.5.1.4.1.1.66.4',
|
||||
};
|
||||
|
||||
const sopClassUIDs = Object.values(SOP_CLASS_UIDS);
|
||||
|
||||
// TODO: Handle the case where there is more than one SOP Class Handler for the
|
||||
// same SOP Class.
|
||||
const OHIFDicomSegSopClassHandler = {
|
||||
id: 'OHIFDicomSegSopClassHandler',
|
||||
type: MODULE_TYPES.SOP_CLASS_HANDLER,
|
||||
sopClassUIDs,
|
||||
getDisplaySetFromSeries: function (
|
||||
series,
|
||||
study,
|
||||
dicomWebClient,
|
||||
authorizationHeaders
|
||||
) {
|
||||
const instance = series.getFirstInstance();
|
||||
const metadata = instance.getData().metadata;
|
||||
|
||||
const {
|
||||
SeriesDate,
|
||||
SeriesTime,
|
||||
SeriesDescription,
|
||||
FrameOfReferenceUID,
|
||||
SOPInstanceUID,
|
||||
SeriesInstanceUID,
|
||||
StudyInstanceUID,
|
||||
} = metadata;
|
||||
|
||||
const segDisplaySet = {
|
||||
Modality: 'SEG',
|
||||
displaySetInstanceUID: utils.guid(),
|
||||
wadoRoot: study.getData().wadoRoot,
|
||||
wadoUri: instance.getData().wadouri,
|
||||
SOPInstanceUID,
|
||||
SeriesInstanceUID,
|
||||
StudyInstanceUID,
|
||||
FrameOfReferenceUID,
|
||||
authorizationHeaders,
|
||||
metadata,
|
||||
isDerived: true,
|
||||
referencedDisplaySetUID: null, // Assigned when loaded.
|
||||
labelmapIndex: null, // Assigned when loaded.
|
||||
isLoaded: false,
|
||||
SeriesDate,
|
||||
SeriesTime,
|
||||
SeriesDescription,
|
||||
};
|
||||
|
||||
segDisplaySet.load = function (referencedDisplaySet, studies) {
|
||||
return loadSegmentation(
|
||||
segDisplaySet,
|
||||
referencedDisplaySet,
|
||||
studies
|
||||
).catch(error => {
|
||||
segDisplaySet.isLoaded = false;
|
||||
throw new Error(error);
|
||||
});
|
||||
};
|
||||
|
||||
return segDisplaySet;
|
||||
},
|
||||
};
|
||||
|
||||
export default OHIFDicomSegSopClassHandler;
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState } from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { TableListItem, Icon } from '@ohif/ui';
|
||||
|
||||
@@ -19,6 +19,11 @@ ColoredCircle.propTypes = {
|
||||
|
||||
const SegmentItem = ({ index, label, onClick, itemClass, color, visible = true, onVisibilityChange }) => {
|
||||
const [isVisible, setIsVisible] = useState(visible);
|
||||
|
||||
useEffect(() => {
|
||||
setIsVisible(visible);
|
||||
}, [visible]);
|
||||
|
||||
return (
|
||||
<div className="dcmseg-segment-item">
|
||||
<TableListItem
|
||||
|
||||
@@ -8,6 +8,11 @@
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.dcmseg-segmentation-panel.disabled {
|
||||
opacity: 0.6;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.dcmseg-segmentation-panel h3 {
|
||||
padding-left: 20px;
|
||||
margin-top: 0;
|
||||
|
||||
+231
-135
@@ -6,6 +6,9 @@ import moment from 'moment';
|
||||
import { utils, log } from '@ohif/core';
|
||||
import { ScrollableArea, TableList, Icon } from '@ohif/ui';
|
||||
|
||||
import setActiveLabelmap from '../../utils/setActiveLabelMap';
|
||||
import refreshViewports from '../../utils/refreshViewports';
|
||||
|
||||
import {
|
||||
BrushColorSelector,
|
||||
BrushRadius,
|
||||
@@ -19,12 +22,6 @@ import SegmentationSettings from '../SegmentationSettings/SegmentationSettings';
|
||||
|
||||
const { studyMetadataManager } = utils;
|
||||
|
||||
const refreshViewport = () => {
|
||||
cornerstone.getEnabledElements().forEach(enabledElement => {
|
||||
cornerstone.updateImage(enabledElement.element);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* SegmentationPanel component
|
||||
*
|
||||
@@ -32,7 +29,11 @@ const refreshViewport = () => {
|
||||
* @param {Array} props.viewports - Viewports data (viewportSpecificData)
|
||||
* @param {number} props.activeIndex - Active viewport index
|
||||
* @param {boolean} props.isOpen - Boolean that indicates if the panel is expanded
|
||||
* @param {Function} props.onSegItemClick - Segment click handler
|
||||
* @param {Function} props.onSegmentItemClick - Segment click handler
|
||||
* @param {Function} props.onSegmentVisibilityChange - Segment visibiliy change handler
|
||||
* @param {Function} props.onConfigurationChange - Configuration change handler
|
||||
* @param {Function} props.activeContexts - List of active application contexts
|
||||
* @param {Function} props.contexts - List of available application contexts
|
||||
* @returns component
|
||||
*/
|
||||
const SegmentationPanel = ({
|
||||
@@ -40,24 +41,40 @@ const SegmentationPanel = ({
|
||||
viewports,
|
||||
activeIndex,
|
||||
isOpen,
|
||||
onSegItemClick,
|
||||
onSegmentItemClick,
|
||||
onSegmentVisibilityChange,
|
||||
onConfigurationChange,
|
||||
onDisplaySetLoadFailure,
|
||||
onSelectedSegmentationChange,
|
||||
activeContexts = [],
|
||||
contexts = {},
|
||||
}) => {
|
||||
const isVTK = () => activeContexts.includes(contexts.VTK);
|
||||
const isCornerstone = () => activeContexts.includes(contexts.CORNERSTONE);
|
||||
|
||||
/*
|
||||
* TODO: wrap get/set interactions with the cornerstoneTools
|
||||
* store with context to make these kind of things less blurry.
|
||||
*/
|
||||
const { configuration } = cornerstoneTools.getModule('segmentation');
|
||||
const DEFAULT_BRUSH_RADIUS = configuration.radius || 10;
|
||||
|
||||
/*
|
||||
* TODO: We shouldn't hardcode brushColor color, in the future
|
||||
* the SEG may set the colorLUT to whatever it wants.
|
||||
*/
|
||||
const [state, setState] = useState({
|
||||
brushRadius: DEFAULT_BRUSH_RADIUS,
|
||||
brushColor:
|
||||
'rgba(221, 85, 85, 1)' /* TODO: We shouldn't hardcode this color, in the future the SEG may set the colorLUT to whatever it wants. */,
|
||||
brushColor: 'rgba(221, 85, 85, 1)',
|
||||
selectedSegment: null,
|
||||
selectedSegmentation: null,
|
||||
showSegSettings: false,
|
||||
showSegmentationSettings: false,
|
||||
brushStackState: null,
|
||||
labelmapList: [],
|
||||
segmentList: [],
|
||||
cachedSegmentsProperties: [],
|
||||
isLoading: false,
|
||||
isDisabled: true,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
@@ -74,6 +91,28 @@ const SegmentationPanel = ({
|
||||
updateState('brushStackState', module.state.series[firstImageId]);
|
||||
};
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
document.addEventListener(
|
||||
'extensiondicomsegmentationsegloaded',
|
||||
refreshSegmentations
|
||||
);
|
||||
|
||||
/*
|
||||
* These are specific to each element;
|
||||
* Need to iterate cornerstone-tools tracked enabled elements?
|
||||
@@ -87,6 +126,10 @@ const SegmentationPanel = ({
|
||||
);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener(
|
||||
'extensiondicomsegmentationsegloaded',
|
||||
refreshSegmentations
|
||||
);
|
||||
cornerstoneTools.store.state.enabledElements.forEach(enabledElement =>
|
||||
enabledElement.removeEventListener(
|
||||
'cornerstonetoolslabelmapmodified',
|
||||
@@ -94,49 +137,69 @@ const SegmentationPanel = ({
|
||||
)
|
||||
);
|
||||
};
|
||||
});
|
||||
}, [activeIndex, viewports]);
|
||||
|
||||
useEffect(() => {
|
||||
const refreshSegmentations = useCallback(() => {
|
||||
const module = cornerstoneTools.getModule('segmentation');
|
||||
const activeViewport = viewports[activeIndex];
|
||||
const studyMetadata = studyMetadataManager.get(
|
||||
activeViewport.StudyInstanceUID
|
||||
);
|
||||
const firstImageId = studyMetadata.getFirstImageId(
|
||||
activeViewport.displaySetInstanceUID
|
||||
);
|
||||
const brushStackState = module.state.series[firstImageId];
|
||||
|
||||
if (brushStackState) {
|
||||
const labelmap3D =
|
||||
brushStackState.labelmaps3D[brushStackState.activeLabelmapIndex];
|
||||
const labelmapList = getLabelmapList(
|
||||
brushStackState,
|
||||
firstImageId,
|
||||
activeViewport
|
||||
const isDisabled = !activeViewport || !activeViewport.StudyInstanceUID;
|
||||
if (!isDisabled) {
|
||||
const studyMetadata = studyMetadataManager.get(
|
||||
activeViewport.StudyInstanceUID
|
||||
);
|
||||
const segmentList = getSegmentList(labelmap3D, firstImageId, brushStackState);
|
||||
setState(state => ({
|
||||
...state,
|
||||
brushStackState,
|
||||
selectedSegmentation: brushStackState.activeLabelmapIndex,
|
||||
labelmapList,
|
||||
segmentList,
|
||||
}));
|
||||
} else {
|
||||
setState(state => ({
|
||||
...state,
|
||||
labelmapList: [],
|
||||
segmentList: [],
|
||||
}));
|
||||
const firstImageId = studyMetadata.getFirstImageId(
|
||||
activeViewport.displaySetInstanceUID
|
||||
);
|
||||
const brushStackState = module.state.series[firstImageId];
|
||||
if (brushStackState) {
|
||||
const labelmap3D =
|
||||
brushStackState.labelmaps3D[brushStackState.activeLabelmapIndex];
|
||||
const labelmapList = getLabelmapList(
|
||||
brushStackState,
|
||||
firstImageId,
|
||||
activeViewport
|
||||
);
|
||||
const segmentList = getSegmentList(
|
||||
labelmap3D,
|
||||
firstImageId,
|
||||
brushStackState
|
||||
);
|
||||
setState(state => ({
|
||||
...state,
|
||||
brushStackState,
|
||||
selectedSegmentation: brushStackState.activeLabelmapIndex,
|
||||
labelmapList,
|
||||
segmentList,
|
||||
isDisabled,
|
||||
}));
|
||||
} else {
|
||||
setState(state => ({
|
||||
...state,
|
||||
labelmapList: [],
|
||||
segmentList: [],
|
||||
isDisabled,
|
||||
}));
|
||||
}
|
||||
}
|
||||
}, [studies, viewports, activeIndex, getLabelmapList, getSegmentList, state.selectedSegmentation]);
|
||||
}, [viewports, activeIndex, state.isLoading]);
|
||||
|
||||
useEffect(() => {
|
||||
refreshSegmentations();
|
||||
}, [
|
||||
viewports,
|
||||
activeIndex,
|
||||
isOpen,
|
||||
state.selectedSegmentation,
|
||||
activeContexts,
|
||||
state.isLoading,
|
||||
]);
|
||||
|
||||
/* Handle open/closed panel behaviour */
|
||||
useEffect(() => {
|
||||
setState(state => ({
|
||||
...state,
|
||||
showSegSettings: state.showSegSettings && !isOpen,
|
||||
showSegmentationSettings: state.showSegmentationSettings && !isOpen,
|
||||
}));
|
||||
}, [isOpen]);
|
||||
|
||||
@@ -165,12 +228,12 @@ const SegmentationPanel = ({
|
||||
title: displayDescription,
|
||||
description: displayDate,
|
||||
onClick: async () => {
|
||||
const activatedLabelmapIndex = await _setActiveLabelmap(
|
||||
const activatedLabelmapIndex = await setActiveLabelmap(
|
||||
activeViewport,
|
||||
studies,
|
||||
displaySet,
|
||||
firstImageId,
|
||||
brushStackState.activeLabelmapIndex
|
||||
() => onSelectedSegmentationChange(),
|
||||
onDisplaySetLoadFailure
|
||||
);
|
||||
updateState('selectedSegmentation', activatedLabelmapIndex);
|
||||
},
|
||||
@@ -249,66 +312,111 @@ const SegmentationPanel = ({
|
||||
: prev;
|
||||
});
|
||||
|
||||
const enabledElements = cornerstone.getEnabledElements();
|
||||
const element = enabledElements[activeIndex].element;
|
||||
const toolState = cornerstoneTools.getToolState(element, 'stack');
|
||||
if (isCornerstone()) {
|
||||
const enabledElements = cornerstone.getEnabledElements();
|
||||
const element = enabledElements[activeIndex].element;
|
||||
const toolState = cornerstoneTools.getToolState(element, 'stack');
|
||||
|
||||
if (!toolState) {
|
||||
return;
|
||||
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
|
||||
);
|
||||
|
||||
onSegmentItemClick({
|
||||
StudyInstanceUID,
|
||||
SOPInstanceUID,
|
||||
frameIndex,
|
||||
activeViewportIndex: activeIndex,
|
||||
});
|
||||
}
|
||||
|
||||
const imageIds = toolState.data[0].imageIds;
|
||||
const imageId = imageIds[closest];
|
||||
const frameIndex = imageIds.indexOf(imageId);
|
||||
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 SOPInstanceUID = cornerstone.metaData.get(
|
||||
'SOPInstanceUID',
|
||||
imageId
|
||||
);
|
||||
const StudyInstanceUID = cornerstone.metaData.get(
|
||||
'StudyInstanceUID',
|
||||
imageId
|
||||
);
|
||||
const frame = labelmap3D.labelmaps2D[closest];
|
||||
|
||||
onSegItemClick({
|
||||
StudyInstanceUID,
|
||||
SOPInstanceUID,
|
||||
frameIndex,
|
||||
activeViewportIndex: activeIndex,
|
||||
});
|
||||
onSegmentItemClick({
|
||||
studies,
|
||||
StudyInstanceUID: currentDisplaySet.StudyInstanceUID,
|
||||
displaySetInstanceUID: currentDisplaySet.displaySetInstanceUID,
|
||||
SOPClassUID: viewports[activeIndex].sopClassUIDs[0],
|
||||
SOPInstanceUID: currentDisplaySet.SOPInstanceUID,
|
||||
segmentNumber,
|
||||
frameIndex: closest,
|
||||
frame,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const enabledElements = cornerstone.getEnabledElements();
|
||||
const enabledElementViewport = enabledElements[activeIndex];
|
||||
const isSegmentVisible = () => {
|
||||
return !labelmap3D.segmentsHidden[segmentIndex];
|
||||
};
|
||||
|
||||
let isVisible = true;
|
||||
if (enabledElementViewport) {
|
||||
const element = enabledElementViewport.element;
|
||||
const module = cornerstoneTools.getModule('segmentation');
|
||||
isVisible = module.getters.isSegmentVisible(
|
||||
element,
|
||||
segmentNumber,
|
||||
brushStackState.activeLabelmapIndex
|
||||
);
|
||||
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}
|
||||
onClick={() => setCurrentSelectedSegment()}
|
||||
label={segmentLabel}
|
||||
index={segmentNumber}
|
||||
color={color}
|
||||
visible={isVisible}
|
||||
onVisibilityChange={() => {
|
||||
const element = enabledElements[activeIndex].element;
|
||||
module.setters.toggleSegmentVisibility(
|
||||
element,
|
||||
segmentNumber,
|
||||
brushStackState.activeLabelmapIndex
|
||||
);
|
||||
refreshViewport();
|
||||
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();
|
||||
}}
|
||||
/>
|
||||
);
|
||||
@@ -323,9 +431,24 @@ const SegmentationPanel = ({
|
||||
* Show default name
|
||||
*/
|
||||
},
|
||||
[activeIndex, onSegItemClick, state.selectedSegment]
|
||||
[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(() => {
|
||||
updateState('cachedSegmentsProperties', []);
|
||||
}, [activeContexts]);
|
||||
|
||||
const updateState = (field, value) => {
|
||||
setState(state => ({ ...state, [field]: value }));
|
||||
};
|
||||
@@ -374,7 +497,6 @@ const SegmentationPanel = ({
|
||||
};
|
||||
|
||||
const updateConfiguration = newConfiguration => {
|
||||
/* Supported configuration */
|
||||
configuration.renderFill = newConfiguration.renderFill;
|
||||
configuration.renderOutline = newConfiguration.renderOutline;
|
||||
configuration.shouldRenderInactiveLabelmaps =
|
||||
@@ -384,26 +506,35 @@ const SegmentationPanel = ({
|
||||
configuration.outlineWidth = newConfiguration.outlineWidth;
|
||||
configuration.fillAlphaInactive = newConfiguration.fillAlphaInactive;
|
||||
configuration.outlineAlphaInactive = newConfiguration.outlineAlphaInactive;
|
||||
refreshViewport();
|
||||
onConfigurationChange(newConfiguration);
|
||||
refreshViewports();
|
||||
};
|
||||
|
||||
if (state.showSegSettings) {
|
||||
const disabledConfigurationFields = [
|
||||
'outlineAlpha',
|
||||
'shouldRenderInactiveLabelmaps',
|
||||
];
|
||||
if (state.showSegmentationSettings) {
|
||||
return (
|
||||
<SegmentationSettings
|
||||
disabledFields={isVTK() ? disabledConfigurationFields : []}
|
||||
configuration={configuration}
|
||||
onBack={() => updateState('showSegSettings', false)}
|
||||
onBack={() => updateState('showSegmentationSettings', false)}
|
||||
onChange={updateConfiguration}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div className="dcmseg-segmentation-panel">
|
||||
<div
|
||||
className={`dcmseg-segmentation-panel ${state.isDisabled &&
|
||||
'disabled'}`}
|
||||
>
|
||||
<Icon
|
||||
className="cog-icon"
|
||||
name="cog"
|
||||
width="25px"
|
||||
height="25px"
|
||||
onClick={() => updateState('showSegSettings', true)}
|
||||
onClick={() => updateState('showSegmentationSettings', true)}
|
||||
/>
|
||||
{false && (
|
||||
<form className="selector-form">
|
||||
@@ -424,7 +555,9 @@ const SegmentationPanel = ({
|
||||
<h3>Segmentations</h3>
|
||||
<div className="segmentations">
|
||||
<SegmentationSelect
|
||||
value={state.labelmapList.find(i => i.value === state.selectedSegmentation)}
|
||||
value={state.labelmapList.find(
|
||||
i => i.value === state.selectedSegmentation
|
||||
)}
|
||||
formatOptionLabel={SegmentationItem}
|
||||
options={state.labelmapList}
|
||||
/>
|
||||
@@ -494,43 +627,6 @@ const _getReferencedSegDisplaysets = (StudyInstanceUID, SeriesInstanceUID) => {
|
||||
return referencedDisplaysets;
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param {*} viewportSpecificData
|
||||
* @param {*} studies
|
||||
* @param {*} displaySet
|
||||
* @param {*} firstImageId
|
||||
* @param {*} activeLabelmapIndex
|
||||
* @returns
|
||||
*/
|
||||
const _setActiveLabelmap = async (
|
||||
viewportSpecificData,
|
||||
studies,
|
||||
displaySet,
|
||||
firstImageId,
|
||||
activeLabelmapIndex
|
||||
) => {
|
||||
if (displaySet.labelmapIndex === activeLabelmapIndex) {
|
||||
log.warn(`${activeLabelmapIndex} is already the active labelmap`);
|
||||
return displaySet.labelmapIndex;
|
||||
}
|
||||
|
||||
if (!displaySet.isLoaded) {
|
||||
// What props does this expect `viewportSpecificData` to have?
|
||||
// TODO: Should this return the `labelmapIndex`?
|
||||
await displaySet.load(viewportSpecificData, studies);
|
||||
}
|
||||
|
||||
const { state } = cornerstoneTools.getModule('segmentation');
|
||||
const brushStackState = state.series[firstImageId];
|
||||
brushStackState.activeLabelmapIndex = displaySet.labelmapIndex;
|
||||
|
||||
refreshViewport();
|
||||
|
||||
return displaySet.labelmapIndex;
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} firstImageId
|
||||
@@ -550,7 +646,7 @@ const _setActiveSegment = (firstImageId, segmentIndex, activeSegmentIndex) => {
|
||||
brushStackState.labelmaps3D[brushStackState.activeLabelmapIndex];
|
||||
labelmap3D.activeSegmentIndex = segmentIndex;
|
||||
|
||||
refreshViewport();
|
||||
refreshViewports();
|
||||
|
||||
return segmentIndex;
|
||||
};
|
||||
|
||||
+26
-22
@@ -4,7 +4,7 @@ import { Range } from '@ohif/ui';
|
||||
|
||||
import './SegmentationSettings.css';
|
||||
|
||||
const SegmentationSettings = ({ configuration, onBack, onChange }) => {
|
||||
const SegmentationSettings = ({ configuration, onBack, onChange, disabledFields = [] }) => {
|
||||
const [state, setState] = useState({
|
||||
renderFill: configuration.renderFill,
|
||||
renderOutline: configuration.renderOutline,
|
||||
@@ -70,28 +70,32 @@ const SegmentationSettings = ({ configuration, onBack, onChange }) => {
|
||||
/>
|
||||
{state.renderOutline && (
|
||||
<>
|
||||
<CustomRange
|
||||
value={state.outlineAlpha * 100}
|
||||
label="Opacity"
|
||||
showPercentage
|
||||
step={1}
|
||||
min={0}
|
||||
max={100}
|
||||
onChange={event => save('outlineAlpha', toFloat(event.target.value))}
|
||||
/>
|
||||
<CustomRange
|
||||
value={state.outlineWidth}
|
||||
label="Width"
|
||||
showValue
|
||||
step={1}
|
||||
min={0}
|
||||
max={5}
|
||||
onChange={event => save('outlineWidth', parseInt(event.target.value))}
|
||||
/>
|
||||
{!disabledFields.includes('outlineAlpha') && (
|
||||
<CustomRange
|
||||
value={state.outlineAlpha * 100}
|
||||
label="Opacity"
|
||||
showPercentage
|
||||
step={1}
|
||||
min={0}
|
||||
max={100}
|
||||
onChange={event => save('outlineAlpha', toFloat(event.target.value))}
|
||||
/>
|
||||
)}
|
||||
{!disabledFields.includes('outlineWidth') && (
|
||||
<CustomRange
|
||||
value={state.outlineWidth}
|
||||
label="Width"
|
||||
showValue
|
||||
step={1}
|
||||
min={0}
|
||||
max={5}
|
||||
onChange={event => save('outlineWidth', parseInt(event.target.value))}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{(state.renderFill || state.renderOutline) && (
|
||||
{(state.renderFill || state.renderOutline) && !disabledFields.includes('shouldRenderInactiveLabelmaps') && (
|
||||
<div
|
||||
className="settings-group"
|
||||
style={{ marginBottom: state.shouldRenderInactiveLabelmaps ? 15 : 0 }}
|
||||
@@ -103,7 +107,7 @@ const SegmentationSettings = ({ configuration, onBack, onChange }) => {
|
||||
/>
|
||||
{state.shouldRenderInactiveLabelmaps && (
|
||||
<>
|
||||
{state.renderFill && (
|
||||
{state.renderFill && !disabledFields.includes('fillAlphaInactive') && (
|
||||
<CustomRange
|
||||
label="Fill Opacity"
|
||||
showPercentage
|
||||
@@ -114,7 +118,7 @@ const SegmentationSettings = ({ configuration, onBack, onChange }) => {
|
||||
onChange={event => save('fillAlphaInactive', toFloat(event.target.value))}
|
||||
/>
|
||||
)}
|
||||
{state.renderOutline && (
|
||||
{state.renderOutline && !disabledFields.includes('outlineAlphaInactive') && (
|
||||
<CustomRange
|
||||
label="Outline Opacity"
|
||||
showPercentage
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
import { MODULE_TYPES, utils } from '@ohif/core';
|
||||
import loadSegmentation from './loadSegmentation';
|
||||
import getSourceDisplaySet from './getSourceDisplaySet';
|
||||
|
||||
// TODO: Should probably use dcmjs for this
|
||||
const SOP_CLASS_UIDS = {
|
||||
DICOM_SEG: '1.2.840.10008.5.1.4.1.1.66.4',
|
||||
};
|
||||
|
||||
const sopClassUIDs = Object.values(SOP_CLASS_UIDS);
|
||||
|
||||
export default function getSopClassHandlerModule({ servicesManager }) {
|
||||
// TODO: Handle the case where there is more than one SOP Class Handler for the
|
||||
// same SOP Class.
|
||||
return {
|
||||
id: 'OHIFDicomSegSopClassHandler',
|
||||
type: MODULE_TYPES.SOP_CLASS_HANDLER,
|
||||
sopClassUIDs,
|
||||
getDisplaySetFromSeries: function(
|
||||
series,
|
||||
study,
|
||||
dicomWebClient,
|
||||
authorizationHeaders
|
||||
) {
|
||||
const instance = series.getFirstInstance();
|
||||
const metadata = instance.getData().metadata;
|
||||
|
||||
const {
|
||||
SeriesDate,
|
||||
SeriesTime,
|
||||
SeriesDescription,
|
||||
FrameOfReferenceUID,
|
||||
SOPInstanceUID,
|
||||
SeriesInstanceUID,
|
||||
StudyInstanceUID,
|
||||
} = metadata;
|
||||
|
||||
const segDisplaySet = {
|
||||
Modality: 'SEG',
|
||||
displaySetInstanceUID: utils.guid(),
|
||||
wadoRoot: study.getData().wadoRoot,
|
||||
wadoUri: instance.getData().wadouri,
|
||||
SOPInstanceUID,
|
||||
SeriesInstanceUID,
|
||||
StudyInstanceUID,
|
||||
FrameOfReferenceUID,
|
||||
authorizationHeaders,
|
||||
metadata,
|
||||
isDerived: true,
|
||||
referencedDisplaySetUID: null, // Assigned when loaded.
|
||||
labelmapIndex: null, // Assigned when loaded.
|
||||
isLoaded: false,
|
||||
SeriesDate,
|
||||
SeriesTime,
|
||||
SeriesDescription,
|
||||
};
|
||||
|
||||
segDisplaySet.getSourceDisplaySet = function(studies) {
|
||||
return getSourceDisplaySet(studies, segDisplaySet);
|
||||
};
|
||||
|
||||
segDisplaySet.load = function(referencedDisplaySet, studies) {
|
||||
return loadSegmentation(segDisplaySet, referencedDisplaySet, studies);
|
||||
};
|
||||
|
||||
return segDisplaySet;
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import setActiveLabelmap from './utils/setActiveLabelMap';
|
||||
|
||||
export default function getSourceDisplaySet(studies, segDisplaySet) {
|
||||
const referencedDisplaySet = _getReferencedDisplaySet(segDisplaySet, studies);
|
||||
|
||||
setActiveLabelmap(referencedDisplaySet, studies, segDisplaySet);
|
||||
|
||||
return referencedDisplaySet;
|
||||
}
|
||||
|
||||
const _getReferencedDisplaySet = (segDisplaySet, studies) => {
|
||||
let allDisplaySets = [];
|
||||
|
||||
studies.forEach(study => {
|
||||
allDisplaySets = allDisplaySets.concat(study.displaySets);
|
||||
});
|
||||
|
||||
const otherDisplaySets = allDisplaySets.filter(
|
||||
ds => ds.displaySetInstanceUID !== segDisplaySet.displaySetInstanceUID
|
||||
);
|
||||
|
||||
const ReferencedSeriesSequence = Array.isArray(
|
||||
segDisplaySet.metadata.ReferencedSeriesSequence
|
||||
)
|
||||
? segDisplaySet.metadata.ReferencedSeriesSequence
|
||||
: [segDisplaySet.metadata.ReferencedSeriesSequence];
|
||||
|
||||
const referencedSeriesInstanceUIDs = ReferencedSeriesSequence.map(
|
||||
ReferencedSeries => ReferencedSeries.SeriesInstanceUID
|
||||
);
|
||||
|
||||
const referencedDisplaySet = otherDisplaySets.find(ds =>
|
||||
referencedSeriesInstanceUIDs.includes(ds.SeriesInstanceUID)
|
||||
);
|
||||
|
||||
return referencedDisplaySet;
|
||||
};
|
||||
@@ -2,7 +2,7 @@ import React from 'react';
|
||||
|
||||
import init from './init.js';
|
||||
import toolbarModule from './toolbarModule.js';
|
||||
import sopClassHandlerModule from './OHIFDicomSegSopClassHandler.js';
|
||||
import getSopClassHandlerModule from './getOHIFDicomSegSopClassHandler.js';
|
||||
import SegmentationPanel from './components/SegmentationPanel/SegmentationPanel.js';
|
||||
|
||||
export default {
|
||||
@@ -23,14 +23,57 @@ export default {
|
||||
getToolbarModule({ servicesManager }) {
|
||||
return toolbarModule;
|
||||
},
|
||||
getPanelModule({ commandsManager }) {
|
||||
getPanelModule({ commandsManager, api, servicesManager }) {
|
||||
const { UINotificationService } = servicesManager.services;
|
||||
|
||||
const ExtendedSegmentationPanel = props => {
|
||||
const segItemClickHandler = segData => {
|
||||
commandsManager.runCommand('jumpToImage', segData);
|
||||
const { activeContexts } = api.hooks.useAppContext();
|
||||
|
||||
const onDisplaySetLoadFailureHandler = error => {
|
||||
UINotificationService.show({
|
||||
title: 'DICOM Segmentation Loader',
|
||||
message: error.message,
|
||||
type: 'error',
|
||||
autoClose: false,
|
||||
});
|
||||
};
|
||||
|
||||
const segmentItemClickHandler = data => {
|
||||
commandsManager.runCommand('jumpToImage', data);
|
||||
commandsManager.runCommand('jumpToSlice', data);
|
||||
};
|
||||
|
||||
const onSegmentVisibilityChangeHandler = (segmentNumber, visible) => {
|
||||
commandsManager.runCommand('setSegmentConfiguration', {
|
||||
segmentNumber,
|
||||
visible,
|
||||
});
|
||||
};
|
||||
|
||||
const onConfigurationChangeHandler = configuration => {
|
||||
commandsManager.runCommand('setSegmentationConfiguration', {
|
||||
globalOpacity: configuration.fillAlpha,
|
||||
outlineThickness: configuration.outlineWidth,
|
||||
renderOutline: configuration.renderOutline,
|
||||
visible: configuration.renderFill,
|
||||
});
|
||||
};
|
||||
|
||||
const onSelectedSegmentationChangeHandler = () => {
|
||||
commandsManager.runCommand('requestNewSegmentation');
|
||||
};
|
||||
|
||||
return (
|
||||
<SegmentationPanel {...props} onSegItemClick={segItemClickHandler} />
|
||||
<SegmentationPanel
|
||||
{...props}
|
||||
activeContexts={activeContexts}
|
||||
contexts={api.contexts}
|
||||
onSegmentItemClick={segmentItemClickHandler}
|
||||
onSegmentVisibilityChange={onSegmentVisibilityChangeHandler}
|
||||
onConfigurationChange={onConfigurationChangeHandler}
|
||||
onSelectedSegmentationChange={onSelectedSegmentationChangeHandler}
|
||||
onDisplaySetLoadFailure={onDisplaySetLoadFailureHandler}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -72,7 +115,5 @@ export default {
|
||||
defaultContext: ['VIEWER'],
|
||||
};
|
||||
},
|
||||
getSopClassHandlerModule({ servicesManager }) {
|
||||
return sopClassHandlerModule;
|
||||
},
|
||||
getSopClassHandlerModule,
|
||||
};
|
||||
@@ -34,28 +34,59 @@ export default async function loadSegmentation(
|
||||
referencedDisplaySet.SeriesInstanceUID
|
||||
);
|
||||
|
||||
const results = _parseSeg(segArrayBuffer, imageIds);
|
||||
return new Promise((resolve, reject) => {
|
||||
let results;
|
||||
|
||||
if (!results) {
|
||||
throw new Error('Fractional segmentations are not yet supported');
|
||||
}
|
||||
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');
|
||||
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]);
|
||||
// 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
|
||||
);
|
||||
setters.labelmap3DByFirstImageId(
|
||||
imageIds[0],
|
||||
labelmapBuffer,
|
||||
labelmapIndex,
|
||||
segMetadata,
|
||||
imageIds.length,
|
||||
segmentsOnFrame,
|
||||
colorLUTIndex
|
||||
);
|
||||
|
||||
segDisplaySet.labelmapIndex = labelmapIndex;
|
||||
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) {
|
||||
@@ -79,6 +110,69 @@ function _getNextLabelmapIndex(firstImageId) {
|
||||
return labelmapIndex;
|
||||
}
|
||||
|
||||
function _makeColorLUTAndGetIndex(segMetadata) {
|
||||
const { setters, state } = cornerstoneTools.getModule('segmentation');
|
||||
const { colorLutTables } = state;
|
||||
const colorLUTIndex = _getNextColorLUTIndex();
|
||||
|
||||
const { data } = segMetadata;
|
||||
|
||||
if (
|
||||
!data.some(
|
||||
segment =>
|
||||
segment &&
|
||||
(segment.ROIDisplayColor || segment.RecommendedDisplayCIELabValue)
|
||||
)
|
||||
) {
|
||||
// Use default cornerstoneTools colorLUT.
|
||||
return 0;
|
||||
}
|
||||
|
||||
const colorLUT = [];
|
||||
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
const segment = data[i];
|
||||
if (!segment) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const { ROIDisplayColor, RecommendedDisplayCIELabValue } = segment;
|
||||
|
||||
if (RecommendedDisplayCIELabValue) {
|
||||
const rgb = dcmjs.data.Colors.dicomlab2RGB(
|
||||
RecommendedDisplayCIELabValue
|
||||
).map(x => Math.round(x * 255));
|
||||
|
||||
colorLUT[i] = [...rgb, 255];
|
||||
} else if (ROIDisplayColor) {
|
||||
colorLUT[i] = [...ROIDisplayColor, 255];
|
||||
} else {
|
||||
colorLUT[i] = [...colorLutTables[0][i]];
|
||||
}
|
||||
}
|
||||
|
||||
colorLUT.shift();
|
||||
setters.colorLUT(colorLUTIndex, colorLUT);
|
||||
|
||||
return colorLUTIndex;
|
||||
}
|
||||
|
||||
function _getNextColorLUTIndex() {
|
||||
const { state } = cornerstoneTools.getModule('segmentation');
|
||||
const { colorLutTables } = state;
|
||||
|
||||
let colorLUTIndex = colorLutTables.length;
|
||||
|
||||
for (let i = 0; i < colorLutTables.length; i++) {
|
||||
if (!colorLutTables[i]) {
|
||||
colorLUTIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return colorLUTIndex;
|
||||
}
|
||||
|
||||
function _parseSeg(arrayBuffer, imageIds) {
|
||||
return dcmjs.adapters.Cornerstone.Segmentation.generateToolState(
|
||||
imageIds,
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
export default function refreshViewports() {
|
||||
cornerstone.getEnabledElements().forEach(enabledElement => {
|
||||
cornerstone.updateImage(enabledElement.element);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
import { utils, log } from '@ohif/core';
|
||||
import cornerstoneTools from 'cornerstone-tools';
|
||||
import refreshViewports from './refreshViewports';
|
||||
|
||||
const { studyMetadataManager } = utils;
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param {*} viewportSpecificData
|
||||
* @param {*} studies
|
||||
* @param {*} displaySet
|
||||
* @param {*} firstImageId
|
||||
* @param {*} activeLabelmapIndex
|
||||
* @returns
|
||||
*/
|
||||
export default async function setActiveLabelmap(
|
||||
referencedDisplaySet,
|
||||
studies,
|
||||
displaySet,
|
||||
callback = () => {},
|
||||
onDisplaySetLoadFailure = err => {
|
||||
throw new Error(err.message);
|
||||
}
|
||||
) {
|
||||
const studyMetadata = studyMetadataManager.get(
|
||||
referencedDisplaySet.StudyInstanceUID
|
||||
);
|
||||
const firstImageId = studyMetadata.getFirstImageId(
|
||||
referencedDisplaySet.displaySetInstanceUID
|
||||
);
|
||||
|
||||
let { state } = cornerstoneTools.getModule('segmentation');
|
||||
|
||||
let brushStackState = state.series[firstImageId];
|
||||
const activeLabelmapIndex = brushStackState
|
||||
? brushStackState.activeLabelmapIndex
|
||||
: undefined;
|
||||
|
||||
if (displaySet.labelmapIndex === activeLabelmapIndex) {
|
||||
log.warn(`${activeLabelmapIndex} is already the active labelmap`);
|
||||
return displaySet.labelmapIndex;
|
||||
}
|
||||
|
||||
if (!displaySet.isLoaded) {
|
||||
const loadPromise = displaySet.load(referencedDisplaySet, studies);
|
||||
|
||||
loadPromise.catch(error => {
|
||||
onDisplaySetLoadFailure(error);
|
||||
|
||||
// Return old index.
|
||||
return activeLabelmapIndex;
|
||||
});
|
||||
|
||||
await loadPromise;
|
||||
}
|
||||
|
||||
// This might have just been created, so need to use the non-cached value.
|
||||
state = cornerstoneTools.getModule('segmentation').state;
|
||||
brushStackState = state.series[firstImageId];
|
||||
brushStackState.activeLabelmapIndex = displaySet.labelmapIndex;
|
||||
|
||||
refreshViewports();
|
||||
callback();
|
||||
|
||||
return displaySet.labelmapIndex;
|
||||
}
|
||||
@@ -3,6 +3,143 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.6.10](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.6.9...@ohif/extension-vtk@1.6.10) (2020-08-10)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.6.9](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.6.8...@ohif/extension-vtk@1.6.9) (2020-07-23)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.6.8](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.6.7...@ohif/extension-vtk@1.6.8) (2020-07-22)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.6.7](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.6.6...@ohif/extension-vtk@1.6.7) (2020-07-13)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.6.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.6.5...@ohif/extension-vtk@1.6.6) (2020-07-13)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.6.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.6.4...@ohif/extension-vtk@1.6.5) (2020-07-13)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.6.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.6.3...@ohif/extension-vtk@1.6.4) (2020-06-18)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.6.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.6.2...@ohif/extension-vtk@1.6.3) (2020-06-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 🐛 Disable seg panel when data for seg unavailable ([#1732](https://github.com/OHIF/Viewers/issues/1732)) ([698e900](https://github.com/OHIF/Viewers/commit/698e900b85121d3c2a46747c443ef69fb7a8c95b)), closes [#1728](https://github.com/OHIF/Viewers/issues/1728)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.6.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.6.1...@ohif/extension-vtk@1.6.2) (2020-06-05)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.6.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.6.0...@ohif/extension-vtk@1.6.1) (2020-06-04)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [1.6.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.5.6...@ohif/extension-vtk@1.6.0) (2020-06-04)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* 🎸 1729 - error boundary wrapper ([#1764](https://github.com/OHIF/Viewers/issues/1764)) ([c02b232](https://github.com/OHIF/Viewers/commit/c02b232b0cc24f38af5d5e3831d987d048e60ada))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.5.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.5.5...@ohif/extension-vtk@1.5.6) (2020-05-14)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.5.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.5.4...@ohif/extension-vtk@1.5.5) (2020-05-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 🐛 Fix seg color load ([#1724](https://github.com/OHIF/Viewers/issues/1724)) ([c4f84b1](https://github.com/OHIF/Viewers/commit/c4f84b1174d04ba84d37ed89b6d7ab541be28181))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.5.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.5.3...@ohif/extension-vtk@1.5.4) (2020-05-06)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.5.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.5.2...@ohif/extension-vtk@1.5.3) (2020-05-04)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.5.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.5.1...@ohif/extension-vtk@1.5.2) (2020-05-04)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.5.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.5.0...@ohif/extension-vtk@1.5.1) (2020-04-28)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/extension-vtk",
|
||||
"version": "1.5.1",
|
||||
"version": "1.6.10",
|
||||
"description": "OHIF extension for VTK.js",
|
||||
"author": "OHIF",
|
||||
"license": "MIT",
|
||||
@@ -33,9 +33,9 @@
|
||||
"@ohif/i18n": "^0.50.0",
|
||||
"@ohif/ui": "^0.50.0",
|
||||
"cornerstone-core": "^2.2.8",
|
||||
"cornerstone-tools": "4.12.5",
|
||||
"cornerstone-tools": "4.15.1",
|
||||
"cornerstone-wado-image-loader": "^3.1.0",
|
||||
"dcmjs": "^0.12.2",
|
||||
"dcmjs": "0.16.0",
|
||||
"dicom-parser": "^1.8.3",
|
||||
"i18next": "^17.0.3",
|
||||
"i18next-browser-languagedetector": "^3.0.1",
|
||||
@@ -50,12 +50,12 @@
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.5.5",
|
||||
"lodash.throttle": "^4.1.1",
|
||||
"react-vtkjs-viewport": "^0.8.3"
|
||||
"react-vtkjs-viewport": "^0.9.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ohif/core": "^2.9.1",
|
||||
"@ohif/ui": "^1.4.3",
|
||||
"cornerstone-tools": "4.12.5",
|
||||
"@ohif/core": "^2.9.14",
|
||||
"@ohif/ui": "^1.5.3",
|
||||
"cornerstone-tools": "4.15.1",
|
||||
"cornerstone-wado-image-loader": "^3.1.0",
|
||||
"dicom-parser": "^1.8.3",
|
||||
"gh-pages": "^2.0.1",
|
||||
|
||||
@@ -32,16 +32,16 @@ class LoadingIndicator extends PureComponent {
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="imageViewerLoadingIndicator loadingIndicator">
|
||||
<div className="indicatorContents">
|
||||
<p>
|
||||
{this.props.t('Reformatting')}...
|
||||
<i className="fa fa-spin fa-circle-o-notch fa-fw" />
|
||||
{percComplete}
|
||||
</p>
|
||||
<div className="imageViewerLoadingIndicator loadingIndicator">
|
||||
<div className="indicatorContents">
|
||||
<p>
|
||||
{this.props.t('Loading...')}
|
||||
<i className="fa fa-spin fa-circle-o-notch fa-fw" />
|
||||
{percComplete}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ class OHIFVTKViewport extends Component {
|
||||
state = {
|
||||
volumes: null,
|
||||
paintFilterLabelMapImageData: null,
|
||||
paintFilterBackgroundImageData: null,
|
||||
paintFilterBackgroundImageData: null
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
@@ -69,7 +69,7 @@ class OHIFVTKViewport extends Component {
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
onScroll: () => {},
|
||||
onScroll: () => { },
|
||||
};
|
||||
|
||||
static id = 'OHIFVTKViewport';
|
||||
@@ -156,6 +156,10 @@ class OHIFVTKViewport extends Component {
|
||||
const { activeLabelmapIndex } = brushStackState;
|
||||
const labelmap3D = brushStackState.labelmaps3D[activeLabelmapIndex];
|
||||
|
||||
this.segmentsDefaultProperties = labelmap3D.segmentsHidden.map(isHidden => {
|
||||
return { visible: !isHidden };
|
||||
});
|
||||
|
||||
const vtkLabelmapID = `${firstImageId}_${activeLabelmapIndex}`;
|
||||
|
||||
if (labelmapCache[vtkLabelmapID]) {
|
||||
@@ -339,13 +343,13 @@ class OHIFVTKViewport extends Component {
|
||||
this.setStateFromProps();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
const { displaySet } = this.props.viewportData;
|
||||
const prevDisplaySet = prevProps.viewportData.displaySet;
|
||||
|
||||
if (
|
||||
displaySet.displaySetInstanceUID !==
|
||||
prevDisplaySet.displaySetInstanceUID ||
|
||||
prevDisplaySet.displaySetInstanceUID ||
|
||||
displaySet.SOPInstanceUID !== prevDisplaySet.SOPInstanceUID ||
|
||||
displaySet.frameIndex !== prevDisplaySet.frameIndex
|
||||
) {
|
||||
@@ -405,10 +409,6 @@ class OHIFVTKViewport extends Component {
|
||||
|
||||
const style = { width: '100%', height: '100%', position: 'relative' };
|
||||
|
||||
const visible = configuration.renderFill || configuration.renderOutline;
|
||||
const opacity = configuration.fillAlpha;
|
||||
const outlineThickness = configuration.outlineThickness;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div style={style}>
|
||||
@@ -428,10 +428,14 @@ class OHIFVTKViewport extends Component {
|
||||
dataDetails={this.state.dataDetails}
|
||||
labelmapRenderingOptions={{
|
||||
colorLUT: this.state.labelmapColorLUT,
|
||||
globalOpacity: opacity,
|
||||
visible,
|
||||
outlineThickness,
|
||||
renderOutline: true,
|
||||
globalOpacity: configuration.fillAlpha,
|
||||
visible: configuration.renderFill,
|
||||
outlineThickness: configuration.outlineWidth,
|
||||
renderOutline: configuration.renderOutline,
|
||||
segmentsDefaultProperties: this.segmentsDefaultProperties,
|
||||
onNewSegmentationRequested: () => {
|
||||
this.setStateFromProps();
|
||||
}
|
||||
}}
|
||||
onScroll={this.props.onScroll}
|
||||
/>
|
||||
|
||||
@@ -5,10 +5,13 @@ import {
|
||||
vtkInteractorStyleMPRRotate,
|
||||
vtkSVGCrosshairsWidget,
|
||||
} from 'react-vtkjs-viewport';
|
||||
|
||||
import { getImageData } from 'react-vtkjs-viewport';
|
||||
import { vec3 } from 'gl-matrix';
|
||||
import setMPRLayout from './utils/setMPRLayout.js';
|
||||
import setViewportToVTK from './utils/setViewportToVTK.js';
|
||||
import Constants from 'vtk.js/Sources/Rendering/Core/VolumeMapper/Constants.js';
|
||||
import OHIFVTKViewport from './OHIFVTKViewport';
|
||||
import vtkCoordinate from 'vtk.js/Sources/Rendering/Core/Coordinate';
|
||||
|
||||
const { BlendMode } = Constants;
|
||||
|
||||
@@ -34,7 +37,6 @@ const commandsModule = ({ commandsManager }) => {
|
||||
}
|
||||
|
||||
const displaySet = viewportSpecificData[activeViewportIndex];
|
||||
|
||||
let api;
|
||||
if (!api) {
|
||||
try {
|
||||
@@ -99,6 +101,20 @@ const commandsModule = ({ commandsManager }) => {
|
||||
});
|
||||
}
|
||||
|
||||
const _convertModelToWorldSpace = (position, vtkImageData) => {
|
||||
const indexToWorld = vtkImageData.getIndexToWorld();
|
||||
const pos = vec3.create();
|
||||
|
||||
position[0] += 0.5; /* Move to the centre of the voxel. */
|
||||
position[1] += 0.5; /* Move to the centre of the voxel. */
|
||||
position[2] += 0.5; /* Move to the centre of the voxel. */
|
||||
|
||||
vec3.set(pos, position[0], position[1], position[2]);
|
||||
vec3.transformMat4(pos, pos, indexToWorld);
|
||||
|
||||
return pos;
|
||||
};
|
||||
|
||||
const actions = {
|
||||
getVtkApis: ({ index }) => {
|
||||
return apis[index];
|
||||
@@ -124,6 +140,119 @@ const commandsModule = ({ commandsManager }) => {
|
||||
|
||||
_setView(api, [0, 1, 0], [0, 0, 1]);
|
||||
},
|
||||
requestNewSegmentation: async ({ viewports }) => {
|
||||
const allViewports = Object.values(viewports.viewportSpecificData);
|
||||
const promises = allViewports.map(async (viewport, viewportIndex) => {
|
||||
let api = apis[viewportIndex];
|
||||
|
||||
if (!api) {
|
||||
api = await _getActiveViewportVTKApi(viewports);
|
||||
apis[viewportIndex] = api;
|
||||
}
|
||||
|
||||
api.requestNewSegmentation();
|
||||
api.updateImage();
|
||||
});
|
||||
await Promise.all(promises);
|
||||
},
|
||||
jumpToSlice: async ({
|
||||
viewports,
|
||||
studies,
|
||||
StudyInstanceUID,
|
||||
displaySetInstanceUID,
|
||||
SOPClassUID,
|
||||
SOPInstanceUID,
|
||||
segmentNumber,
|
||||
frameIndex,
|
||||
frame,
|
||||
done = () => { }
|
||||
}) => {
|
||||
let api = apis[viewports.activeViewportIndex];
|
||||
|
||||
if (!api) {
|
||||
api = await _getActiveViewportVTKApi(viewports);
|
||||
apis[viewports.activeViewportIndex] = api;
|
||||
}
|
||||
|
||||
const stack = OHIFVTKViewport.getCornerstoneStack(
|
||||
studies,
|
||||
StudyInstanceUID,
|
||||
displaySetInstanceUID,
|
||||
SOPClassUID,
|
||||
SOPInstanceUID,
|
||||
frameIndex,
|
||||
);
|
||||
|
||||
const imageDataObject = getImageData(stack.imageIds, displaySetInstanceUID);
|
||||
|
||||
let pixelIndex = 0;
|
||||
let x = 0;
|
||||
let y = 0;
|
||||
let count = 0;
|
||||
|
||||
const rows = imageDataObject.dimensions[1];
|
||||
const cols = imageDataObject.dimensions[0];
|
||||
|
||||
for (let j = 0; j < rows; j++) {
|
||||
for (let i = 0; i < cols; i++) {
|
||||
// [i, j] =
|
||||
const pixel = frame.pixelData[pixelIndex];
|
||||
if (pixel === segmentNumber) {
|
||||
x += i;
|
||||
y += j;
|
||||
count++;
|
||||
}
|
||||
pixelIndex++;
|
||||
}
|
||||
}
|
||||
x /= count;
|
||||
y /= count;
|
||||
|
||||
const position = [x, y, frameIndex];
|
||||
const worldPos = _convertModelToWorldSpace(position, imageDataObject.vtkImageData);
|
||||
|
||||
api.svgWidgets.crosshairsWidget.moveCrosshairs(worldPos, apis);
|
||||
done();
|
||||
},
|
||||
setSegmentationConfiguration: async ({
|
||||
viewports,
|
||||
globalOpacity,
|
||||
visible,
|
||||
renderOutline,
|
||||
outlineThickness,
|
||||
}) => {
|
||||
const allViewports = Object.values(viewports.viewportSpecificData);
|
||||
const promises = allViewports.map(async (viewport, viewportIndex) => {
|
||||
let api = apis[viewportIndex];
|
||||
|
||||
if (!api) {
|
||||
api = await _getActiveViewportVTKApi(viewports);
|
||||
apis[viewportIndex] = api;
|
||||
}
|
||||
|
||||
api.setGlobalOpacity(globalOpacity);
|
||||
api.setVisibility(visible);
|
||||
api.setOutlineThickness(outlineThickness);
|
||||
api.setOutlineRendering(renderOutline);
|
||||
api.updateImage();
|
||||
});
|
||||
await Promise.all(promises);
|
||||
},
|
||||
setSegmentConfiguration: async ({ viewports, visible, segmentNumber }) => {
|
||||
const allViewports = Object.values(viewports.viewportSpecificData);
|
||||
const promises = allViewports.map(async (viewport, viewportIndex) => {
|
||||
let api = apis[viewportIndex];
|
||||
|
||||
if (!api) {
|
||||
api = await _getActiveViewportVTKApi(viewports);
|
||||
apis[viewportIndex] = api;
|
||||
}
|
||||
|
||||
api.setSegmentVisibility(segmentNumber, visible);
|
||||
api.updateImage();
|
||||
});
|
||||
await Promise.all(promises);
|
||||
},
|
||||
enableRotateTool: () => {
|
||||
apis.forEach(api => {
|
||||
const istyle = vtkInteractorStyleMPRRotate.newInstance();
|
||||
@@ -280,6 +409,26 @@ const commandsModule = ({ commandsManager }) => {
|
||||
window.vtkActions = actions;
|
||||
|
||||
const definitions = {
|
||||
requestNewSegmentation: {
|
||||
commandFn: actions.requestNewSegmentation,
|
||||
storeContexts: ['viewports'],
|
||||
options: {},
|
||||
},
|
||||
jumpToSlice: {
|
||||
commandFn: actions.jumpToSlice,
|
||||
storeContexts: ['viewports'],
|
||||
options: {},
|
||||
},
|
||||
setSegmentationConfiguration: {
|
||||
commandFn: actions.setSegmentationConfiguration,
|
||||
storeContexts: ['viewports'],
|
||||
options: {},
|
||||
},
|
||||
setSegmentConfiguration: {
|
||||
commandFn: actions.setSegmentConfiguration,
|
||||
storeContexts: ['viewports'],
|
||||
options: {},
|
||||
},
|
||||
axial: {
|
||||
commandFn: actions.axial,
|
||||
storeContexts: ['viewports'],
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import React from 'react';
|
||||
import asyncComponent from './asyncComponent.js';
|
||||
import commandsModule from './commandsModule.js';
|
||||
import toolbarModule from './toolbarModule.js';
|
||||
@@ -16,7 +17,8 @@ const vtkExtension = {
|
||||
id: 'vtk',
|
||||
|
||||
getViewportModule({ commandsManager }) {
|
||||
return withCommandsManager(OHIFVTKViewport, commandsManager);
|
||||
const ExtendedVTKViewport = props => <OHIFVTKViewport {...props} />;
|
||||
return withCommandsManager(ExtendedVTKViewport, commandsManager);
|
||||
},
|
||||
getToolbarModule() {
|
||||
return toolbarModule;
|
||||
|
||||
Executable
+26
@@ -0,0 +1,26 @@
|
||||
#!/bin/sh
|
||||
NODE_MODULES_CACHE="./node_modules"
|
||||
LERNA_CACHE="$NODE_MODULES_CACHE/lerna-cache"
|
||||
|
||||
echo "Running netlify-lerna-cache.sh"
|
||||
mkdir -p "$NODE_MODULES_CACHE/lerna-cache"
|
||||
|
||||
cache_deps() {
|
||||
PACKAGES=$(ls -1 $1)
|
||||
|
||||
for PKG in $PACKAGES
|
||||
do
|
||||
PKG_NODE_MODULES="$1/$PKG/node_modules"
|
||||
if [ -d $PKG_NODE_MODULES ];
|
||||
then
|
||||
mv $PKG_NODE_MODULES $LERNA_CACHE/$PKG
|
||||
echo "Cached node modules for $PKG"
|
||||
else
|
||||
echo "Unable to cache node modules for $PKG"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
cache_deps platform
|
||||
cache_deps extensions
|
||||
cache_deps modes
|
||||
Executable
+27
@@ -0,0 +1,27 @@
|
||||
#!/bin/sh
|
||||
NODE_MODULES_CACHE="./node_modules"
|
||||
LERNA_CACHE="$NODE_MODULES_CACHE/lerna-cache"
|
||||
|
||||
echo "Running netlify-lerna-restore.sh"
|
||||
mkdir -p "$NODE_MODULES_CACHE/lerna-cache"
|
||||
echo "$NODE_MODULES_CACHE/lerna-cache/*"
|
||||
|
||||
restore_deps() {
|
||||
PACKAGES=$(ls -1 $1)
|
||||
|
||||
for PKG in $PACKAGES
|
||||
do
|
||||
PKG_CACHE="$LERNA_CACHE/$PKG"
|
||||
if [ -d $PKG_CACHE ];
|
||||
then
|
||||
mv $PKG_CACHE $1/$PKG/node_modules
|
||||
echo "Restored node modules for $PKG"
|
||||
else
|
||||
echo "Unable to restore cache for $PKG"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
restore_deps platform
|
||||
restore_deps extensions
|
||||
restore_deps modes
|
||||
+6
-7
@@ -8,6 +8,11 @@
|
||||
# managed by CircleCI and deployed to our Google Hosting
|
||||
#
|
||||
|
||||
[build]
|
||||
base = ""
|
||||
publish = ".netlify/www/"
|
||||
command = "chmod +x .netlify/build-deploy-preview.sh && .netlify/build-deploy-preview.sh"
|
||||
|
||||
# NODE_VERSION in root `.nvmrc` takes priority
|
||||
# YARN_FLAGS: https://www.netlify.com/docs/build-gotchas/#yarn
|
||||
[build.environment]
|
||||
@@ -16,19 +21,13 @@
|
||||
YARN_VERSION = "1.17.3"
|
||||
RUBY_VERSION = "2.6.2"
|
||||
YARN_FLAGS = "--no-ignore-optional --pure-lockfile"
|
||||
NETLIFY_RESTORE = "1"
|
||||
|
||||
# Production context: all deploys from the Production branch set in your site's
|
||||
# deploy contexts will inherit these settings.
|
||||
[context.production]
|
||||
ignore = "exit 0" # Never build production; We'll let our CI do that
|
||||
|
||||
# Deploy Preview context: all deploys generated from a pull/merge request will
|
||||
# inherit these settings.
|
||||
[context.deploy-preview]
|
||||
base = ""
|
||||
publish = ".netlify/www/"
|
||||
command = "chmod +x .netlify/build-deploy-preview.sh && .netlify/build-deploy-preview.sh"
|
||||
|
||||
[[headers]]
|
||||
# Define which paths this specific [[headers]] block will cover.
|
||||
for = "/*"
|
||||
|
||||
@@ -34,6 +34,9 @@
|
||||
"see-changed": "lerna changed",
|
||||
"docs:publish": "chmod +x ./build-and-publish-docs.sh && ./build-and-publish-docs.sh",
|
||||
"release": "yarn run lerna:version && yarn run lerna:publish",
|
||||
"lerna:cache": "[ $NETLIFY_RESTORE = 1 ] && ./netlify-lerna-cache.sh || :",
|
||||
"lerna:restore": "[ $NETLIFY_RESTORE = 1 ] && ./netlify-lerna-restore.sh || :",
|
||||
"preinstall": "yarn lerna:restore",
|
||||
"lerna:version": "npx lerna version prerelease --force-publish",
|
||||
"lerna:publish": "lerna publish from-package --canary --dist-tag canary",
|
||||
"link-list": "npm ls --depth=0 --link=true"
|
||||
|
||||
@@ -3,6 +3,134 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [2.9.14](https://github.com/OHIF/Viewers/compare/@ohif/core@2.9.13...@ohif/core@2.9.14) (2020-08-10)
|
||||
|
||||
**Note:** Version bump only for package @ohif/core
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.9.13](https://github.com/OHIF/Viewers/compare/@ohif/core@2.9.12...@ohif/core@2.9.13) (2020-07-22)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Switch DICOMFileUploader to use the UIModalService ([#1904](https://github.com/OHIF/Viewers/issues/1904)) ([7772fee](https://github.com/OHIF/Viewers/commit/7772fee21ae6a65994e1251e2f1d2554b47781be))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.9.12](https://github.com/OHIF/Viewers/compare/@ohif/core@2.9.11...@ohif/core@2.9.12) (2020-07-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 🐛 - Put guards in all places that a cornerstone re-render ([#1899](https://github.com/OHIF/Viewers/issues/1899)) ([451f7ea](https://github.com/OHIF/Viewers/commit/451f7eab9258e7a193eb362e0926b13aedc4b3c9))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.9.11](https://github.com/OHIF/Viewers/compare/@ohif/core@2.9.10...@ohif/core@2.9.11) (2020-07-13)
|
||||
|
||||
**Note:** Version bump only for package @ohif/core
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.9.10](https://github.com/OHIF/Viewers/compare/@ohif/core@2.9.9...@ohif/core@2.9.10) (2020-07-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 🐛 Fix RT Panel hide/show and Fix looping load errors ([#1877](https://github.com/OHIF/Viewers/issues/1877)) ([e7cc735](https://github.com/OHIF/Viewers/commit/e7cc735c03d02eeb0d3af4ba02c15ed4f81bbec2))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.9.9](https://github.com/OHIF/Viewers/compare/@ohif/core@2.9.8...@ohif/core@2.9.9) (2020-06-18)
|
||||
|
||||
**Note:** Version bump only for package @ohif/core
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.9.8](https://github.com/OHIF/Viewers/compare/@ohif/core@2.9.7...@ohif/core@2.9.8) (2020-06-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 🐛 Disable seg panel when data for seg unavailable ([#1732](https://github.com/OHIF/Viewers/issues/1732)) ([698e900](https://github.com/OHIF/Viewers/commit/698e900b85121d3c2a46747c443ef69fb7a8c95b)), closes [#1728](https://github.com/OHIF/Viewers/issues/1728)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.9.7](https://github.com/OHIF/Viewers/compare/@ohif/core@2.9.6...@ohif/core@2.9.7) (2020-06-04)
|
||||
|
||||
**Note:** Version bump only for package @ohif/core
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.9.6](https://github.com/OHIF/Viewers/compare/@ohif/core@2.9.5...@ohif/core@2.9.6) (2020-05-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 🐛 Load default display set when no time metadata ([#1684](https://github.com/OHIF/Viewers/issues/1684)) ([f7b8b6a](https://github.com/OHIF/Viewers/commit/f7b8b6a41c4626084ef56b0fdf7363e914b143c4)), closes [#1683](https://github.com/OHIF/Viewers/issues/1683)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.9.5](https://github.com/OHIF/Viewers/compare/@ohif/core@2.9.4...@ohif/core@2.9.5) (2020-05-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 🐛 Fix seg color load ([#1724](https://github.com/OHIF/Viewers/issues/1724)) ([c4f84b1](https://github.com/OHIF/Viewers/commit/c4f84b1174d04ba84d37ed89b6d7ab541be28181))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.9.4](https://github.com/OHIF/Viewers/compare/@ohif/core@2.9.3...@ohif/core@2.9.4) (2020-05-06)
|
||||
|
||||
**Note:** Version bump only for package @ohif/core
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.9.3](https://github.com/OHIF/Viewers/compare/@ohif/core@2.9.2...@ohif/core@2.9.3) (2020-05-04)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 🐛 Proper error handling for derived display sets ([#1708](https://github.com/OHIF/Viewers/issues/1708)) ([5b20d8f](https://github.com/OHIF/Viewers/commit/5b20d8f323e4b3ef9988f2f2ab672d697b6da409))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.9.2](https://github.com/OHIF/Viewers/compare/@ohif/core@2.9.1...@ohif/core@2.9.2) (2020-05-04)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* use bit-appropriate array for palette lookup tables ([#1698](https://github.com/OHIF/Viewers/issues/1698)) ([7033886](https://github.com/OHIF/Viewers/commit/70338866978a76fa026c18d7c3c05257c5ece762))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.9.1](https://github.com/OHIF/Viewers/compare/@ohif/core@2.9.0...@ohif/core@2.9.1) (2020-04-28)
|
||||
|
||||
**Note:** Version bump only for package @ohif/core
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/core",
|
||||
"version": "2.9.1",
|
||||
"version": "2.9.14",
|
||||
"description": "Generic business logic for web-based medical imaging applications",
|
||||
"author": "OHIF Core Team",
|
||||
"license": "MIT",
|
||||
@@ -32,14 +32,14 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"cornerstone-core": "^2.2.8",
|
||||
"cornerstone-tools": "4.12.5",
|
||||
"cornerstone-tools": "4.15.1",
|
||||
"cornerstone-wado-image-loader": "^3.1.0",
|
||||
"dicom-parser": "^1.8.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.5.5",
|
||||
"ajv": "^6.10.0",
|
||||
"dcmjs": "^0.12.2",
|
||||
"dcmjs": "0.16.0",
|
||||
"dicomweb-client": "^0.6.0",
|
||||
"immer": "6.0.2",
|
||||
"isomorphic-base64": "^1.0.2",
|
||||
|
||||
@@ -13,7 +13,7 @@ const findMostRecentStructuredReport = studies => {
|
||||
// Skip series that may not have instances yet
|
||||
// This can happen if we have retrieved just the initial
|
||||
// details about the series via QIDO-RS, but not the full metadata
|
||||
if (!series.instances.length) {
|
||||
if (!series.instances || !series.instances.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -135,12 +135,15 @@ export class StudyMetadata extends Metadata {
|
||||
study,
|
||||
sopClassUIDs
|
||||
);
|
||||
|
||||
if (displaySet) {
|
||||
displaySet.sopClassModule = true;
|
||||
|
||||
displaySet.isDerived
|
||||
? this._addDerivedDisplaySet(displaySet)
|
||||
: displaySets.push(displaySet);
|
||||
if (displaySet.isDerived) {
|
||||
this._addDerivedDisplaySet(displaySet);
|
||||
}
|
||||
|
||||
displaySets.push(displaySet);
|
||||
|
||||
return displaySets;
|
||||
}
|
||||
@@ -215,6 +218,14 @@ export class StudyMetadata extends Metadata {
|
||||
// TODO -> Get x Modality by referencedSeriesInstanceUid, FoR, etc.
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the displaySets to the studies list of derived displaySets.
|
||||
* @param {array} displaySets The displaySets array to append to the derived displaysets list.
|
||||
*/
|
||||
_addDerivedDisplaySets(displaySets) {
|
||||
displaySets.map(displaySet => this._derivedDisplaySets.push(displaySet));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of derived datasets in the study, filtered by the given filter.
|
||||
* @param {object} filter An object containing search filters
|
||||
@@ -263,7 +274,7 @@ export class StudyMetadata extends Metadata {
|
||||
filteredDerivedDisplaySets = filteredDerivedDisplaySets.filter(
|
||||
displaySet =>
|
||||
displaySet.ReferencedFrameOfReferenceUID ===
|
||||
ReferencedFrameOfReferenceUID
|
||||
referencedFrameOfReferenceUID
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import MODULE_TYPES from './MODULE_TYPES.js';
|
||||
import log from './../log.js';
|
||||
|
||||
export default class ExtensionManager {
|
||||
constructor({ commandsManager, servicesManager, appConfig = {} }) {
|
||||
constructor({ commandsManager, servicesManager, api, appConfig = {} }) {
|
||||
this.modules = {};
|
||||
this.registeredExtensionIds = [];
|
||||
this.moduleTypeNames = Object.values(MODULE_TYPES);
|
||||
@@ -10,6 +10,7 @@ export default class ExtensionManager {
|
||||
this._commandsManager = commandsManager;
|
||||
this._servicesManager = servicesManager;
|
||||
this._appConfig = appConfig;
|
||||
this._api = api;
|
||||
|
||||
this.moduleTypeNames.forEach(moduleType => {
|
||||
this.modules[moduleType] = [];
|
||||
@@ -119,6 +120,7 @@ export default class ExtensionManager {
|
||||
commandsManager: this._commandsManager,
|
||||
appConfig: this._appConfig,
|
||||
configuration,
|
||||
api: this._api
|
||||
});
|
||||
|
||||
if (!extensionModule) {
|
||||
|
||||
@@ -251,7 +251,9 @@ export default class MeasurementApi {
|
||||
this.syncMeasurementsAndToolData();
|
||||
|
||||
cornerstone.getEnabledElements().forEach(enabledElement => {
|
||||
cornerstone.updateImage(enabledElement.element);
|
||||
if (enabledElement.image) {
|
||||
cornerstone.updateImage(enabledElement.element);
|
||||
}
|
||||
});
|
||||
|
||||
// Let others know that the measurements are updated
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import cornerstone from 'cornerstone-core';
|
||||
|
||||
export default function refreshCornerstoneViewports() {
|
||||
cornerstone.getEnabledElements().forEach(enabledElement => {
|
||||
if (enabledElement.image) {
|
||||
cornerstone.updateImage(enabledElement.element);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import log from '../../log';
|
||||
import user from '../../user';
|
||||
import getImageAttributes from '../lib/getImageAttributes';
|
||||
import getLabel from '../lib/getLabel';
|
||||
import refreshCornerstoneViewports from '../lib/refreshCornerstoneViewports';
|
||||
|
||||
export default function({ eventData, tool, toolGroupId, toolGroup }) {
|
||||
const measurementApi = MeasurementApi.Instance;
|
||||
@@ -86,9 +87,7 @@ export default function({ eventData, tool, toolGroupId, toolGroup }) {
|
||||
}
|
||||
|
||||
// TODO: This is very hacky, but will work for now
|
||||
cornerstone.getEnabledElements().forEach(enabledElement => {
|
||||
cornerstone.updateImage(enabledElement.element);
|
||||
});
|
||||
refreshCornerstoneViewports();
|
||||
|
||||
// TODO: Notify about the last activated measurement
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import cornerstone from 'cornerstone-core';
|
||||
import { MeasurementApi } from '../classes';
|
||||
import log from '../../log';
|
||||
import refreshCornerstoneViewports from '../lib/refreshCornerstoneViewports';
|
||||
|
||||
export default function({ eventData, tool, toolGroupId, toolGroup }) {
|
||||
log.info('CornerstoneToolsMeasurementRemoved');
|
||||
@@ -37,9 +38,7 @@ export default function({ eventData, tool, toolGroupId, toolGroup }) {
|
||||
}
|
||||
|
||||
// TODO: This is very hacky, but will work for now
|
||||
cornerstone.getEnabledElements().forEach(enabledElement => {
|
||||
cornerstone.updateImage(enabledElement.element);
|
||||
});
|
||||
refreshCornerstoneViewports();
|
||||
|
||||
if (MeasurementApi.isToolIncluded(tool)) {
|
||||
// TODO: Notify that viewer suffered changes
|
||||
|
||||
@@ -4,6 +4,7 @@ import log from '../../log';
|
||||
import user from '../../user';
|
||||
import getImageAttributes from '../lib/getImageAttributes';
|
||||
import getLabel from '../lib/getLabel';
|
||||
import refreshCornerstoneViewports from '../lib/refreshCornerstoneViewports';
|
||||
|
||||
export default function handleSingleMeasurementAdded({ eventData, tool }) {
|
||||
const measurementApi = MeasurementApi.Instance;
|
||||
@@ -39,9 +40,7 @@ export default function handleSingleMeasurementAdded({ eventData, tool }) {
|
||||
}
|
||||
|
||||
// TODO: This is very hacky, but will work for now
|
||||
cornerstone.getEnabledElements().forEach(enabledElement => {
|
||||
cornerstone.updateImage(enabledElement.element);
|
||||
});
|
||||
refreshCornerstoneViewports();
|
||||
|
||||
// TODO: Notify about the last activated measurement
|
||||
|
||||
|
||||
+2
-3
@@ -1,6 +1,7 @@
|
||||
import cornerstone from 'cornerstone-core';
|
||||
import { MeasurementApi } from '../classes';
|
||||
import log from '../../log';
|
||||
import refreshCornerstoneViewports from '../lib/refreshCornerstoneViewports';
|
||||
|
||||
export default function handleSingleMeasurementRemoved({
|
||||
eventData,
|
||||
@@ -35,9 +36,7 @@ export default function handleSingleMeasurementRemoved({
|
||||
});
|
||||
|
||||
// TODO: This is very hacky, but will work for now
|
||||
cornerstone.getEnabledElements().forEach(enabledElement => {
|
||||
cornerstone.updateImage(enabledElement.element);
|
||||
});
|
||||
refreshCornerstoneViewports();
|
||||
|
||||
if (MeasurementApi.isToolIncluded(tool)) {
|
||||
// TODO: Notify that viewer suffered changes
|
||||
|
||||
@@ -6,11 +6,12 @@ const defaultState = {
|
||||
|
||||
const servers = (state = defaultState, action) => {
|
||||
switch (action.type) {
|
||||
case 'SET_STUDY_DATA':
|
||||
const updatedStudyData = cloneDeep(state).studyData;
|
||||
updatedStudyData[action.StudyInstanceUID] = action.data;
|
||||
case 'SET_STUDY_DATA': {
|
||||
const updatedStudyData = cloneDeep(state.studyData);
|
||||
updatedStudyData[action.StudyInstanceUID] = cloneDeep(action.data);
|
||||
|
||||
return Object.assign({}, state, { studyData: updatedStudyData });
|
||||
}
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
||||
@@ -28,13 +28,14 @@ const serviceImplementation = {
|
||||
/**
|
||||
* Show a new UI modal;
|
||||
*
|
||||
* @param {ModalProps} props { content, contentProps, shouldCloseOnEsc, isOpen, closeButton, title, customClassName }
|
||||
* @param {ModalProps} props { content, contentProps, shouldCloseOnEsc, isOpen, onClose, closeButton, title, customClassName }
|
||||
*/
|
||||
function _show({
|
||||
content = null,
|
||||
contentProps = null,
|
||||
shouldCloseOnEsc = false,
|
||||
isOpen = true,
|
||||
onClose = null,
|
||||
closeButton = true,
|
||||
title = null,
|
||||
customClassName = null,
|
||||
@@ -44,6 +45,7 @@ function _show({
|
||||
contentProps,
|
||||
shouldCloseOnEsc,
|
||||
isOpen,
|
||||
onClose,
|
||||
closeButton,
|
||||
title,
|
||||
customClassName,
|
||||
|
||||
@@ -50,10 +50,12 @@ import studyMetadataManager from './studyMetadataManager';
|
||||
const loadAndCacheDerivedDisplaySets = (referencedDisplaySet, studies) => {
|
||||
const { StudyInstanceUID, SeriesInstanceUID } = referencedDisplaySet;
|
||||
|
||||
const promises = [];
|
||||
|
||||
const studyMetadata = studyMetadataManager.get(StudyInstanceUID);
|
||||
|
||||
if (!studyMetadata) {
|
||||
return;
|
||||
return promises;
|
||||
}
|
||||
|
||||
const derivedDisplaySets = studyMetadata.getDerivedDatasets({
|
||||
@@ -61,7 +63,7 @@ const loadAndCacheDerivedDisplaySets = (referencedDisplaySet, studies) => {
|
||||
});
|
||||
|
||||
if (!derivedDisplaySets.length) {
|
||||
return;
|
||||
return promises;
|
||||
}
|
||||
|
||||
// Filter by type
|
||||
@@ -80,15 +82,20 @@ const loadAndCacheDerivedDisplaySets = (referencedDisplaySet, studies) => {
|
||||
// For each type, see if any are loaded, if not load the most recent.
|
||||
Object.keys(displaySetsPerModality).forEach(key => {
|
||||
const displaySets = displaySetsPerModality[key];
|
||||
|
||||
const isLoaded = displaySets.some(displaySet => displaySet.isLoaded);
|
||||
|
||||
if (isLoaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (displaySets.some(displaySet => displaySet.loadError)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// find most recent and load it.
|
||||
let recentDateTime = 0;
|
||||
let recentDisplaySet;
|
||||
let recentDisplaySet = displaySets[0];
|
||||
|
||||
displaySets.forEach(displaySet => {
|
||||
const dateTime = Number(
|
||||
@@ -100,8 +107,12 @@ const loadAndCacheDerivedDisplaySets = (referencedDisplaySet, studies) => {
|
||||
}
|
||||
});
|
||||
|
||||
recentDisplaySet.load(referencedDisplaySet, studies);
|
||||
recentDisplaySet.isLoading = true;
|
||||
|
||||
promises.push(recentDisplaySet.load(referencedDisplaySet, studies));
|
||||
});
|
||||
|
||||
return promises;
|
||||
};
|
||||
|
||||
export default loadAndCacheDerivedDisplaySets;
|
||||
@@ -115,22 +115,14 @@ function _getPaletteColor(server, paletteColorLookupTableData, lutDescriptor) {
|
||||
const numLutEntries = lutDescriptor[0];
|
||||
const bits = lutDescriptor[2];
|
||||
|
||||
const readUInt16 = (byteArray, position) => {
|
||||
return byteArray[position] + byteArray[position + 1] * 256;
|
||||
};
|
||||
|
||||
const arrayBufferToPaletteColorLUT = arraybuffer => {
|
||||
const byteArray = new Uint8Array(arraybuffer);
|
||||
const byteArray = bits === 16 ?
|
||||
new Uint16Array(arraybuffer) :
|
||||
new Uint8Array(arraybuffer);
|
||||
const lut = [];
|
||||
|
||||
if (bits === 16) {
|
||||
for (let i = 0; i < numLutEntries; i++) {
|
||||
lut[i] = readUInt16(byteArray, i * 2);
|
||||
}
|
||||
} else {
|
||||
for (let i = 0; i < numLutEntries; i++) {
|
||||
lut[i] = byteArray[i];
|
||||
}
|
||||
for (let i = 0; i < numLutEntries; i++) {
|
||||
lut[i] = byteArray[i];
|
||||
}
|
||||
|
||||
return lut;
|
||||
|
||||
@@ -3,6 +3,55 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.5.3](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.5.2...@ohif/ui@1.5.3) (2020-07-23)
|
||||
|
||||
**Note:** Version bump only for package @ohif/ui
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.5.2](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.5.1...@ohif/ui@1.5.2) (2020-07-22)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Switch DICOMFileUploader to use the UIModalService ([#1904](https://github.com/OHIF/Viewers/issues/1904)) ([7772fee](https://github.com/OHIF/Viewers/commit/7772fee21ae6a65994e1251e2f1d2554b47781be))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.5.1](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.5.0...@ohif/ui@1.5.1) (2020-06-05)
|
||||
|
||||
**Note:** Version bump only for package @ohif/ui
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [1.5.0](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.4.4...@ohif/ui@1.5.0) (2020-06-04)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* 🎸 1729 - error boundary wrapper ([#1764](https://github.com/OHIF/Viewers/issues/1764)) ([c02b232](https://github.com/OHIF/Viewers/commit/c02b232b0cc24f38af5d5e3831d987d048e60ada))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.4.4](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.4.3...@ohif/ui@1.4.4) (2020-05-04)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 🐛 Proper error handling for derived display sets ([#1708](https://github.com/OHIF/Viewers/issues/1708)) ([5b20d8f](https://github.com/OHIF/Viewers/commit/5b20d8f323e4b3ef9988f2f2ab672d697b6da409))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.4.3](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.4.2...@ohif/ui@1.4.3) (2020-04-09)
|
||||
|
||||
**Note:** Version bump only for package @ohif/ui
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
const path = require('path');
|
||||
// ~~ Plugins
|
||||
const PnpWebpackPlugin = require(`pnp-webpack-plugin`); // Required until Webpack@5
|
||||
|
||||
exports.onCreateWebpackConfig = args => {
|
||||
args.actions.setWebpackConfig({
|
||||
node: { fs: 'empty' },
|
||||
resolve: {
|
||||
plugins: [PnpWebpackPlugin],
|
||||
// Note the '..' in the path because docz gatsby project lives in the '.docz' directory
|
||||
modules: [
|
||||
// platform/ui
|
||||
path.resolve(__dirname, '../node_modules'),
|
||||
// .docz
|
||||
'node_modules',
|
||||
],
|
||||
// resolve: {
|
||||
// symlinks: true,
|
||||
// },
|
||||
},
|
||||
resolveLoader: {
|
||||
plugins: [PnpWebpackPlugin.moduleLoader(module)],
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/ui",
|
||||
"version": "1.4.3",
|
||||
"version": "1.5.3",
|
||||
"description": "A set of React components for Medical Imaging Viewers",
|
||||
"author": "OHIF Contributors",
|
||||
"license": "MIT",
|
||||
@@ -41,20 +41,24 @@
|
||||
"@ohif/i18n": "^0.2.3",
|
||||
"browser-detect": "^0.2.28",
|
||||
"classnames": "2.2.6",
|
||||
"dom-helpers": "^3.3.1",
|
||||
"i18next": "^17.0.3",
|
||||
"i18next-browser-languagedetector": "^3.0.1",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"lodash.isequal": "4.5.0",
|
||||
"moment": "2.24.0",
|
||||
"prop-types": "15.6.2",
|
||||
"react-bootstrap-modal": "4.2.0",
|
||||
"prop-types-extra": "^1.1.1",
|
||||
"react-dates": "21.2.1",
|
||||
"react-dnd": "9.4.0",
|
||||
"react-dnd-html5-backend": "^9.4.0",
|
||||
"react-dnd-touch-backend": "^9.4.0",
|
||||
"react-draggable": "^4.1.0",
|
||||
"react-error-boundary": "^2.2.1",
|
||||
"react-i18next": "^10.11.0",
|
||||
"react-modal": "^3.11.1",
|
||||
"react-overlays": "^0.8.0",
|
||||
"react-transition-group": "2.0.0",
|
||||
"react-with-direction": "1.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { ErrorBoundary } from 'react-error-boundary';
|
||||
import './ErrorFallback.css';
|
||||
|
||||
const ErrorFallback = ({ error, componentStack, resetErrorBoundary }) => {
|
||||
return (
|
||||
<div className="ErrorFallback" role="alert">
|
||||
<p>Something went wrong.</p>
|
||||
<pre>{error.message}</pre>
|
||||
<pre>{componentStack}</pre>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const OHIFErrorBoundary = ({
|
||||
context = 'OHIF',
|
||||
onReset = () => {},
|
||||
onError = () => {},
|
||||
fallbackComponent,
|
||||
children,
|
||||
}) => {
|
||||
const onErrorHandler = (error, componentStack) => {
|
||||
console.error(`${context} Error Boundary`, error, componentStack);
|
||||
onError(error, componentStack);
|
||||
};
|
||||
|
||||
const onResetHandler = () => {
|
||||
onReset();
|
||||
};
|
||||
|
||||
return (
|
||||
<ErrorBoundary
|
||||
FallbackComponent={fallbackComponent || ErrorFallback}
|
||||
onReset={onResetHandler}
|
||||
onError={onErrorHandler}
|
||||
>
|
||||
{children}
|
||||
</ErrorBoundary>
|
||||
);
|
||||
};
|
||||
|
||||
OHIFErrorBoundary.propTypes = {
|
||||
context: PropTypes.string,
|
||||
onReset: PropTypes.func,
|
||||
onError: PropTypes.func,
|
||||
children: PropTypes.node.isRequired,
|
||||
fallbackComponent: PropTypes.element,
|
||||
};
|
||||
|
||||
export default OHIFErrorBoundary;
|
||||
@@ -0,0 +1,4 @@
|
||||
.ErrorFallback {
|
||||
padding: 10px;
|
||||
color: var(--active-color);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { default as ErrorBoundary } from './ErrorBoundary';
|
||||
@@ -7,7 +7,6 @@ import { AboutContent } from './content/aboutContent/AboutContent';
|
||||
import { TabComponents, TabFooter } from './tabComponents';
|
||||
import { HotkeyField } from './customForm';
|
||||
import { LanguageSwitcher } from './languageSwitcher';
|
||||
|
||||
import { Checkbox } from './checkbox';
|
||||
import { CineDialog } from './cineDialog';
|
||||
import { ViewportDownloadForm } from './content/viewportDownloadForm';
|
||||
@@ -25,8 +24,10 @@ import {
|
||||
} from './studyList';
|
||||
import { ToolbarSection } from './toolbarSection';
|
||||
import { Tooltip } from './tooltip';
|
||||
import { ErrorBoundary } from './errorBoundary';
|
||||
|
||||
export {
|
||||
ErrorBoundary,
|
||||
ContextMenu,
|
||||
Checkbox,
|
||||
CineDialog,
|
||||
|
||||
@@ -127,7 +127,7 @@
|
||||
|
||||
.sb-message {
|
||||
font-size: 14px;
|
||||
word-break: break-all;
|
||||
word-break: normal;
|
||||
}
|
||||
|
||||
.sb-item {
|
||||
|
||||
@@ -32,6 +32,7 @@ const ModalProvider = ({ children, modal: Modal, service }) => {
|
||||
contentProps: null,
|
||||
shouldCloseOnEsc: false,
|
||||
isOpen: true,
|
||||
onClose: null,
|
||||
closeButton: true,
|
||||
title: null,
|
||||
customClassName: '',
|
||||
@@ -73,6 +74,7 @@ const ModalProvider = ({ children, modal: Modal, service }) => {
|
||||
content: ModalContent,
|
||||
contentProps,
|
||||
isOpen,
|
||||
onClose,
|
||||
title,
|
||||
customClassName,
|
||||
shouldCloseOnEsc,
|
||||
@@ -88,7 +90,13 @@ const ModalProvider = ({ children, modal: Modal, service }) => {
|
||||
isOpen={isOpen}
|
||||
title={title}
|
||||
closeButton={closeButton}
|
||||
onClose={hide}
|
||||
onClose={() => {
|
||||
if (onClose) {
|
||||
onClose();
|
||||
}
|
||||
|
||||
hide();
|
||||
}}
|
||||
>
|
||||
<ModalContent {...contentProps} show={show} hide={hide} />
|
||||
</Modal>
|
||||
|
||||
@@ -51,3 +51,9 @@ button.close
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
|
||||
.btn-sm
|
||||
padding: .25rem .5rem;
|
||||
font-size: .875rem;
|
||||
line-height: 1.5;
|
||||
border-radius: .2rem;
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
Tooltip,
|
||||
AboutContent,
|
||||
OHIFModal,
|
||||
ErrorBoundary
|
||||
} from './components';
|
||||
import { useDebounce, useMedia } from './hooks';
|
||||
|
||||
@@ -123,6 +124,7 @@ export {
|
||||
DialogProvider,
|
||||
withDialog,
|
||||
useDialog,
|
||||
ErrorBoundary,
|
||||
// Hooks
|
||||
useDebounce,
|
||||
useMedia,
|
||||
|
||||
@@ -3,6 +3,272 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [4.2.16](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.2.15...@ohif/viewer@4.2.16) (2020-08-10)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [4.2.15](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.2.14...@ohif/viewer@4.2.15) (2020-08-05)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [4.2.14](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.2.13...@ohif/viewer@4.2.14) (2020-08-05)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [4.2.13](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.2.12...@ohif/viewer@4.2.13) (2020-07-23)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [4.2.12](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.2.11...@ohif/viewer@4.2.12) (2020-07-22)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Switch DICOMFileUploader to use the UIModalService ([#1904](https://github.com/OHIF/Viewers/issues/1904)) ([7772fee](https://github.com/OHIF/Viewers/commit/7772fee21ae6a65994e1251e2f1d2554b47781be))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [4.2.11](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.2.10...@ohif/viewer@4.2.11) (2020-07-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 🐛 - Put guards in all places that a cornerstone re-render ([#1899](https://github.com/OHIF/Viewers/issues/1899)) ([451f7ea](https://github.com/OHIF/Viewers/commit/451f7eab9258e7a193eb362e0926b13aedc4b3c9))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [4.2.10](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.2.9...@ohif/viewer@4.2.10) (2020-07-13)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [4.2.9](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.2.8...@ohif/viewer@4.2.9) (2020-07-13)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [4.2.8](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.2.7...@ohif/viewer@4.2.8) (2020-07-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 🐛 Fix RT Panel hide/show and Fix looping load errors ([#1877](https://github.com/OHIF/Viewers/issues/1877)) ([e7cc735](https://github.com/OHIF/Viewers/commit/e7cc735c03d02eeb0d3af4ba02c15ed4f81bbec2))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [4.2.7](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.2.6...@ohif/viewer@4.2.7) (2020-06-18)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [4.2.6](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.2.5...@ohif/viewer@4.2.6) (2020-06-18)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [4.2.5](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.2.4...@ohif/viewer@4.2.5) (2020-06-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 🐛 Disable seg panel when data for seg unavailable ([#1732](https://github.com/OHIF/Viewers/issues/1732)) ([698e900](https://github.com/OHIF/Viewers/commit/698e900b85121d3c2a46747c443ef69fb7a8c95b)), closes [#1728](https://github.com/OHIF/Viewers/issues/1728)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [4.2.4](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.2.3...@ohif/viewer@4.2.4) (2020-06-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* OIDC Redirect erases query parameters ([#1773](https://github.com/OHIF/Viewers/issues/1773)) ([6123741](https://github.com/OHIF/Viewers/commit/6123741765e81d0bea8fbd5dbb0f310aaca0fb33))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [4.2.3](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.2.2...@ohif/viewer@4.2.3) (2020-06-05)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [4.2.2](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.2.1...@ohif/viewer@4.2.2) (2020-06-04)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [4.2.1](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.2.0...@ohif/viewer@4.2.1) (2020-06-04)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [4.2.0](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.1.0...@ohif/viewer@4.2.0) (2020-06-04)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* 🎸 1729 - error boundary wrapper ([#1764](https://github.com/OHIF/Viewers/issues/1764)) ([c02b232](https://github.com/OHIF/Viewers/commit/c02b232b0cc24f38af5d5e3831d987d048e60ada))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [4.1.0](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.0.0...@ohif/viewer@4.1.0) (2020-05-15)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* expose some app internals as window.app ([#1735](https://github.com/OHIF/Viewers/issues/1735)) ([63fd656](https://github.com/OHIF/Viewers/commit/63fd65690cba450721870a6222e0fb3ad71bb291))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [4.0.0](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.11.11...@ohif/viewer@4.0.0) (2020-05-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 🐛 Fix race condition when loading derived display sets ([#1718](https://github.com/OHIF/Viewers/issues/1718)) ([b1678ce](https://github.com/OHIF/Viewers/commit/b1678ce6399dde37a9878f45ccc7c63286d93fab)), closes [#1715](https://github.com/OHIF/Viewers/issues/1715)
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* 🧨 However we start to load once the first set of metadata arrives. We need
|
||||
to wait until all series metadata is fetched.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.11.11](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.11.10...@ohif/viewer@3.11.11) (2020-05-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 🐛 Load default display set when no time metadata ([#1684](https://github.com/OHIF/Viewers/issues/1684)) ([f7b8b6a](https://github.com/OHIF/Viewers/commit/f7b8b6a41c4626084ef56b0fdf7363e914b143c4)), closes [#1683](https://github.com/OHIF/Viewers/issues/1683)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.11.10](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.11.9...@ohif/viewer@3.11.10) (2020-05-13)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.11.9](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.11.8...@ohif/viewer@3.11.9) (2020-05-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 🐛 Fix seg color load ([#1724](https://github.com/OHIF/Viewers/issues/1724)) ([c4f84b1](https://github.com/OHIF/Viewers/commit/c4f84b1174d04ba84d37ed89b6d7ab541be28181))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.11.8](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.11.7...@ohif/viewer@3.11.8) (2020-05-06)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.11.7](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.11.6...@ohif/viewer@3.11.7) (2020-05-04)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.11.6](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.11.5...@ohif/viewer@3.11.6) (2020-05-04)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 🐛 Proper error handling for derived display sets ([#1708](https://github.com/OHIF/Viewers/issues/1708)) ([5b20d8f](https://github.com/OHIF/Viewers/commit/5b20d8f323e4b3ef9988f2f2ab672d697b6da409))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.11.5](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.11.4...@ohif/viewer@3.11.5) (2020-05-04)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.11.4](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.11.3...@ohif/viewer@3.11.4) (2020-05-04)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.11.3](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.11.2...@ohif/viewer@3.11.3) (2020-04-29)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Add IHEInvokeImageDisplay routes back into viewer ([#1695](https://github.com/OHIF/Viewers/issues/1695)) ([f7162ce](https://github.com/OHIF/Viewers/commit/f7162ce61708776a6c192732b0904a022bcc6b3a))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.11.2](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.11.1...@ohif/viewer@3.11.2) (2020-04-28)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
@@ -115,13 +115,13 @@ Commands to run the tests:
|
||||
|
||||
```js
|
||||
// Open Cypress Dashboard that provides insight into what happened when your tests ran
|
||||
yarn run cy
|
||||
yarn test:e2e
|
||||
|
||||
// Run all tests using Electron browser headless
|
||||
yarn run cy:run
|
||||
yarn test:e2e:local
|
||||
|
||||
// Run all tests in CI mode
|
||||
yarn run cy:run:ci
|
||||
yarn run test:e2e:ci
|
||||
```
|
||||
|
||||
### Contributing
|
||||
|
||||
@@ -5,17 +5,18 @@ describe('OHIF Routes', function() {
|
||||
cy.openStudyList();
|
||||
});
|
||||
|
||||
it('checks TEST json url study route', function() {
|
||||
cy.visit(
|
||||
'/viewer?url=https://ohif-viewer.s3.eu-central-1.amazonaws.com/JSON/testJSON.json'
|
||||
);
|
||||
// TODO -> Bring back when testJSON is hosted again.
|
||||
// it('checks TEST json url study route', function() {
|
||||
// cy.visit(
|
||||
// '/viewer?url=https://ohif-viewer.s3.eu-central-1.amazonaws.com/JSON/testJSON.json'
|
||||
// );
|
||||
|
||||
cy.server();
|
||||
cy.route('GET', '**/MRStudy/**').as('getTESTStudy');
|
||||
// cy.server();
|
||||
// cy.route('GET', '**/ MRStudy; /**').as('getTESTStudy');
|
||||
|
||||
cy.wait('@getTESTStudy.all');
|
||||
cy.get('@getTESTStudy').should($route => {
|
||||
expect($route.status).to.be.eq(200);
|
||||
});
|
||||
});
|
||||
// cy.wait('@getTESTStudy.all');
|
||||
// cy.get('@getTESTStudy').should($route => {
|
||||
// expect($route.status).to.be.eq(200);
|
||||
// });
|
||||
// });
|
||||
});
|
||||
@@ -22,7 +22,7 @@ describe('OHIF Study List', function() {
|
||||
//Wait result list to be displayed
|
||||
cy.waitStudyList();
|
||||
cy.get('@searchResult').should($list => {
|
||||
expect($list.length).to.be.eq(2);
|
||||
expect($list.length).to.be.eq(3);
|
||||
expect($list).to.contain('Juno');
|
||||
});
|
||||
});
|
||||
@@ -38,12 +38,12 @@ describe('OHIF Study List', function() {
|
||||
});
|
||||
|
||||
it('searches Accession with exact string', function() {
|
||||
cy.get('@AccessionNumber').type('fpcben98890');
|
||||
cy.get('@AccessionNumber').type('0000481914');
|
||||
//Wait result list to be displayed
|
||||
cy.waitStudyList();
|
||||
cy.get('@searchResult').should($list => {
|
||||
expect($list.length).to.be.eq(1);
|
||||
expect($list).to.contain('fpcben98890');
|
||||
expect($list).to.contain('0000481914');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -52,7 +52,8 @@ describe('OHIF Study List', function() {
|
||||
//Wait result list to be displayed
|
||||
cy.waitStudyList();
|
||||
cy.get('@searchResult').should($list => {
|
||||
expect($list.length).to.be.eq(15); // TODO: Where are you hiding MISTER^MR?
|
||||
// TODO: Why are we facing some inconsistency with this result? ¯\_(ツ)_/¯
|
||||
expect($list.length).to.be.eq(9);
|
||||
expect($list).to.contain('MR');
|
||||
});
|
||||
});
|
||||
@@ -127,7 +128,7 @@ describe('OHIF Study List', function() {
|
||||
//Wait result list to be displayed
|
||||
cy.waitStudyList();
|
||||
cy.get('@searchResult').should($list => {
|
||||
expect($list.length).to.be.eq(2);
|
||||
expect($list.length).to.be.eq(3);
|
||||
expect($list).to.contain('Juno');
|
||||
});
|
||||
});
|
||||
@@ -137,7 +138,7 @@ describe('OHIF Study List', function() {
|
||||
//Wait result list to be displayed
|
||||
cy.waitStudyList();
|
||||
cy.get('@searchResult').should($list => {
|
||||
expect($list.length).to.be.eq(2);
|
||||
expect($list.length).to.be.eq(6);
|
||||
expect($list).to.contain('ProstateX-0000');
|
||||
});
|
||||
});
|
||||
@@ -147,18 +148,19 @@ describe('OHIF Study List', function() {
|
||||
//Wait result list to be displayed
|
||||
cy.waitStudyList();
|
||||
cy.get('@searchResult').should($list => {
|
||||
expect($list.length).to.be.eq(15); // TODO: Where are you hiding MISTER^MR?
|
||||
// TODO: Why are we facing some inconsistency with this result? ¯\_(ツ)_/¯
|
||||
expect($list.length).to.be.eq(9);
|
||||
expect($list).to.contain('MR');
|
||||
});
|
||||
});
|
||||
|
||||
it('searches Accession with exact string', function() {
|
||||
cy.get('@accessionModalityDescription').type('fpcben98890');
|
||||
cy.get('@accessionModalityDescription').type('0000481914');
|
||||
//Wait result list to be displayed
|
||||
cy.waitStudyList();
|
||||
cy.get('@searchResult').should($list => {
|
||||
expect($list.length).to.be.eq(1);
|
||||
expect($list).to.contain('fpcben98890');
|
||||
expect($list).to.contain('0000481914');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
describe('OHIF Microscopy Extension', () => {
|
||||
before(() => {
|
||||
cy.openStudyModality('SM');
|
||||
cy.expectMinimumThumbnails(6);
|
||||
cy.expectMinimumThumbnails(2);
|
||||
});
|
||||
|
||||
it('checks if series thumbnails are being displayed', () => {
|
||||
@@ -12,6 +12,8 @@ describe('OHIF Microscopy Extension', () => {
|
||||
});
|
||||
|
||||
it('drags and drop a SM thumbnail into viewport', () => {
|
||||
// Waiting for series list to load all displaySets (lots of SRs, before defining which dom element to grab.)
|
||||
cy.wait(3000);
|
||||
cy.get('[data-cy="thumbnail-list"]')
|
||||
.contains('SM')
|
||||
.drag('.viewport-drop-target');
|
||||
|
||||
@@ -21,8 +21,8 @@ describe('OHIF VTK Extension', () => {
|
||||
//Select 2D MPR button
|
||||
cy.get('[data-cy="2d mpr"]').click();
|
||||
|
||||
//Wait Reformatting Images
|
||||
cy.waitVTKReformatting();
|
||||
//Wait waitVTKLoading Images
|
||||
cy.waitVTKLoading();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
+2
-2
@@ -18,8 +18,8 @@ describe('Visual Regression - OHIF VTK Extension', () => {
|
||||
//Select 2D MPR button
|
||||
cy.get('[data-cy="2d mpr"]').click();
|
||||
|
||||
//Wait Reformatting Images
|
||||
cy.waitVTKReformatting();
|
||||
//Wait waitVTKLoading Images
|
||||
cy.waitVTKwaitVTKLoading();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
@@ -5,13 +5,15 @@ describe('Visual Regression - OHIF Routes', function() {
|
||||
cy.openStudyList();
|
||||
});
|
||||
|
||||
/*
|
||||
// TODO -> Bring back when testJSON is hosted again.
|
||||
it('checks TEST json url study route', function() {
|
||||
cy.visit(
|
||||
'/viewer?url=https://ohif-viewer.s3.eu-central-1.amazonaws.com/JSON/testJSON.json'
|
||||
);
|
||||
|
||||
cy.server();
|
||||
cy.route('GET', '**/TESTStudy/**').as('getTESTStudy');
|
||||
cy.route('GET', '**/ TESTStudy; /**').as('getTESTStudy');
|
||||
|
||||
cy.wait('@getTESTStudy.all');
|
||||
cy.get('@getTESTStudy').should($route => {
|
||||
@@ -20,4 +22,5 @@ describe('Visual Regression - OHIF Routes', function() {
|
||||
|
||||
cy.percyCanvasSnapshot('TEST json study route');
|
||||
});
|
||||
*/
|
||||
});
|
||||
@@ -107,15 +107,15 @@ Cypress.Commands.add('waitStudyList', () => {
|
||||
});
|
||||
});
|
||||
|
||||
Cypress.Commands.add('waitVTKReformatting', () => {
|
||||
// Wait for start reformatting
|
||||
cy.get('[data-cy="viewprt-grid"]', { timeout: 10000 }).should($grid => {
|
||||
expect($grid).to.contain.text('Reform');
|
||||
Cypress.Commands.add('waitVTKLoading', () => {
|
||||
// Wait for start loading
|
||||
cy.get('[data-cy="viewprt-grid"]', { timeout: 20000 }).should($grid => {
|
||||
expect($grid).to.contain.text('Loading');
|
||||
});
|
||||
|
||||
// Wait for finish reformatting
|
||||
// Wait for finish loading
|
||||
cy.get('[data-cy="viewprt-grid"]', { timeout: 30000 }).should($grid => {
|
||||
expect($grid).not.to.contain.text('Reform');
|
||||
expect($grid).not.to.contain.text('Loading');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/viewer",
|
||||
"version": "3.11.2",
|
||||
"version": "4.2.16",
|
||||
"description": "OHIF Viewer",
|
||||
"author": "OHIF Contributors",
|
||||
"license": "MIT",
|
||||
@@ -48,26 +48,26 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.5.5",
|
||||
"@ohif/core": "^2.9.1",
|
||||
"@ohif/extension-cornerstone": "^2.7.0",
|
||||
"@ohif/extension-dicom-html": "^1.2.1",
|
||||
"@ohif/extension-dicom-microscopy": "^0.50.9",
|
||||
"@ohif/extension-dicom-p10-downloader": "^0.1.0",
|
||||
"@ohif/core": "^2.9.14",
|
||||
"@ohif/extension-cornerstone": "^2.8.5",
|
||||
"@ohif/extension-dicom-html": "^1.2.4",
|
||||
"@ohif/extension-dicom-microscopy": "^0.51.0",
|
||||
"@ohif/extension-dicom-p10-downloader": "^0.1.2",
|
||||
"@ohif/extension-dicom-pdf": "^1.0.3",
|
||||
"@ohif/extension-dicom-rt": "^0.1.1",
|
||||
"@ohif/extension-dicom-segmentation": "^0.2.2",
|
||||
"@ohif/extension-dicom-rt": "^0.2.7",
|
||||
"@ohif/extension-dicom-segmentation": "^0.3.4",
|
||||
"@ohif/extension-lesion-tracker": "^0.2.0",
|
||||
"@ohif/extension-vtk": "^1.5.1",
|
||||
"@ohif/extension-vtk": "^1.6.10",
|
||||
"@ohif/i18n": "^0.52.8",
|
||||
"@ohif/ui": "^1.4.3",
|
||||
"@ohif/ui": "^1.5.3",
|
||||
"@tanem/react-nprogress": "^1.1.25",
|
||||
"classnames": "^2.2.6",
|
||||
"core-js": "^3.2.1",
|
||||
"cornerstone-core": "^2.2.8",
|
||||
"cornerstone-math": "^0.1.8",
|
||||
"cornerstone-tools": "4.12.5",
|
||||
"cornerstone-tools": "4.15.1",
|
||||
"cornerstone-wado-image-loader": "^3.1.0",
|
||||
"dcmjs": "^0.12.2",
|
||||
"dcmjs": "0.16.0",
|
||||
"dicom-parser": "^1.8.3",
|
||||
"dicomweb-client": "^0.4.4",
|
||||
"hammerjs": "^2.0.8",
|
||||
|
||||
@@ -15,6 +15,7 @@ window.config = {
|
||||
imageRendering: 'wadors',
|
||||
thumbnailRendering: 'wadors',
|
||||
enableStudyLazyLoad: true,
|
||||
supportsFuzzyMatching: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -9,11 +9,10 @@ window.config = function(props) {
|
||||
httpErrorHandler: error => {
|
||||
// This is 429 when rejected from the public idc sandbox too often.
|
||||
console.warn(error.status);
|
||||
|
||||
// Could use services manager here to bring up a dialog/modal if needed.
|
||||
console.warn('test, navigate to https://ohif.org/');
|
||||
window.location = 'https://ohif.org/';
|
||||
},
|
||||
healthcareApiEndpoint: 'https://idc-sandbox-002.appspot.com/v1beta1',
|
||||
healthcareApiEndpoint: 'https://proxy-dot-idc-dev.appspot.com/v1beta1',
|
||||
};
|
||||
};
|
||||
@@ -12,6 +12,7 @@ window.config = {
|
||||
imageRendering: 'wadors',
|
||||
thumbnailRendering: 'wadors',
|
||||
enableStudyLazyLoad: true,
|
||||
supportsFuzzyMatching: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -11,6 +11,7 @@ window.config = {
|
||||
qidoSupportsIncludeField: true,
|
||||
imageRendering: 'wadors',
|
||||
thumbnailRendering: 'wadors',
|
||||
supportsFuzzyMatching: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
+59
-40
@@ -13,6 +13,7 @@ import {
|
||||
ModalProvider,
|
||||
DialogProvider,
|
||||
OHIFModal,
|
||||
ErrorBoundary
|
||||
} from '@ohif/ui';
|
||||
|
||||
import {
|
||||
@@ -53,7 +54,7 @@ import store from './store';
|
||||
/** Contexts */
|
||||
import WhiteLabelingContext from './context/WhiteLabelingContext';
|
||||
import UserManagerContext from './context/UserManagerContext';
|
||||
import AppContext from './context/AppContext';
|
||||
import { AppProvider, useAppContext, CONTEXTS } from './context/AppContext';
|
||||
|
||||
/** ~~~~~~~~~~~~~ Application Setup */
|
||||
const commandsManagerConfig = {
|
||||
@@ -71,6 +72,14 @@ let extensionManager;
|
||||
// TODO[react] Use a provider when the whole tree is React
|
||||
window.store = store;
|
||||
|
||||
window.ohif = window.ohif || {};
|
||||
window.ohif.app = {
|
||||
commandsManager,
|
||||
hotkeysManager,
|
||||
servicesManager,
|
||||
extensionManager,
|
||||
};
|
||||
|
||||
class App extends Component {
|
||||
static propTypes = {
|
||||
config: PropTypes.oneOfType([
|
||||
@@ -159,53 +168,57 @@ class App extends Component {
|
||||
|
||||
if (this._userManager) {
|
||||
return (
|
||||
<AppContext.Provider value={{ appConfig: this._appConfig }}>
|
||||
<ErrorBoundary context='App'>
|
||||
<Provider store={store}>
|
||||
<I18nextProvider i18n={i18n}>
|
||||
<OidcProvider store={store} userManager={this._userManager}>
|
||||
<UserManagerContext.Provider value={this._userManager}>
|
||||
<Router basename={routerBasename}>
|
||||
<WhiteLabelingContext.Provider value={whiteLabeling}>
|
||||
<SnackbarProvider service={UINotificationService}>
|
||||
<DialogProvider service={UIDialogService}>
|
||||
<ModalProvider
|
||||
modal={OHIFModal}
|
||||
service={UIModalService}
|
||||
>
|
||||
<OHIFStandaloneViewer
|
||||
userManager={this._userManager}
|
||||
/>
|
||||
</ModalProvider>
|
||||
</DialogProvider>
|
||||
</SnackbarProvider>
|
||||
</WhiteLabelingContext.Provider>
|
||||
</Router>
|
||||
</UserManagerContext.Provider>
|
||||
</OidcProvider>
|
||||
</I18nextProvider>
|
||||
<AppProvider config={this._appConfig}>
|
||||
<I18nextProvider i18n={i18n}>
|
||||
<OidcProvider store={store} userManager={this._userManager}>
|
||||
<UserManagerContext.Provider value={this._userManager}>
|
||||
<Router basename={routerBasename}>
|
||||
<WhiteLabelingContext.Provider value={whiteLabeling}>
|
||||
<SnackbarProvider service={UINotificationService}>
|
||||
<DialogProvider service={UIDialogService}>
|
||||
<ModalProvider
|
||||
modal={OHIFModal}
|
||||
service={UIModalService}
|
||||
>
|
||||
<OHIFStandaloneViewer
|
||||
userManager={this._userManager}
|
||||
/>
|
||||
</ModalProvider>
|
||||
</DialogProvider>
|
||||
</SnackbarProvider>
|
||||
</WhiteLabelingContext.Provider>
|
||||
</Router>
|
||||
</UserManagerContext.Provider>
|
||||
</OidcProvider>
|
||||
</I18nextProvider>
|
||||
</AppProvider>
|
||||
</Provider>
|
||||
</AppContext.Provider>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<AppContext.Provider value={{ appConfig: this._appConfig }}>
|
||||
<ErrorBoundary context='App'>
|
||||
<Provider store={store}>
|
||||
<I18nextProvider i18n={i18n}>
|
||||
<Router basename={routerBasename}>
|
||||
<WhiteLabelingContext.Provider value={whiteLabeling}>
|
||||
<SnackbarProvider service={UINotificationService}>
|
||||
<DialogProvider service={UIDialogService}>
|
||||
<ModalProvider modal={OHIFModal} service={UIModalService}>
|
||||
<OHIFStandaloneViewer />
|
||||
</ModalProvider>
|
||||
</DialogProvider>
|
||||
</SnackbarProvider>
|
||||
</WhiteLabelingContext.Provider>
|
||||
</Router>
|
||||
</I18nextProvider>
|
||||
<AppProvider config={this._appConfig}>
|
||||
<I18nextProvider i18n={i18n}>
|
||||
<Router basename={routerBasename}>
|
||||
<WhiteLabelingContext.Provider value={whiteLabeling}>
|
||||
<SnackbarProvider service={UINotificationService}>
|
||||
<DialogProvider service={UIDialogService}>
|
||||
<ModalProvider modal={OHIFModal} service={UIModalService}>
|
||||
<OHIFStandaloneViewer />
|
||||
</ModalProvider>
|
||||
</DialogProvider>
|
||||
</SnackbarProvider>
|
||||
</WhiteLabelingContext.Provider>
|
||||
</Router>
|
||||
</I18nextProvider>
|
||||
</AppProvider>
|
||||
</Provider>
|
||||
</AppContext.Provider>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -255,6 +268,12 @@ function _initExtensions(extensions, cornerstoneExtensionConfig, appConfig) {
|
||||
commandsManager,
|
||||
servicesManager,
|
||||
appConfig,
|
||||
api: {
|
||||
contexts: CONTEXTS,
|
||||
hooks: {
|
||||
useAppContext
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const requiredExtensions = [
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Route, Switch } from 'react-router-dom';
|
||||
import { NProgress } from '@tanem/react-nprogress';
|
||||
import { CSSTransition } from 'react-transition-group';
|
||||
import { connect } from 'react-redux';
|
||||
import { ViewerbaseDragDropContext } from '@ohif/ui';
|
||||
import { ViewerbaseDragDropContext, ErrorBoundary } from '@ohif/ui';
|
||||
import { SignoutCallbackComponent } from 'redux-oidc';
|
||||
import asyncComponent from './components/AsyncComponent.js';
|
||||
import * as RoutesUtil from './routes/routesUtil';
|
||||
@@ -52,10 +52,13 @@ class OHIFStandaloneViewer extends Component {
|
||||
const { appConfig = {} } = this.context;
|
||||
const userNotLoggedIn = userManager && (!user || user.expired);
|
||||
if (userNotLoggedIn) {
|
||||
const pathname = this.props.location.pathname;
|
||||
const { pathname, search } = this.props.location;
|
||||
|
||||
if (pathname !== '/callback') {
|
||||
sessionStorage.setItem('ohif-redirect-to', pathname);
|
||||
sessionStorage.setItem(
|
||||
'ohif-redirect-to',
|
||||
JSON.stringify({ pathname, search })
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -103,12 +106,21 @@ class OHIFStandaloneViewer extends Component {
|
||||
|
||||
userManager.removeUser().then(() => {
|
||||
if (targetLinkUri !== null) {
|
||||
const ohifRedirectTo = {
|
||||
pathname: new URL(targetLinkUri).pathname,
|
||||
};
|
||||
sessionStorage.setItem(
|
||||
'ohif-redirect-to',
|
||||
new URL(targetLinkUri).pathname
|
||||
JSON.stringify(ohifRedirectTo)
|
||||
);
|
||||
} else {
|
||||
sessionStorage.setItem('ohif-redirect-to', '/');
|
||||
const ohifRedirectTo = {
|
||||
pathname: '/',
|
||||
};
|
||||
sessionStorage.setItem(
|
||||
'ohif-redirect-to',
|
||||
JSON.stringify(ohifRedirectTo)
|
||||
);
|
||||
}
|
||||
|
||||
if (loginHint !== null) {
|
||||
@@ -191,7 +203,9 @@ class OHIFStandaloneViewer extends Component {
|
||||
{match === null ? (
|
||||
<></>
|
||||
) : (
|
||||
<Component match={match} location={this.props.location} />
|
||||
<ErrorBoundary context={match.url}>
|
||||
<Component match={match} location={this.props.location} />
|
||||
</ErrorBoundary>
|
||||
)}
|
||||
</CSSTransition>
|
||||
)}
|
||||
|
||||
@@ -329,7 +329,9 @@ const mapDispatchToProps = (dispatch, ownProps) => {
|
||||
measurementApi.syncMeasurementsAndToolData();
|
||||
|
||||
cornerstone.getEnabledElements().forEach(enabledElement => {
|
||||
cornerstone.updateImage(enabledElement.element);
|
||||
if (enabledElement.image) {
|
||||
cornerstone.updateImage(enabledElement.element);
|
||||
}
|
||||
});
|
||||
|
||||
// Needs to update viewports.layout state to set layout
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
.ErrorBoundaryDialogTitle {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.ErrorBoundaryDialogButton {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.ErrorBoundaryDialogIcon {
|
||||
margin-right: 5px;
|
||||
width: 10px;
|
||||
transform: rotate(-90deg);
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.ErrorBoundaryDialogIcon.opened {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
import React, { useState } from 'react';
|
||||
import classnames from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
import { ErrorBoundary, Icon } from '@ohif/ui';
|
||||
import { servicesManager } from './../../App';
|
||||
|
||||
import './ErrorBoundaryDialog.css';
|
||||
|
||||
const { UIModalService } = servicesManager.services;
|
||||
|
||||
const ErrorBoundaryDialog = ({ context, children }) => {
|
||||
const handleOnError = (error, componentStack) => {
|
||||
const ErrorDialog = () => {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="ErrorFallback" role="alert">
|
||||
<div className="ErrorBoundaryDialog">
|
||||
<h3 className="ErrorBoundaryDialogTitle">
|
||||
{context}: <span>{error.message}</span>
|
||||
</h3>
|
||||
</div>
|
||||
<button
|
||||
className="btn btn-primary btn-sm ErrorBoundaryDialogButton"
|
||||
onClick={() => setOpen(s => !s)}
|
||||
>
|
||||
<Icon
|
||||
name="chevron-down"
|
||||
className={classnames('ErrorBoundaryDialogIcon', {
|
||||
opened: open,
|
||||
})}
|
||||
/>
|
||||
Stack Trace
|
||||
</button>
|
||||
|
||||
{open && <pre>{componentStack}</pre>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
UIModalService.show({
|
||||
content: ErrorDialog,
|
||||
title: `Something went wrong in ${context}`,
|
||||
});
|
||||
};
|
||||
|
||||
const fallbackComponent = () => (
|
||||
<div className="ErrorFallback" role="alert">
|
||||
<p>
|
||||
Error rendering {context}. <br /> Check the browser console for more
|
||||
details.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<ErrorBoundary
|
||||
fallbackComponent={fallbackComponent}
|
||||
context={context}
|
||||
onError={handleOnError}
|
||||
>
|
||||
{children}
|
||||
</ErrorBoundary>
|
||||
);
|
||||
};
|
||||
|
||||
ErrorBoundaryDialog.propTypes = {
|
||||
context: PropTypes.string.isRequired,
|
||||
children: PropTypes.node.isRequired,
|
||||
};
|
||||
|
||||
export default ErrorBoundaryDialog;
|
||||
@@ -0,0 +1,3 @@
|
||||
import ErrorBoundaryDialog from './ErrorBoundaryDialog';
|
||||
|
||||
export default ErrorBoundaryDialog;
|
||||
@@ -4,6 +4,7 @@ import React, { useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import { utils } from '@ohif/core';
|
||||
import { useSnackbarContext } from '@ohif/ui';
|
||||
//
|
||||
import ViewportPane from './ViewportPane.js';
|
||||
import DefaultViewport from './DefaultViewport.js';
|
||||
@@ -11,7 +12,7 @@ import EmptyViewport from './EmptyViewport.js';
|
||||
|
||||
const { loadAndCacheDerivedDisplaySets } = utils;
|
||||
|
||||
const ViewportGrid = function (props) {
|
||||
const ViewportGrid = function(props) {
|
||||
const {
|
||||
activeViewportIndex,
|
||||
availablePlugins,
|
||||
@@ -23,6 +24,7 @@ const ViewportGrid = function (props) {
|
||||
studies,
|
||||
viewportData,
|
||||
children,
|
||||
isStudyLoaded,
|
||||
} = props;
|
||||
|
||||
const rowSize = 100 / numRows;
|
||||
@@ -33,11 +35,26 @@ const ViewportGrid = function (props) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const snackbar = useSnackbarContext();
|
||||
|
||||
useEffect(() => {
|
||||
viewportData.forEach(displaySet => {
|
||||
loadAndCacheDerivedDisplaySets(displaySet, studies);
|
||||
});
|
||||
}, [studies, viewportData]);
|
||||
if (isStudyLoaded) {
|
||||
viewportData.forEach(displaySet => {
|
||||
const promises = loadAndCacheDerivedDisplaySets(displaySet, studies);
|
||||
|
||||
promises.forEach(promise => {
|
||||
promise.catch(error => {
|
||||
snackbar.show({
|
||||
title: 'Error loading derived display set:',
|
||||
message: error.message,
|
||||
type: 'error',
|
||||
autoClose: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}, [studies, viewportData, isStudyLoaded, snackbar]);
|
||||
|
||||
const getViewportPanes = () =>
|
||||
layout.viewports.map((layout, viewportIndex) => {
|
||||
@@ -63,6 +80,7 @@ const ViewportGrid = function (props) {
|
||||
// - When updating a panel, ensure that the currently enabled plugin
|
||||
// in the viewport is capable of rendering this display set. If not
|
||||
// then use the most capable available plugin
|
||||
|
||||
const pluginName =
|
||||
!layout.plugin && displaySet && displaySet.plugin
|
||||
? displaySet.plugin
|
||||
|
||||
@@ -4,7 +4,7 @@ import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import './ViewportPane.css';
|
||||
|
||||
const ViewportPane = function(props) {
|
||||
const ViewportPane = function (props) {
|
||||
const { children, onDrop, viewportIndex, className: propClassName } = props;
|
||||
const [{ hovered, highlighted }, drop] = useDrop({
|
||||
accept: 'thumbnail',
|
||||
|
||||
@@ -4,6 +4,8 @@ import { StudyBrowser } from '@ohif/ui';
|
||||
import cloneDeep from 'lodash.clonedeep';
|
||||
import findDisplaySetByUID from './findDisplaySetByUID';
|
||||
|
||||
const { studyMetadataManager } = OHIF.utils;
|
||||
|
||||
const { setActiveViewportSpecificData } = OHIF.redux.actions;
|
||||
|
||||
// TODO
|
||||
@@ -39,11 +41,27 @@ const mapStateToProps = (state, ownProps) => {
|
||||
const mapDispatchToProps = (dispatch, ownProps) => {
|
||||
return {
|
||||
onThumbnailClick: displaySetInstanceUID => {
|
||||
const displaySet = findDisplaySetByUID(
|
||||
let displaySet = findDisplaySetByUID(
|
||||
ownProps.studyMetadata,
|
||||
displaySetInstanceUID
|
||||
);
|
||||
|
||||
if (displaySet.isDerived) {
|
||||
const { Modality } = displaySet;
|
||||
|
||||
displaySet = displaySet.getSourceDisplaySet(ownProps.studyMetadata);
|
||||
|
||||
if (!displaySet) {
|
||||
throw new Error(
|
||||
`Referenced series for ${Modality} dataset not present.`
|
||||
);
|
||||
}
|
||||
|
||||
if (!displaySet) {
|
||||
throw new Error('Source data not present');
|
||||
}
|
||||
}
|
||||
|
||||
dispatch(setActiveViewportSpecificData(displaySet));
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
// TODO: REPLACE THIS WITH A CONTEXT PROVIDER
|
||||
// EVERYTHING IN `VIEWER.JS` COULD USE THIS FOR APPROPRIATE CONTEXT
|
||||
import ToolbarRow from './ToolbarRow';
|
||||
import { connect } from 'react-redux';
|
||||
import { getActiveContexts } from './../store/layout/selectors.js';
|
||||
|
||||
const mapStateToProps = state => {
|
||||
return {
|
||||
activeContexts: getActiveContexts(state),
|
||||
};
|
||||
};
|
||||
|
||||
const ConnectedToolbarRow = connect(mapStateToProps)(ToolbarRow);
|
||||
|
||||
export default ConnectedToolbarRow;
|
||||
Loaded 100 of 111 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user