* Add single viewport configuration * Multiple viewport configuration * Improve performance by using independent set methods * Add jump to slice command * Add context configuration * Cache panel visibility * Fix sync between vtk and cornerstone * Remove apis index * Add approach * Add loading to update volumes * Fix broken configuration * Bump vtk version * Use loading label * Update cy tests after vtk loading label changed * Remove loading for segs
209 lines
5.8 KiB
JavaScript
209 lines
5.8 KiB
JavaScript
import OHIF from '@ohif/core';
|
|
import dcmjs from 'dcmjs';
|
|
import cornerstone from 'cornerstone-core';
|
|
import cornerstoneTools from 'cornerstone-tools';
|
|
|
|
const { DicomLoaderService } = OHIF.utils;
|
|
|
|
export default async function loadSegmentation(
|
|
segDisplaySet,
|
|
referencedDisplaySet,
|
|
studies
|
|
) {
|
|
const { StudyInstanceUID } = referencedDisplaySet;
|
|
|
|
// Set here is loading is asynchronous.
|
|
// If this function throws its set back to false.
|
|
segDisplaySet.isLoaded = true;
|
|
|
|
const segArrayBuffer = await DicomLoaderService.findDicomDataPromise(
|
|
segDisplaySet,
|
|
studies
|
|
);
|
|
|
|
const dicomData = dcmjs.data.DicomMessage.readFile(segArrayBuffer);
|
|
const dataset = dcmjs.data.DicomMetaDictionary.naturalizeDataset(
|
|
dicomData.dict
|
|
);
|
|
|
|
dataset._meta = dcmjs.data.DicomMetaDictionary.namifyDataset(dicomData.meta);
|
|
|
|
const imageIds = _getImageIdsForDisplaySet(
|
|
studies,
|
|
StudyInstanceUID,
|
|
referencedDisplaySet.SeriesInstanceUID
|
|
);
|
|
|
|
return new Promise((resolve, reject) => {
|
|
let results;
|
|
|
|
try {
|
|
results = _parseSeg(segArrayBuffer, imageIds);
|
|
} catch (error) {
|
|
segDisplaySet.isLoaded = false;
|
|
reject(error);
|
|
}
|
|
|
|
const { labelmapBuffer, segMetadata, segmentsOnFrame } = results;
|
|
const { setters } = cornerstoneTools.getModule('segmentation');
|
|
|
|
// TODO: Could define a color LUT based on colors in the SEG.
|
|
const labelmapIndex = _getNextLabelmapIndex(imageIds[0]);
|
|
const colorLUTIndex = _makeColorLUTAndGetIndex(segMetadata);
|
|
|
|
setters.labelmap3DByFirstImageId(
|
|
imageIds[0],
|
|
labelmapBuffer,
|
|
labelmapIndex,
|
|
segMetadata,
|
|
imageIds.length,
|
|
segmentsOnFrame,
|
|
colorLUTIndex
|
|
);
|
|
|
|
segDisplaySet.labelmapIndex = labelmapIndex;
|
|
|
|
/*
|
|
* TODO: Improve the way we notify parts of the app that depends on segs to be loaded.
|
|
*
|
|
* Currently we are using a non-ideal implementation through a custom event to notify the segmentation panel
|
|
* or other components that could rely on loaded segmentations that
|
|
* the segments were loaded so that e.g. when the user opens the panel
|
|
* before the segments are fully loaded, the panel can subscribe to this custom event
|
|
* and update itself with the new segments.
|
|
*
|
|
* This limitation is due to the fact that the cs segmentation module is an object (which will be
|
|
* updated after the segments are loaded) that React its not aware of its changes
|
|
* because the module object its not passed in to the panel component as prop but accessed externally.
|
|
*
|
|
* Improving this event approach to something reactive that can be tracked inside the react lifecycle,
|
|
* allows us to easily watch the module or the segmentations loading process in any other component
|
|
* without subscribing to external events.
|
|
*/
|
|
console.log('Segmentation loaded.');
|
|
const event = new CustomEvent('extensiondicomsegmentationsegloaded');
|
|
document.dispatchEvent(event);
|
|
|
|
resolve(labelmapIndex);
|
|
});
|
|
}
|
|
|
|
function _getNextLabelmapIndex(firstImageId) {
|
|
const { state } = cornerstoneTools.getModule('segmentation');
|
|
const brushStackState = state.series[firstImageId];
|
|
|
|
let labelmapIndex = 0;
|
|
|
|
if (brushStackState) {
|
|
const { labelmaps3D } = brushStackState;
|
|
labelmapIndex = labelmaps3D.length;
|
|
|
|
for (let i = 0; i < labelmaps3D.length; i++) {
|
|
if (!labelmaps3D[i]) {
|
|
labelmapIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return labelmapIndex;
|
|
}
|
|
|
|
function _makeColorLUTAndGetIndex(segMetadata) {
|
|
const { setters, state } = cornerstoneTools.getModule('segmentation');
|
|
const { colorLutTables } = state;
|
|
const colorLUTIndex = _getNextColorLUTIndex();
|
|
|
|
const { data } = segMetadata;
|
|
|
|
if (
|
|
!data.some(
|
|
segment =>
|
|
segment &&
|
|
(segment.ROIDisplayColor || segment.RecommendedDisplayCIELabValue)
|
|
)
|
|
) {
|
|
// Use default cornerstoneTools colorLUT.
|
|
return 0;
|
|
}
|
|
|
|
const colorLUT = [];
|
|
|
|
for (let i = 0; i < data.length; i++) {
|
|
const segment = data[i];
|
|
if (!segment) {
|
|
continue;
|
|
}
|
|
|
|
const { ROIDisplayColor, RecommendedDisplayCIELabValue } = segment;
|
|
|
|
if (RecommendedDisplayCIELabValue) {
|
|
const rgb = dcmjs.data.Colors.dicomlab2RGB(
|
|
RecommendedDisplayCIELabValue
|
|
).map(x => Math.round(x * 255));
|
|
|
|
colorLUT[i] = [...rgb, 255];
|
|
} else if (ROIDisplayColor) {
|
|
colorLUT[i] = [...ROIDisplayColor, 255];
|
|
} else {
|
|
colorLUT[i] = [...colorLutTables[0][i]];
|
|
}
|
|
}
|
|
|
|
colorLUT.shift();
|
|
setters.colorLUT(colorLUTIndex, colorLUT);
|
|
|
|
return colorLUTIndex;
|
|
}
|
|
|
|
function _getNextColorLUTIndex() {
|
|
const { state } = cornerstoneTools.getModule('segmentation');
|
|
const { colorLutTables } = state;
|
|
|
|
let colorLUTIndex = colorLutTables.length;
|
|
|
|
for (let i = 0; i < colorLutTables.length; i++) {
|
|
if (!colorLutTables[i]) {
|
|
colorLUTIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return colorLUTIndex;
|
|
}
|
|
|
|
function _parseSeg(arrayBuffer, imageIds) {
|
|
return dcmjs.adapters.Cornerstone.Segmentation.generateToolState(
|
|
imageIds,
|
|
arrayBuffer,
|
|
cornerstone.metaData
|
|
);
|
|
}
|
|
|
|
function _getImageIdsForDisplaySet(
|
|
studies,
|
|
StudyInstanceUID,
|
|
SeriesInstanceUID
|
|
) {
|
|
const study = studies.find(
|
|
study => study.StudyInstanceUID === StudyInstanceUID
|
|
);
|
|
|
|
const displaySets = study.displaySets.filter(displaySet => {
|
|
return displaySet.SeriesInstanceUID === SeriesInstanceUID;
|
|
});
|
|
|
|
if (displaySets.length > 1) {
|
|
console.warn(
|
|
'More than one display set with the same SeriesInstanceUID. This is not supported yet...'
|
|
);
|
|
// TODO -> We could make check the instance list and see if any match?
|
|
// Do we split the segmentation into two cornerstoneTools segmentations if there are images in both series?
|
|
// ^ Will that even happen?
|
|
}
|
|
|
|
const referencedDisplaySet = displaySets[0];
|
|
|
|
return referencedDisplaySet.images.map(image => image.getImageId());
|
|
}
|