diff --git a/extensions/cornerstone/package.json b/extensions/cornerstone/package.json index 8ba04384de6..9d0dcb80deb 100644 --- a/extensions/cornerstone/package.json +++ b/extensions/cornerstone/package.json @@ -36,7 +36,7 @@ "cornerstone-math": "^0.1.8", "cornerstone-tools": "4.12.5", "cornerstone-wado-image-loader": "^3.1.0", - "dcmjs": "^0.12.2", + "dcmjs": "^0.12.3", "dicom-parser": "^1.8.3", "hammerjs": "^2.0.8", "prop-types": "^15.6.2", diff --git a/extensions/dicom-html/package.json b/extensions/dicom-html/package.json index 5628aed9b17..8dca4f8a139 100644 --- a/extensions/dicom-html/package.json +++ b/extensions/dicom-html/package.json @@ -29,7 +29,7 @@ }, "peerDependencies": { "@ohif/core": "^0.50.0", - "dcmjs": "^0.12.2", + "dcmjs": "^0.12.3", "prop-types": "^15.6.2", "react": "^16.8.6", "react-dom": "^16.8.6" diff --git a/extensions/dicom-rt/package.json b/extensions/dicom-rt/package.json index 85be0531a89..5a2ed51f087 100644 --- a/extensions/dicom-rt/package.json +++ b/extensions/dicom-rt/package.json @@ -31,7 +31,7 @@ "@ohif/core": "^0.50.0", "cornerstone-core": "^2.2.8", "cornerstone-tools": "^4.0.9", - "dcmjs": "^0.12.2", + "dcmjs": "^0.12.3", "prop-types": "^15.6.2", "react": "^16.8.6", "react-dom": "^16.8.6" diff --git a/extensions/dicom-segmentation/src/OHIFDicomSegSopClassHandler.js b/extensions/dicom-segmentation/src/OHIFDicomSegSopClassHandler.js deleted file mode 100644 index c6ada274698..00000000000 --- a/extensions/dicom-segmentation/src/OHIFDicomSegSopClassHandler.js +++ /dev/null @@ -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; diff --git a/extensions/dicom-segmentation/src/components/SegmentationPanel/SegmentationPanel.js b/extensions/dicom-segmentation/src/components/SegmentationPanel/SegmentationPanel.js index e532d9d8119..a0c0d8bb45d 100644 --- a/extensions/dicom-segmentation/src/components/SegmentationPanel/SegmentationPanel.js +++ b/extensions/dicom-segmentation/src/components/SegmentationPanel/SegmentationPanel.js @@ -41,6 +41,7 @@ const SegmentationPanel = ({ activeIndex, isOpen, onSegItemClick, + UINotificationService, }) => { /* * TODO: wrap get/set interactions with the cornerstoneTools @@ -115,7 +116,11 @@ const SegmentationPanel = ({ firstImageId, activeViewport ); - const segmentList = getSegmentList(labelmap3D, firstImageId, brushStackState); + const segmentList = getSegmentList( + labelmap3D, + firstImageId, + brushStackState + ); setState(state => ({ ...state, brushStackState, @@ -130,7 +135,14 @@ const SegmentationPanel = ({ segmentList: [], })); } - }, [studies, viewports, activeIndex, getLabelmapList, getSegmentList, state.selectedSegmentation]); + }, [ + studies, + viewports, + activeIndex, + getLabelmapList, + getSegmentList, + state.selectedSegmentation, + ]); /* Handle open/closed panel behaviour */ useEffect(() => { @@ -170,7 +182,8 @@ const SegmentationPanel = ({ studies, displaySet, firstImageId, - brushStackState.activeLabelmapIndex + brushStackState.activeLabelmapIndex, + UINotificationService ); updateState('selectedSegmentation', activatedLabelmapIndex); }, @@ -424,7 +437,9 @@ const SegmentationPanel = ({

Segmentations

i.value === state.selectedSegmentation)} + value={state.labelmapList.find( + i => i.value === state.selectedSegmentation + )} formatOptionLabel={SegmentationItem} options={state.labelmapList} /> @@ -509,7 +524,8 @@ const _setActiveLabelmap = async ( studies, displaySet, firstImageId, - activeLabelmapIndex + activeLabelmapIndex, + UINotificationService ) => { if (displaySet.labelmapIndex === activeLabelmapIndex) { log.warn(`${activeLabelmapIndex} is already the active labelmap`); @@ -519,7 +535,22 @@ const _setActiveLabelmap = async ( if (!displaySet.isLoaded) { // What props does this expect `viewportSpecificData` to have? // TODO: Should this return the `labelmapIndex`? - await displaySet.load(viewportSpecificData, studies); + + const loadPromise = displaySet.load(viewportSpecificData, studies); + + loadPromise.catch(error => { + UINotificationService.show({ + title: 'DICOM Segmentation Loader', + message: error.message, + type: 'error', + autoClose: false, + }); + + // Return old index. + return activeLabelmapIndex; + }); + + await loadPromise; } const { state } = cornerstoneTools.getModule('segmentation'); diff --git a/extensions/dicom-segmentation/src/getOHIFDicomSegSopClassHandler.js b/extensions/dicom-segmentation/src/getOHIFDicomSegSopClassHandler.js new file mode 100644 index 00000000000..f589f6e5411 --- /dev/null +++ b/extensions/dicom-segmentation/src/getOHIFDicomSegSopClassHandler.js @@ -0,0 +1,64 @@ +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); + +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.load = function(referencedDisplaySet, studies) { + return loadSegmentation(segDisplaySet, referencedDisplaySet, studies); + }; + + return segDisplaySet; + }, + }; +} diff --git a/extensions/dicom-segmentation/src/index.js b/extensions/dicom-segmentation/src/index.js index 22a146326f9..188dd1fb562 100644 --- a/extensions/dicom-segmentation/src/index.js +++ b/extensions/dicom-segmentation/src/index.js @@ -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,20 @@ export default { getToolbarModule({ servicesManager }) { return toolbarModule; }, - getPanelModule({ commandsManager }) { + getPanelModule({ commandsManager, servicesManager }) { const ExtendedSegmentationPanel = props => { const segItemClickHandler = segData => { commandsManager.runCommand('jumpToImage', segData); }; + const { UINotificationService } = servicesManager.services; + return ( - + ); }; @@ -72,7 +78,5 @@ export default { defaultContext: ['VIEWER'], }; }, - getSopClassHandlerModule({ servicesManager }) { - return sopClassHandlerModule; - }, + getSopClassHandlerModule, }; diff --git a/extensions/dicom-segmentation/src/loadSegmentation.js b/extensions/dicom-segmentation/src/loadSegmentation.js index 1244b52c902..b0f9dde9b5e 100644 --- a/extensions/dicom-segmentation/src/loadSegmentation.js +++ b/extensions/dicom-segmentation/src/loadSegmentation.js @@ -34,28 +34,35 @@ export default async function loadSegmentation( referencedDisplaySet.SeriesInstanceUID ); - const results = _parseSeg(segArrayBuffer, imageIds); + return new Promise((resolve, reject) => { + let results; + + try { + results = _parseSeg(segArrayBuffer, imageIds); + } catch (error) { + segDisplaySet.isLoaded = false; + reject(error); + } - if (!results) { - throw new Error('Fractional segmentations are not yet supported'); - } + 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]); + setters.labelmap3DByFirstImageId( + imageIds[0], + labelmapBuffer, + labelmapIndex, + segMetadata, + imageIds.length, + segmentsOnFrame + ); - setters.labelmap3DByFirstImageId( - imageIds[0], - labelmapBuffer, - labelmapIndex, - segMetadata, - imageIds.length, - segmentsOnFrame - ); + segDisplaySet.labelmapIndex = labelmapIndex; - segDisplaySet.labelmapIndex = labelmapIndex; + resolve(labelmapIndex); + }); } function _getNextLabelmapIndex(firstImageId) { diff --git a/platform/core/src/utils/loadAndCacheDerivedDisplaySets.js b/platform/core/src/utils/loadAndCacheDerivedDisplaySets.js index b7ab4fdbe9b..b32eb581b2c 100644 --- a/platform/core/src/utils/loadAndCacheDerivedDisplaySets.js +++ b/platform/core/src/utils/loadAndCacheDerivedDisplaySets.js @@ -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 @@ -100,8 +102,10 @@ const loadAndCacheDerivedDisplaySets = (referencedDisplaySet, studies) => { } }); - recentDisplaySet.load(referencedDisplaySet, studies); + promises.push(recentDisplaySet.load(referencedDisplaySet, studies)); }); + + return promises; }; export default loadAndCacheDerivedDisplaySets; diff --git a/platform/ui/src/components/snackbar/Snackbar.css b/platform/ui/src/components/snackbar/Snackbar.css index 8b0ee787e4e..bb99cefe6b6 100644 --- a/platform/ui/src/components/snackbar/Snackbar.css +++ b/platform/ui/src/components/snackbar/Snackbar.css @@ -127,7 +127,7 @@ .sb-message { font-size: 14px; - word-break: break-all; + word-break: normal; } .sb-item { diff --git a/platform/viewer/src/components/ViewportGrid/ViewportGrid.js b/platform/viewer/src/components/ViewportGrid/ViewportGrid.js index 7dbd126e609..082ec6eb112 100644 --- a/platform/viewer/src/components/ViewportGrid/ViewportGrid.js +++ b/platform/viewer/src/components/ViewportGrid/ViewportGrid.js @@ -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, @@ -33,9 +34,22 @@ const ViewportGrid = function (props) { return null; } + const snackbar = useSnackbarContext(); + useEffect(() => { viewportData.forEach(displaySet => { - loadAndCacheDerivedDisplaySets(displaySet, studies); + 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]);