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:
parent
9ea37a2201
commit
5b20d8f323
@ -36,7 +36,7 @@
|
|||||||
"cornerstone-math": "^0.1.8",
|
"cornerstone-math": "^0.1.8",
|
||||||
"cornerstone-tools": "4.12.5",
|
"cornerstone-tools": "4.12.5",
|
||||||
"cornerstone-wado-image-loader": "^3.1.0",
|
"cornerstone-wado-image-loader": "^3.1.0",
|
||||||
"dcmjs": "^0.12.2",
|
"dcmjs": "^0.12.3",
|
||||||
"dicom-parser": "^1.8.3",
|
"dicom-parser": "^1.8.3",
|
||||||
"hammerjs": "^2.0.8",
|
"hammerjs": "^2.0.8",
|
||||||
"prop-types": "^15.6.2",
|
"prop-types": "^15.6.2",
|
||||||
|
|||||||
@ -29,7 +29,7 @@
|
|||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@ohif/core": "^0.50.0",
|
"@ohif/core": "^0.50.0",
|
||||||
"dcmjs": "^0.12.2",
|
"dcmjs": "^0.12.3",
|
||||||
"prop-types": "^15.6.2",
|
"prop-types": "^15.6.2",
|
||||||
"react": "^16.8.6",
|
"react": "^16.8.6",
|
||||||
"react-dom": "^16.8.6"
|
"react-dom": "^16.8.6"
|
||||||
|
|||||||
@ -31,7 +31,7 @@
|
|||||||
"@ohif/core": "^0.50.0",
|
"@ohif/core": "^0.50.0",
|
||||||
"cornerstone-core": "^2.2.8",
|
"cornerstone-core": "^2.2.8",
|
||||||
"cornerstone-tools": "^4.0.9",
|
"cornerstone-tools": "^4.0.9",
|
||||||
"dcmjs": "^0.12.2",
|
"dcmjs": "^0.12.3",
|
||||||
"prop-types": "^15.6.2",
|
"prop-types": "^15.6.2",
|
||||||
"react": "^16.8.6",
|
"react": "^16.8.6",
|
||||||
"react-dom": "^16.8.6"
|
"react-dom": "^16.8.6"
|
||||||
|
|||||||
@ -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;
|
|
||||||
@ -41,6 +41,7 @@ const SegmentationPanel = ({
|
|||||||
activeIndex,
|
activeIndex,
|
||||||
isOpen,
|
isOpen,
|
||||||
onSegItemClick,
|
onSegItemClick,
|
||||||
|
UINotificationService,
|
||||||
}) => {
|
}) => {
|
||||||
/*
|
/*
|
||||||
* TODO: wrap get/set interactions with the cornerstoneTools
|
* TODO: wrap get/set interactions with the cornerstoneTools
|
||||||
@ -115,7 +116,11 @@ const SegmentationPanel = ({
|
|||||||
firstImageId,
|
firstImageId,
|
||||||
activeViewport
|
activeViewport
|
||||||
);
|
);
|
||||||
const segmentList = getSegmentList(labelmap3D, firstImageId, brushStackState);
|
const segmentList = getSegmentList(
|
||||||
|
labelmap3D,
|
||||||
|
firstImageId,
|
||||||
|
brushStackState
|
||||||
|
);
|
||||||
setState(state => ({
|
setState(state => ({
|
||||||
...state,
|
...state,
|
||||||
brushStackState,
|
brushStackState,
|
||||||
@ -130,7 +135,14 @@ const SegmentationPanel = ({
|
|||||||
segmentList: [],
|
segmentList: [],
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}, [studies, viewports, activeIndex, getLabelmapList, getSegmentList, state.selectedSegmentation]);
|
}, [
|
||||||
|
studies,
|
||||||
|
viewports,
|
||||||
|
activeIndex,
|
||||||
|
getLabelmapList,
|
||||||
|
getSegmentList,
|
||||||
|
state.selectedSegmentation,
|
||||||
|
]);
|
||||||
|
|
||||||
/* Handle open/closed panel behaviour */
|
/* Handle open/closed panel behaviour */
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -170,7 +182,8 @@ const SegmentationPanel = ({
|
|||||||
studies,
|
studies,
|
||||||
displaySet,
|
displaySet,
|
||||||
firstImageId,
|
firstImageId,
|
||||||
brushStackState.activeLabelmapIndex
|
brushStackState.activeLabelmapIndex,
|
||||||
|
UINotificationService
|
||||||
);
|
);
|
||||||
updateState('selectedSegmentation', activatedLabelmapIndex);
|
updateState('selectedSegmentation', activatedLabelmapIndex);
|
||||||
},
|
},
|
||||||
@ -424,7 +437,9 @@ const SegmentationPanel = ({
|
|||||||
<h3>Segmentations</h3>
|
<h3>Segmentations</h3>
|
||||||
<div className="segmentations">
|
<div className="segmentations">
|
||||||
<SegmentationSelect
|
<SegmentationSelect
|
||||||
value={state.labelmapList.find(i => i.value === state.selectedSegmentation)}
|
value={state.labelmapList.find(
|
||||||
|
i => i.value === state.selectedSegmentation
|
||||||
|
)}
|
||||||
formatOptionLabel={SegmentationItem}
|
formatOptionLabel={SegmentationItem}
|
||||||
options={state.labelmapList}
|
options={state.labelmapList}
|
||||||
/>
|
/>
|
||||||
@ -509,7 +524,8 @@ const _setActiveLabelmap = async (
|
|||||||
studies,
|
studies,
|
||||||
displaySet,
|
displaySet,
|
||||||
firstImageId,
|
firstImageId,
|
||||||
activeLabelmapIndex
|
activeLabelmapIndex,
|
||||||
|
UINotificationService
|
||||||
) => {
|
) => {
|
||||||
if (displaySet.labelmapIndex === activeLabelmapIndex) {
|
if (displaySet.labelmapIndex === activeLabelmapIndex) {
|
||||||
log.warn(`${activeLabelmapIndex} is already the active labelmap`);
|
log.warn(`${activeLabelmapIndex} is already the active labelmap`);
|
||||||
@ -519,7 +535,22 @@ const _setActiveLabelmap = async (
|
|||||||
if (!displaySet.isLoaded) {
|
if (!displaySet.isLoaded) {
|
||||||
// What props does this expect `viewportSpecificData` to have?
|
// What props does this expect `viewportSpecificData` to have?
|
||||||
// TODO: Should this return the `labelmapIndex`?
|
// 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');
|
const { state } = cornerstoneTools.getModule('segmentation');
|
||||||
|
|||||||
@ -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;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -2,7 +2,7 @@ import React from 'react';
|
|||||||
|
|
||||||
import init from './init.js';
|
import init from './init.js';
|
||||||
import toolbarModule from './toolbarModule.js';
|
import toolbarModule from './toolbarModule.js';
|
||||||
import sopClassHandlerModule from './OHIFDicomSegSopClassHandler.js';
|
import getSopClassHandlerModule from './getOHIFDicomSegSopClassHandler.js';
|
||||||
import SegmentationPanel from './components/SegmentationPanel/SegmentationPanel.js';
|
import SegmentationPanel from './components/SegmentationPanel/SegmentationPanel.js';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@ -23,14 +23,20 @@ export default {
|
|||||||
getToolbarModule({ servicesManager }) {
|
getToolbarModule({ servicesManager }) {
|
||||||
return toolbarModule;
|
return toolbarModule;
|
||||||
},
|
},
|
||||||
getPanelModule({ commandsManager }) {
|
getPanelModule({ commandsManager, servicesManager }) {
|
||||||
const ExtendedSegmentationPanel = props => {
|
const ExtendedSegmentationPanel = props => {
|
||||||
const segItemClickHandler = segData => {
|
const segItemClickHandler = segData => {
|
||||||
commandsManager.runCommand('jumpToImage', segData);
|
commandsManager.runCommand('jumpToImage', segData);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const { UINotificationService } = servicesManager.services;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SegmentationPanel {...props} onSegItemClick={segItemClickHandler} />
|
<SegmentationPanel
|
||||||
|
{...props}
|
||||||
|
onSegItemClick={segItemClickHandler}
|
||||||
|
UINotificationService={UINotificationService}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -72,7 +78,5 @@ export default {
|
|||||||
defaultContext: ['VIEWER'],
|
defaultContext: ['VIEWER'],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
getSopClassHandlerModule({ servicesManager }) {
|
getSopClassHandlerModule,
|
||||||
return sopClassHandlerModule;
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|||||||
@ -34,28 +34,35 @@ export default async function loadSegmentation(
|
|||||||
referencedDisplaySet.SeriesInstanceUID
|
referencedDisplaySet.SeriesInstanceUID
|
||||||
);
|
);
|
||||||
|
|
||||||
const results = _parseSeg(segArrayBuffer, imageIds);
|
return new Promise((resolve, reject) => {
|
||||||
|
let results;
|
||||||
|
|
||||||
if (!results) {
|
try {
|
||||||
throw new Error('Fractional segmentations are not yet supported');
|
results = _parseSeg(segArrayBuffer, imageIds);
|
||||||
}
|
} catch (error) {
|
||||||
|
segDisplaySet.isLoaded = false;
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
|
||||||
const { labelmapBuffer, segMetadata, segmentsOnFrame } = results;
|
const { labelmapBuffer, segMetadata, segmentsOnFrame } = results;
|
||||||
const { setters } = cornerstoneTools.getModule('segmentation');
|
const { setters } = cornerstoneTools.getModule('segmentation');
|
||||||
|
|
||||||
// TODO: Could define a color LUT based on colors in the SEG.
|
// TODO: Could define a color LUT based on colors in the SEG.
|
||||||
const labelmapIndex = _getNextLabelmapIndex(imageIds[0]);
|
const labelmapIndex = _getNextLabelmapIndex(imageIds[0]);
|
||||||
|
|
||||||
setters.labelmap3DByFirstImageId(
|
setters.labelmap3DByFirstImageId(
|
||||||
imageIds[0],
|
imageIds[0],
|
||||||
labelmapBuffer,
|
labelmapBuffer,
|
||||||
labelmapIndex,
|
labelmapIndex,
|
||||||
segMetadata,
|
segMetadata,
|
||||||
imageIds.length,
|
imageIds.length,
|
||||||
segmentsOnFrame
|
segmentsOnFrame
|
||||||
);
|
);
|
||||||
|
|
||||||
segDisplaySet.labelmapIndex = labelmapIndex;
|
segDisplaySet.labelmapIndex = labelmapIndex;
|
||||||
|
|
||||||
|
resolve(labelmapIndex);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function _getNextLabelmapIndex(firstImageId) {
|
function _getNextLabelmapIndex(firstImageId) {
|
||||||
|
|||||||
@ -50,10 +50,12 @@ import studyMetadataManager from './studyMetadataManager';
|
|||||||
const loadAndCacheDerivedDisplaySets = (referencedDisplaySet, studies) => {
|
const loadAndCacheDerivedDisplaySets = (referencedDisplaySet, studies) => {
|
||||||
const { StudyInstanceUID, SeriesInstanceUID } = referencedDisplaySet;
|
const { StudyInstanceUID, SeriesInstanceUID } = referencedDisplaySet;
|
||||||
|
|
||||||
|
const promises = [];
|
||||||
|
|
||||||
const studyMetadata = studyMetadataManager.get(StudyInstanceUID);
|
const studyMetadata = studyMetadataManager.get(StudyInstanceUID);
|
||||||
|
|
||||||
if (!studyMetadata) {
|
if (!studyMetadata) {
|
||||||
return;
|
return promises;
|
||||||
}
|
}
|
||||||
|
|
||||||
const derivedDisplaySets = studyMetadata.getDerivedDatasets({
|
const derivedDisplaySets = studyMetadata.getDerivedDatasets({
|
||||||
@ -61,7 +63,7 @@ const loadAndCacheDerivedDisplaySets = (referencedDisplaySet, studies) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!derivedDisplaySets.length) {
|
if (!derivedDisplaySets.length) {
|
||||||
return;
|
return promises;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filter by type
|
// 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;
|
export default loadAndCacheDerivedDisplaySets;
|
||||||
|
|||||||
@ -127,7 +127,7 @@
|
|||||||
|
|
||||||
.sb-message {
|
.sb-message {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
word-break: break-all;
|
word-break: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sb-item {
|
.sb-item {
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import React, { useEffect } from 'react';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { utils } from '@ohif/core';
|
import { utils } from '@ohif/core';
|
||||||
|
import { useSnackbarContext } from '@ohif/ui';
|
||||||
//
|
//
|
||||||
import ViewportPane from './ViewportPane.js';
|
import ViewportPane from './ViewportPane.js';
|
||||||
import DefaultViewport from './DefaultViewport.js';
|
import DefaultViewport from './DefaultViewport.js';
|
||||||
@ -11,7 +12,7 @@ import EmptyViewport from './EmptyViewport.js';
|
|||||||
|
|
||||||
const { loadAndCacheDerivedDisplaySets } = utils;
|
const { loadAndCacheDerivedDisplaySets } = utils;
|
||||||
|
|
||||||
const ViewportGrid = function (props) {
|
const ViewportGrid = function(props) {
|
||||||
const {
|
const {
|
||||||
activeViewportIndex,
|
activeViewportIndex,
|
||||||
availablePlugins,
|
availablePlugins,
|
||||||
@ -33,9 +34,22 @@ const ViewportGrid = function (props) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const snackbar = useSnackbarContext();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
viewportData.forEach(displaySet => {
|
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]);
|
}, [studies, viewportData]);
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user