import { utils, Types as OhifTypes } from '@ohif/core'; import i18n from '@ohif/i18n'; import { segmentation as cstSegmentation } from '@cornerstonejs/tools'; import { SOPClassHandlerId } from './id'; import loadRTStruct from './loadRTStruct'; const { sopClassDictionary } = utils; const sopClassUids = [sopClassDictionary.RTStructureSetStorage]; const loadPromises = {}; function _getDisplaySetsFromSeries( instances, servicesManager: AppTypes.ServicesManager, extensionManager ) { utils.sortStudyInstances(instances); // Choose the LAST instance in the list as the most recently created one. const instance = instances[instances.length - 1]; const { StudyInstanceUID, SeriesInstanceUID, SOPInstanceUID, SeriesDescription = '', SeriesNumber, SeriesDate, SeriesTime, StructureSetDate, StructureSetTime, SOPClassUID, wadoRoot, wadoUri, wadoUriRoot, imageId: predecessorImageId, } = instance; const displaySet = { Modality: 'RTSTRUCT', loading: false, isReconstructable: false, displaySetInstanceUID: utils.guid(), SeriesDescription, SeriesNumber, /** * The "SeriesDate" for a display set is really the display set date, which * should be the date of the instance being used, which will be the structure * set date in this case. */ SeriesDate: StructureSetDate || SeriesDate, SeriesTime: StructureSetTime || SeriesTime, SOPInstanceUID, SeriesInstanceUID, StudyInstanceUID, SOPClassHandlerId, SOPClassUID, frameOfReferenceUID: null, referencedImages: null, referencedSeriesInstanceUID: null, referencedDisplaySetInstanceUID: null, isDerivedDisplaySet: true, isLoaded: false, isHydrated: false, structureSet: null, sopClassUids, instance, instances, predecessorImageId, wadoRoot, wadoUriRoot, wadoUri, isOverlayDisplaySet: true, label: SeriesDescription || `${i18n.t('Series')} ${SeriesNumber} - ${i18n.t('RTSTRUCT')}`, }; let referencedSeriesSequence = instance.ReferencedSeriesSequence; if ( instance.ReferencedFrameOfReferenceSequence?.RTReferencedStudySequence && !instance.ReferencedSeriesSequence ) { instance.ReferencedSeriesSequence = _deriveReferencedSeriesSequenceFromFrameOfReferenceSequence( instance.ReferencedFrameOfReferenceSequence ); referencedSeriesSequence = instance.ReferencedSeriesSequence; } if (!referencedSeriesSequence) { console.error('ReferencedSeriesSequence is missing for the RTSTRUCT'); return; } const referencedSeries = referencedSeriesSequence[0]; displaySet.referencedImages = instance.ReferencedSeriesSequence.ReferencedInstanceSequence; displaySet.referencedSeriesInstanceUID = referencedSeries.SeriesInstanceUID; displaySet.frameOfReferenceUID = instance.ReferencedFrameOfReferenceSequence?.[0]?.FrameOfReferenceUID; const { displaySetService } = servicesManager.services; const referencedDisplaySets = displaySetService.getDisplaySetsForReferences(referencedSeriesSequence); if (referencedDisplaySets?.length > 1) { console.warn( 'Reference applies to more than 1 display set for Contours, applying only to first display set' ); } if (!referencedDisplaySets || referencedDisplaySets.length === 0) { // Instead of throwing error, subscribe to display sets added const { unsubscribe } = displaySetService.subscribe( displaySetService.EVENTS.DISPLAY_SETS_ADDED, ({ displaySetsAdded }) => { const addedDisplaySet = displaySetsAdded[0]; if (addedDisplaySet.SeriesInstanceUID === displaySet.referencedSeriesInstanceUID) { displaySet.referencedDisplaySetInstanceUID = addedDisplaySet.displaySetInstanceUID; displaySet.isReconstructable = addedDisplaySet.isReconstructable; displaySet.frameOfReferenceUID = addedDisplaySet.frameOfReferenceUID; unsubscribe(); } } ); } else { const [referencedDisplaySet] = referencedDisplaySets; displaySet.referencedDisplaySetInstanceUID = referencedDisplaySet.displaySetInstanceUID; displaySet.isReconstructable = referencedDisplaySet.isReconstructable; displaySet.frameOfReferenceUID = referencedDisplaySet.frameOfReferenceUID; } displaySet.load = ({ headers, createSegmentation = true }) => _load(displaySet, servicesManager, extensionManager, headers, createSegmentation); return [displaySet]; } function _load( rtDisplaySet, servicesManager: AppTypes.ServicesManager, extensionManager, headers, createSegmentation = true ) { const { SOPInstanceUID } = rtDisplaySet; const { segmentationService } = servicesManager.services; if ( (rtDisplaySet.loading || rtDisplaySet.isLoaded) && loadPromises[SOPInstanceUID] && _segmentationExists(rtDisplaySet) ) { return loadPromises[SOPInstanceUID]; } rtDisplaySet.loading = true; // We don't want to fire multiple loads, so we'll wait for the first to finish // and also return the same promise to any other callers. loadPromises[SOPInstanceUID] = new Promise(async (resolve, reject) => { try { if (!rtDisplaySet.structureSet) { const structureSet = await loadRTStruct(extensionManager, rtDisplaySet, headers); rtDisplaySet.structureSet = structureSet; } if (createSegmentation) { await segmentationService.createSegmentationForRTDisplaySet(rtDisplaySet); } resolve(); } catch (error) { reject(error); } finally { rtDisplaySet.loading = false; } }); return loadPromises[SOPInstanceUID]; } function _deriveReferencedSeriesSequenceFromFrameOfReferenceSequence( ReferencedFrameOfReferenceSequence ) { const ReferencedSeriesSequence = []; ReferencedFrameOfReferenceSequence.forEach(referencedFrameOfReference => { const { RTReferencedStudySequence } = referencedFrameOfReference; RTReferencedStudySequence.forEach(rtReferencedStudy => { const { RTReferencedSeriesSequence } = rtReferencedStudy; RTReferencedSeriesSequence.forEach(rtReferencedSeries => { const ReferencedInstanceSequence = []; const { ContourImageSequence, SeriesInstanceUID } = rtReferencedSeries; ContourImageSequence.forEach(contourImage => { ReferencedInstanceSequence.push({ ReferencedSOPInstanceUID: contourImage.ReferencedSOPInstanceUID, ReferencedSOPClassUID: contourImage.ReferencedSOPClassUID, }); }); const referencedSeries = { SeriesInstanceUID, ReferencedInstanceSequence, }; ReferencedSeriesSequence.push(referencedSeries); }); }); }); return ReferencedSeriesSequence; } function _segmentationExists(segDisplaySet) { return !!cstSegmentation.state.getSegmentation(segDisplaySet.displaySetInstanceUID); } function getSopClassHandlerModule(params: OhifTypes.Extensions.ExtensionParams) { const { servicesManager, extensionManager } = params; return [ { name: 'dicom-rt', sopClassUids, getDisplaySetsFromSeries: instances => { return _getDisplaySetsFromSeries(instances, servicesManager, extensionManager); }, }, ]; } export default getSopClassHandlerModule;