import { utils } from '@ohif/core'; import { metaData, cache, triggerEvent, eventTarget, } from '@cornerstonejs/core'; import { adaptersSEG, Enums } from '@cornerstonejs/adapters'; import { SOPClassHandlerId } from './id'; import { dicomlabToRGB } from './utils/dicomlabToRGB'; const sopClassUids = ['1.2.840.10008.5.1.4.1.1.66.4']; let loadPromises = {}; function _getDisplaySetsFromSeries( instances, servicesManager, extensionManager ) { const instance = instances[0]; const { StudyInstanceUID, SeriesInstanceUID, SOPInstanceUID, SeriesDescription, SeriesNumber, SeriesDate, SOPClassUID, wadoRoot, wadoUri, wadoUriRoot, } = instance; const displaySet = { Modality: 'SEG', loading: false, isReconstructable: true, // by default for now since it is a volumetric SEG currently displaySetInstanceUID: utils.guid(), SeriesDescription, SeriesNumber, SeriesDate, SOPInstanceUID, SeriesInstanceUID, StudyInstanceUID, SOPClassHandlerId, SOPClassUID, referencedImages: null, referencedSeriesInstanceUID: null, referencedDisplaySetInstanceUID: null, isDerivedDisplaySet: true, isLoaded: false, isHydrated: false, segments: {}, sopClassUids, instance, instances: [instance], wadoRoot, wadoUriRoot, wadoUri, isOverlayDisplaySet: true, }; const referencedSeriesSequence = instance.ReferencedSeriesSequence; if (!referencedSeriesSequence) { throw new Error('ReferencedSeriesSequence is missing for the SEG'); } const referencedSeries = referencedSeriesSequence[0]; displaySet.referencedImages = instance.ReferencedSeriesSequence.ReferencedInstanceSequence; displaySet.referencedSeriesInstanceUID = referencedSeries.SeriesInstanceUID; displaySet.getReferenceDisplaySet = () => { const { displaySetService } = servicesManager.services; const referencedDisplaySets = displaySetService.getDisplaySetsForSeries( displaySet.referencedSeriesInstanceUID ); if (!referencedDisplaySets || referencedDisplaySets.length === 0) { throw new Error('Referenced DisplaySet is missing for the SEG'); } const referencedDisplaySet = referencedDisplaySets[0]; displaySet.referencedDisplaySetInstanceUID = referencedDisplaySet.displaySetInstanceUID; // Todo: this needs to be able to work with other reference volumes (other than streaming) such as nifti, etc. displaySet.referencedVolumeURI = referencedDisplaySet.displaySetInstanceUID; const referencedVolumeId = `cornerstoneStreamingImageVolume:${displaySet.referencedVolumeURI}`; displaySet.referencedVolumeId = referencedVolumeId; return referencedDisplaySet; }; displaySet.load = async ({ headers }) => await _load(displaySet, servicesManager, extensionManager, headers); return [displaySet]; } function _load(segDisplaySet, servicesManager, extensionManager, headers) { const { SOPInstanceUID } = segDisplaySet; const { segmentationService } = servicesManager.services; if ( (segDisplaySet.loading || segDisplaySet.isLoaded) && loadPromises[SOPInstanceUID] && _segmentationExists(segDisplaySet, segmentationService) ) { return loadPromises[SOPInstanceUID]; } segDisplaySet.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) => { if ( !segDisplaySet.segments || Object.keys(segDisplaySet.segments).length === 0 ) { await _loadSegments({ extensionManager, servicesManager, segDisplaySet, headers, }); } const suppressEvents = true; segmentationService .createSegmentationForSEGDisplaySet(segDisplaySet, null, suppressEvents) .then(() => { segDisplaySet.loading = false; resolve(); }) .catch(error => { segDisplaySet.loading = false; reject(error); }); }); return loadPromises[SOPInstanceUID]; } async function _loadSegments({ extensionManager, servicesManager, segDisplaySet, headers, }) { const utilityModule = extensionManager.getModuleEntry( '@ohif/extension-cornerstone.utilityModule.common' ); const { segmentationService } = servicesManager.services; const { dicomLoaderService } = utilityModule.exports; const arrayBuffer = await dicomLoaderService.findDicomDataPromise( segDisplaySet, null, headers ); const cachedReferencedVolume = cache.getVolume( segDisplaySet.referencedVolumeId ); if (!cachedReferencedVolume) { throw new Error( 'Referenced Volume is missing for the SEG, and stack viewport SEG is not supported yet' ); } const { imageIds } = cachedReferencedVolume; // Todo: what should be defaults here const tolerance = 0.001; const skipOverlapping = true; eventTarget.addEventListener(Enums.Events.SEGMENTATION_LOAD_PROGRESS, evt => { const { percentComplete } = evt.detail; segmentationService._broadcastEvent( segmentationService.EVENTS.SEGMENT_LOADING_COMPLETE, { percentComplete, } ); }); const results = await adaptersSEG.Cornerstone3D.Segmentation.generateToolState( imageIds, arrayBuffer, metaData, { skipOverlapping, tolerance, eventTarget, triggerEvent } ); results.segMetadata.data.forEach((data, i) => { if (i > 0) { data.rgba = dicomlabToRGB(data.RecommendedDisplayCIELabValue); } }); Object.assign(segDisplaySet, results); } function _segmentationExists(segDisplaySet, segmentationService) { // This should be abstracted with the CornerstoneCacheService return segmentationService.getSegmentation( segDisplaySet.displaySetInstanceUID ); } function getSopClassHandlerModule({ servicesManager, extensionManager }) { const getDisplaySetsFromSeries = instances => { return _getDisplaySetsFromSeries( instances, servicesManager, extensionManager ); }; return [ { name: 'dicom-seg', sopClassUids, getDisplaySetsFromSeries, }, ]; } export default getSopClassHandlerModule;