import { utils, Types as OhifTypes } from '@ohif/core'; import i18n from '@ohif/i18n'; import { metaData, eventTarget } from '@cornerstonejs/core'; import { CONSTANTS, segmentation as cstSegmentation } from '@cornerstonejs/tools'; 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', '1.2.840.10008.5.1.4.1.1.66.7']; 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, StructureSetDate, SOPClassUID, FrameOfReferenceUID, wadoRoot, wadoUri, wadoUriRoot, imageId: predecessorImageId, } = instance; const displaySet = { Modality: 'SEG', loading: false, isReconstructable: false, displaySetInstanceUID: utils.guid(), SeriesDescription, SeriesNumber, SeriesDate: SeriesDate || StructureSetDate || '', SOPInstanceUID, SeriesInstanceUID, StudyInstanceUID, SOPClassHandlerId, SOPClassUID, frameOfReferenceUID: FrameOfReferenceUID, referencedImages: null, referencedSeriesInstanceUID: null, referencedDisplaySetInstanceUID: null, isDerivedDisplaySet: true, isLoaded: false, isHydrated: false, segments: {}, sopClassUids, instance, predecessorImageId, instances: [instance], wadoRoot, wadoUriRoot, wadoUri, isOverlayDisplaySet: true, label: SeriesDescription || `${i18n.t('Series')} ${SeriesNumber} - ${i18n.t('SEG')}`, }; const referencedSeriesSequence = instance.ReferencedSeriesSequence; if (!referencedSeriesSequence) { console.error('ReferencedSeriesSequence is missing for the SEG'); return; } const referencedSeries = referencedSeriesSequence[0] || referencedSeriesSequence; displaySet.referencedImages = instance.ReferencedSeriesSequence.ReferencedInstanceSequence; displaySet.referencedSeriesInstanceUID = referencedSeries.SeriesInstanceUID; const { displaySetService } = servicesManager.services; const referencedDisplaySets = displaySetService.getDisplaySetsForReferences( instance.ReferencedSeriesSequence ); if (referencedDisplaySets?.length > 1) { console.warn( 'Segmentation does not currently handle references to multiple series, defaulting to first series' ); } const referencedDisplaySet = referencedDisplaySets[0]; if (!referencedDisplaySet) { // subscribe to display sets added which means at some point it will be available const { unsubscribe } = displaySetService.subscribe( displaySetService.EVENTS.DISPLAY_SETS_ADDED, ({ displaySetsAdded }) => { // here we can also do a little bit of search, since sometimes DICOM SEG // does not contain the referenced display set uid , and we can just // see which of the display sets added is more similar and assign it // to the referencedDisplaySet const addedDisplaySet = displaySetsAdded[0]; if (addedDisplaySet.SeriesInstanceUID === displaySet.referencedSeriesInstanceUID) { displaySet.referencedDisplaySetInstanceUID = addedDisplaySet.displaySetInstanceUID; displaySet.isReconstructable = addedDisplaySet.isReconstructable; displaySet.frameOfReferenceUID = addedDisplaySet.frameOfReferenceUID; unsubscribe(); } } ); } else { displaySet.referencedDisplaySetInstanceUID = referencedDisplaySet.displaySetInstanceUID; displaySet.isReconstructable = referencedDisplaySet.isReconstructable; displaySet.frameOfReferenceUID = referencedDisplaySet.frameOfReferenceUID; } displaySet.load = async ({ headers }) => await _load(displaySet, servicesManager, extensionManager, headers); return [displaySet]; } function _load( segDisplaySet, servicesManager: AppTypes.ServicesManager, extensionManager, headers ) { const { SOPInstanceUID } = segDisplaySet; const { segmentationService } = servicesManager.services; if ( (segDisplaySet.loading || segDisplaySet.isLoaded) && loadPromises[SOPInstanceUID] && _segmentationExists(segDisplaySet) ) { 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) { try { await _loadSegments({ extensionManager, servicesManager, segDisplaySet, headers, }); } catch (e) { segDisplaySet.loading = false; return reject(e); } } segmentationService .createSegmentationForSEGDisplaySet(segDisplaySet) .then(() => { segDisplaySet.loading = false; resolve(); }) .catch(error => { segDisplaySet.loading = false; reject(error); }); }); return loadPromises[SOPInstanceUID]; } async function _loadSegments({ extensionManager, servicesManager, segDisplaySet, headers, }: withAppTypes) { const utilityModule = extensionManager.getModuleEntry( '@ohif/extension-cornerstone.utilityModule.common' ); const { segmentationService, uiNotificationService } = servicesManager.services; const { dicomLoaderService } = utilityModule.exports; const arrayBuffer = await dicomLoaderService.findDicomDataPromise(segDisplaySet, null, headers); const referencedDisplaySet = servicesManager.services.displaySetService.getDisplaySetByUID( segDisplaySet.referencedDisplaySetInstanceUID ); if (!referencedDisplaySet) { throw new Error('referencedDisplaySet is missing for SEG'); } let { imageIds } = referencedDisplaySet; if (!imageIds) { // try images const { images } = referencedDisplaySet; imageIds = images.map(image => image.imageId); } // Todo: what should be defaults here const tolerance = 0.001; 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.createFromDICOMSegBuffer( imageIds, arrayBuffer, { metadataProvider: metaData, tolerance } ); let usedRecommendedDisplayCIELabValue = true; results.segMetadata.data.forEach((data, i) => { if (i > 0) { data.rgba = data.RecommendedDisplayCIELabValue; if (data.rgba) { data.rgba = dicomlabToRGB(data.rgba); } else { usedRecommendedDisplayCIELabValue = false; data.rgba = CONSTANTS.COLOR_LUT[i % CONSTANTS.COLOR_LUT.length]; } } }); if (!usedRecommendedDisplayCIELabValue) { // Display a notification about the non-utilization of RecommendedDisplayCIELabValue uiNotificationService.show({ title: 'DICOM SEG import', message: 'RecommendedDisplayCIELabValue not found for one or more segments. The default color was used instead.', type: 'warning', duration: 5000, }); } Object.assign(segDisplaySet, results); } function _segmentationExists(segDisplaySet) { return cstSegmentation.state.getSegmentation(segDisplaySet.displaySetInstanceUID); } function getSopClassHandlerModule(params: OhifTypes.Extensions.ExtensionParams) { const { servicesManager, extensionManager } = params; const getDisplaySetsFromSeries = instances => { return _getDisplaySetsFromSeries(instances, servicesManager, extensionManager); }; return [ { name: 'dicom-seg', sopClassUids, getDisplaySetsFromSeries, }, ]; } export default getSopClassHandlerModule;