import { utils, Types as OhifTypes } from '@ohif/core'; import i18n from '@ohif/i18n'; import { SOPClassHandlerId } from './id'; import loadRTStruct from './loadRTStruct'; const { sopClassDictionary } = utils; const sopClassUids = [sopClassDictionary.RTStructureSetStorage]; const cachedRTStructsSEG = new Set(); 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, 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; 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; unsubscribe(); } } ); } else { const [referencedDisplaySet] = referencedDisplaySets; displaySet.referencedDisplaySetInstanceUID = referencedDisplaySet.displaySetInstanceUID; displaySet.isReconstructable = referencedDisplaySet.isReconstructable; } 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] && cachedRTStructsSEG.has(rtDisplaySet.displaySetInstanceUID) ) { return loadPromises[SOPInstanceUID]; } rtDisplaySet.loading = true; const { unsubscribe } = segmentationService.subscribe( segmentationService.EVENTS.SEGMENTATION_LOADING_COMPLETE, (evt: { rtDisplaySet: { displaySetInstanceUID: string } }) => { if (evt.rtDisplaySet?.displaySetInstanceUID === rtDisplaySet.displaySetInstanceUID) { cachedRTStructsSEG.add(rtDisplaySet.displaySetInstanceUID); unsubscribe(); } } ); // 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 getSopClassHandlerModule(params: OhifTypes.Extensions.ExtensionParams) { const { servicesManager, extensionManager } = params; return [ { name: 'dicom-rt', sopClassUids, getDisplaySetsFromSeries: instances => { return _getDisplaySetsFromSeries(instances, servicesManager, extensionManager); }, }, ]; } export default getSopClassHandlerModule;