256 lines
7.4 KiB
TypeScript
256 lines
7.4 KiB
TypeScript
import { utils } from '@ohif/core';
|
|
import { metaData, triggerEvent, 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'];
|
|
|
|
const loadPromises = {};
|
|
|
|
function _getDisplaySetsFromSeries(
|
|
instances,
|
|
servicesManager: AppTypes.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) {
|
|
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.getDisplaySetsForSeries(
|
|
displaySet.referencedSeriesInstanceUID
|
|
);
|
|
|
|
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;
|
|
unsubscribe();
|
|
}
|
|
}
|
|
);
|
|
} else {
|
|
displaySet.referencedDisplaySetInstanceUID = referencedDisplaySet.displaySetInstanceUID;
|
|
}
|
|
|
|
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');
|
|
}
|
|
|
|
const { instances: images } = referencedDisplaySet;
|
|
const imageIds = images.map(({ imageId }) => imageId);
|
|
|
|
// 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 }
|
|
);
|
|
|
|
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 (results.overlappingSegments) {
|
|
uiNotificationService.show({
|
|
title: 'Overlapping Segments',
|
|
message:
|
|
'Unsupported overlapping segments detected, segmentation rendering results may be incorrect.',
|
|
type: 'warning',
|
|
});
|
|
}
|
|
|
|
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({ servicesManager, extensionManager }) {
|
|
const getDisplaySetsFromSeries = instances => {
|
|
return _getDisplaySetsFromSeries(instances, servicesManager, extensionManager);
|
|
};
|
|
|
|
return [
|
|
{
|
|
name: 'dicom-seg',
|
|
sopClassUids,
|
|
getDisplaySetsFromSeries,
|
|
},
|
|
];
|
|
}
|
|
|
|
export default getSopClassHandlerModule;
|