fix: 🐛 Proper error handling for derived display sets (#1708)

* fix: 🐛 Proper error handling for derived display sets

* Revert idc.js change
This commit is contained in:
James Petts 2020-05-04 15:10:39 +01:00 committed by GitHub
parent 9ea37a2201
commit 5b20d8f323
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 162 additions and 109 deletions

View File

@ -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",

View File

@ -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"

View File

@ -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"

View File

@ -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;

View File

@ -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 = ({
<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}
/>
@ -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');

View File

@ -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;
},
};
}

View File

@ -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 (
<SegmentationPanel {...props} onSegItemClick={segItemClickHandler} />
<SegmentationPanel
{...props}
onSegItemClick={segItemClickHandler}
UINotificationService={UINotificationService}
/>
);
};
@ -72,7 +78,5 @@ export default {
defaultContext: ['VIEWER'],
};
},
getSopClassHandlerModule({ servicesManager }) {
return sopClassHandlerModule;
},
getSopClassHandlerModule,
};

View File

@ -34,28 +34,35 @@ 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;
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]);
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) {

View File

@ -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;

View File

@ -127,7 +127,7 @@
.sb-message {
font-size: 14px;
word-break: break-all;
word-break: normal;
}
.sb-item {

View File

@ -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]);