diff --git a/extensions/dicom-rt/src/OHIFDicomRTStructSopClassHandler.js b/extensions/dicom-rt/src/OHIFDicomRTStructSopClassHandler.js index 6561cf312..5095e82bf 100644 --- a/extensions/dicom-rt/src/OHIFDicomRTStructSopClassHandler.js +++ b/extensions/dicom-rt/src/OHIFDicomRTStructSopClassHandler.js @@ -1,5 +1,6 @@ import { MODULE_TYPES, utils, DICOMWeb } from '@ohif/core'; import loadRTStruct from './loadRTStruct'; +import getSourceDisplaySet from './getSourceDisplaySet'; import id from './id'; @@ -67,6 +68,10 @@ const OHIFDicomRTStructSopClassHandler = { } } + rtStructDisplaySet.getSourceDisplaySet = function(studies) { + return getSourceDisplaySet(studies, rtStructDisplaySet); + }; + rtStructDisplaySet.load = function(referencedDisplaySet, studies) { return loadRTStruct( rtStructDisplaySet, diff --git a/extensions/dicom-rt/src/components/RTPanel/RTPanel.js b/extensions/dicom-rt/src/components/RTPanel/RTPanel.js index 488041bd3..4e1e46c71 100644 --- a/extensions/dicom-rt/src/components/RTPanel/RTPanel.js +++ b/extensions/dicom-rt/src/components/RTPanel/RTPanel.js @@ -242,7 +242,7 @@ const RTPanel = ({ key={SeriesInstanceUID} title={metadata.StructureSetLabel} loading={!isLoaded || !loadedSet} - visible={isLoaded && loadedSet.visible} + visible={isLoaded && loadedSet && loadedSet.visible} hideVisibleButton={!isLoaded} expanded={ isLoaded && diff --git a/extensions/dicom-rt/src/getSourceDisplaySet.js b/extensions/dicom-rt/src/getSourceDisplaySet.js new file mode 100644 index 000000000..e31c036c1 --- /dev/null +++ b/extensions/dicom-rt/src/getSourceDisplaySet.js @@ -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; +}; diff --git a/extensions/dicom-segmentation/src/components/SegmentationPanel/SegmentationPanel.js b/extensions/dicom-segmentation/src/components/SegmentationPanel/SegmentationPanel.js index 5b50ba025..79936efee 100644 --- a/extensions/dicom-segmentation/src/components/SegmentationPanel/SegmentationPanel.js +++ b/extensions/dicom-segmentation/src/components/SegmentationPanel/SegmentationPanel.js @@ -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 * @@ -68,8 +65,7 @@ const SegmentationPanel = ({ */ const [state, setState] = useState({ brushRadius: DEFAULT_BRUSH_RADIUS, - brushColor: - 'rgba(221, 85, 85, 1)', + brushColor: 'rgba(221, 85, 85, 1)', selectedSegment: null, selectedSegmentation: null, showSegmentationSettings: false, @@ -78,7 +74,7 @@ const SegmentationPanel = ({ segmentList: [], cachedSegmentsProperties: [], isLoading: false, - isDisabled: true + isDisabled: true, }); useEffect(() => { @@ -112,7 +108,10 @@ const SegmentationPanel = ({ * 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); + document.addEventListener( + 'extensiondicomsegmentationsegloaded', + refreshSegmentations + ); /* * These are specific to each element; @@ -127,7 +126,10 @@ const SegmentationPanel = ({ ); return () => { - document.removeEventListener('extensiondicomsegmentationsegloaded', refreshSegmentations); + document.removeEventListener( + 'extensiondicomsegmentationsegloaded', + refreshSegmentations + ); cornerstoneTools.store.state.enabledElements.forEach(enabledElement => enabledElement.removeEventListener( 'cornerstonetoolslabelmapmodified', @@ -169,26 +171,29 @@ const SegmentationPanel = ({ selectedSegmentation: brushStackState.activeLabelmapIndex, labelmapList, segmentList, - isDisabled + isDisabled, })); } else { setState(state => ({ ...state, labelmapList: [], segmentList: [], - isDisabled + isDisabled, })); } } - }, [ - viewports, - activeIndex, - state.isLoading - ]); + }, [viewports, activeIndex, state.isLoading]); useEffect(() => { refreshSegmentations(); - }, [viewports, activeIndex, isOpen, state.selectedSegmentation, activeContexts, state.isLoading]); + }, [ + viewports, + activeIndex, + isOpen, + state.selectedSegmentation, + activeContexts, + state.isLoading, + ]); /* Handle open/closed panel behaviour */ useEffect(() => { @@ -223,12 +228,10 @@ const SegmentationPanel = ({ title: displayDescription, description: displayDate, onClick: async () => { - const activatedLabelmapIndex = await _setActiveLabelmap( + const activatedLabelmapIndex = await setActiveLabelmap( activeViewport, studies, displaySet, - firstImageId, - brushStackState.activeLabelmapIndex, () => onSelectedSegmentationChange(), onDisplaySetLoadFailure ); @@ -376,9 +379,13 @@ const SegmentationPanel = ({ return !segmentsHidden[segmentIndex]; }; - const cachedSegmentProperties = state.cachedSegmentsProperties[segmentNumber]; + const cachedSegmentProperties = + state.cachedSegmentsProperties[segmentNumber]; let visible = isSegmentVisible(); - if (cachedSegmentProperties && cachedSegmentProperties.visible !== visible) { + if ( + cachedSegmentProperties && + cachedSegmentProperties.visible !== visible + ) { toggleSegmentVisibility(); } @@ -406,8 +413,10 @@ const SegmentationPanel = ({ onSegmentVisibilityChange(segmentNumber, newVisibility); } - updateCachedSegmentsProperties(segmentNumber, { visible: newVisibility }); - refreshViewport(); + updateCachedSegmentsProperties(segmentNumber, { + visible: newVisibility, + }); + refreshViewports(); }} /> ); @@ -429,10 +438,9 @@ const SegmentationPanel = ({ const segmentsProperties = state.cachedSegmentsProperties; const segmentProperties = state.cachedSegmentsProperties[segmentNumber]; - segmentsProperties[segmentNumber] = - segmentProperties ? - { ...segmentProperties, ...properties } : - properties; + segmentsProperties[segmentNumber] = segmentProperties + ? { ...segmentProperties, ...properties } + : properties; updateState('cachedSegmentsProperties', segmentsProperties); }; @@ -499,10 +507,13 @@ const SegmentationPanel = ({ configuration.fillAlphaInactive = newConfiguration.fillAlphaInactive; configuration.outlineAlphaInactive = newConfiguration.outlineAlphaInactive; onConfigurationChange(newConfiguration); - refreshViewport(); + refreshViewports(); }; - const disabledConfigurationFields = ['outlineAlpha', 'shouldRenderInactiveLabelmaps']; + const disabledConfigurationFields = [ + 'outlineAlpha', + 'shouldRenderInactiveLabelmaps', + ]; if (state.showSegmentationSettings) { return ( +
{ return referencedDisplaysets; }; -/** - * - * - * @param {*} viewportSpecificData - * @param {*} studies - * @param {*} displaySet - * @param {*} firstImageId - * @param {*} activeLabelmapIndex - * @returns - */ -const _setActiveLabelmap = async ( - viewportSpecificData, - studies, - displaySet, - firstImageId, - activeLabelmapIndex, - callback = () => { }, - onDisplaySetLoadFailure -) => { - 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`? - - const loadPromise = displaySet.load(viewportSpecificData, studies); - - loadPromise.catch(error => { - onDisplaySetLoadFailure(error); - - // Return old index. - return activeLabelmapIndex; - }); - - await loadPromise; - } - - const { state } = cornerstoneTools.getModule('segmentation'); - const brushStackState = state.series[firstImageId]; - brushStackState.activeLabelmapIndex = displaySet.labelmapIndex; - - refreshViewport(); - - callback(); - - return displaySet.labelmapIndex; -}; - /** * * @param {*} firstImageId @@ -683,7 +646,7 @@ const _setActiveSegment = (firstImageId, segmentIndex, activeSegmentIndex) => { brushStackState.labelmaps3D[brushStackState.activeLabelmapIndex]; labelmap3D.activeSegmentIndex = segmentIndex; - refreshViewport(); + refreshViewports(); return segmentIndex; }; diff --git a/extensions/dicom-segmentation/src/getOHIFDicomSegSopClassHandler.js b/extensions/dicom-segmentation/src/getOHIFDicomSegSopClassHandler.js index f589f6e54..a396ad92d 100644 --- a/extensions/dicom-segmentation/src/getOHIFDicomSegSopClassHandler.js +++ b/extensions/dicom-segmentation/src/getOHIFDicomSegSopClassHandler.js @@ -1,5 +1,6 @@ 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 = { @@ -54,6 +55,10 @@ export default function getSopClassHandlerModule({ servicesManager }) { SeriesDescription, }; + segDisplaySet.getSourceDisplaySet = function(studies) { + return getSourceDisplaySet(studies, segDisplaySet); + }; + segDisplaySet.load = function(referencedDisplaySet, studies) { return loadSegmentation(segDisplaySet, referencedDisplaySet, studies); }; diff --git a/extensions/dicom-segmentation/src/getSourceDisplaySet.js b/extensions/dicom-segmentation/src/getSourceDisplaySet.js new file mode 100644 index 000000000..f7b62bd09 --- /dev/null +++ b/extensions/dicom-segmentation/src/getSourceDisplaySet.js @@ -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; +}; diff --git a/extensions/dicom-segmentation/src/utils/refreshViewports.js b/extensions/dicom-segmentation/src/utils/refreshViewports.js new file mode 100644 index 000000000..4f7fbcd5c --- /dev/null +++ b/extensions/dicom-segmentation/src/utils/refreshViewports.js @@ -0,0 +1,5 @@ +export default function refreshViewports() { + cornerstone.getEnabledElements().forEach(enabledElement => { + cornerstone.updateImage(enabledElement.element); + }); +} diff --git a/extensions/dicom-segmentation/src/utils/setActiveLabelMap.js b/extensions/dicom-segmentation/src/utils/setActiveLabelMap.js new file mode 100644 index 000000000..ec5535b3a --- /dev/null +++ b/extensions/dicom-segmentation/src/utils/setActiveLabelMap.js @@ -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; +} diff --git a/platform/core/src/classes/metadata/StudyMetadata.js b/platform/core/src/classes/metadata/StudyMetadata.js index 459a0e441..c8d02cac7 100644 --- a/platform/core/src/classes/metadata/StudyMetadata.js +++ b/platform/core/src/classes/metadata/StudyMetadata.js @@ -139,9 +139,11 @@ export class StudyMetadata extends Metadata { if (displaySet) { displaySet.sopClassModule = true; - displaySet.isDerived - ? this._addDerivedDisplaySet(displaySet) - : displaySets.push(displaySet); + if (displaySet.isDerived) { + this._addDerivedDisplaySet(displaySet); + } + + displaySets.push(displaySet); return displaySets; } diff --git a/platform/viewer/cypress/integration/pwa/OHIFExtensionMicroscopy.spec.js b/platform/viewer/cypress/integration/pwa/OHIFExtensionMicroscopy.spec.js index c650e86f9..20afac8e2 100644 --- a/platform/viewer/cypress/integration/pwa/OHIFExtensionMicroscopy.spec.js +++ b/platform/viewer/cypress/integration/pwa/OHIFExtensionMicroscopy.spec.js @@ -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'); diff --git a/platform/viewer/src/components/ViewportGrid/ViewportGrid.js b/platform/viewer/src/components/ViewportGrid/ViewportGrid.js index cb52c06c6..7f0449403 100644 --- a/platform/viewer/src/components/ViewportGrid/ViewportGrid.js +++ b/platform/viewer/src/components/ViewportGrid/ViewportGrid.js @@ -80,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 diff --git a/platform/viewer/src/connectedComponents/ConnectedStudyBrowser.js b/platform/viewer/src/connectedComponents/ConnectedStudyBrowser.js index 624837c67..e0e277117 100644 --- a/platform/viewer/src/connectedComponents/ConnectedStudyBrowser.js +++ b/platform/viewer/src/connectedComponents/ConnectedStudyBrowser.js @@ -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)); }, }; diff --git a/platform/viewer/src/connectedComponents/ViewerMain.js b/platform/viewer/src/connectedComponents/ViewerMain.js index 6a9589495..97992ecca 100644 --- a/platform/viewer/src/connectedComponents/ViewerMain.js +++ b/platform/viewer/src/connectedComponents/ViewerMain.js @@ -133,12 +133,23 @@ class ViewerMain extends Component { StudyInstanceUID, displaySetInstanceUID, }) => { - const displaySet = this.findDisplaySet( + let displaySet = this.findDisplaySet( this.props.studies, StudyInstanceUID, displaySetInstanceUID ); + if (displaySet.isDerived) { + const { Modality } = displaySet; + displaySet = displaySet.getSourceDisplaySet(this.props.studies); + + if (!displaySet) { + throw new Error( + `Referenced series for ${Modality} dataset not present.` + ); + } + } + this.props.setViewportSpecificData(viewportIndex, displaySet); };