feat: overlapping segments (#4849)

This commit is contained in:
Pedro Köhler authored and GitHub committed 2025-03-18 17:07:13 -04:00
1 parent 5a6c2aeed9
commit 55d6393a3f
13 files changed
+232 -248

No files matched your search

@@ -46,8 +46,8 @@
},
"dependencies": {
"@babel/runtime": "^7.20.13",
"@cornerstonejs/adapters": "^3.7.0",
"@cornerstonejs/core": "^3.7.0",
"@cornerstonejs/adapters": "^3.7.1",
"@cornerstonejs/core": "^3.7.1",
"@kitware/vtk.js": "32.12.0",
"react-color": "^2.19.3"
}
@@ -46,8 +46,8 @@
},
"dependencies": {
"@babel/runtime": "^7.20.13",
"@cornerstonejs/adapters": "^3.7.0",
"@cornerstonejs/core": "^3.7.0",
"@cornerstonejs/adapters": "^3.7.1",
"@cornerstonejs/core": "^3.7.1",
"@kitware/vtk.js": "32.12.0",
"react-color": "^2.19.3"
}
@@ -182,7 +182,6 @@ async function _loadSegments({
// 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, {
@@ -190,11 +189,10 @@ async function _loadSegments({
});
});
const results = await adaptersSEG.Cornerstone3D.Segmentation.generateToolState(
const results = await adaptersSEG.Cornerstone3D.Segmentation.createFromDICOMSegBuffer(
imageIds,
arrayBuffer,
metaData,
{ skipOverlapping, tolerance, eventTarget, triggerEvent }
{ metadataProvider: metaData, tolerance }
);
let usedRecommendedDisplayCIELabValue = true;
@@ -211,15 +209,6 @@ async function _loadSegments({
}
});
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({
+3 -3
View File
@@ -46,9 +46,9 @@
},
"dependencies": {
"@babel/runtime": "^7.20.13",
"@cornerstonejs/adapters": "^3.7.0",
"@cornerstonejs/core": "^3.7.0",
"@cornerstonejs/tools": "^3.7.0",
"@cornerstonejs/adapters": "^3.7.1",
"@cornerstonejs/core": "^3.7.1",
"@cornerstonejs/tools": "^3.7.1",
"classnames": "^2.3.2"
}
}
@@ -42,8 +42,8 @@
},
"dependencies": {
"@babel/runtime": "^7.20.13",
"@cornerstonejs/core": "^3.7.0",
"@cornerstonejs/tools": "^3.7.0",
"@cornerstonejs/core": "^3.7.1",
"@cornerstonejs/tools": "^3.7.1",
"classnames": "^2.3.2"
}
}
+6 -6
View File
@@ -38,7 +38,7 @@
"@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2",
"@cornerstonejs/codec-openjpeg": "^1.2.4",
"@cornerstonejs/codec-openjph": "^2.4.5",
"@cornerstonejs/dicom-image-loader": "^3.7.0",
"@cornerstonejs/dicom-image-loader": "^3.7.1",
"@ohif/core": "3.10.0-beta.128",
"@ohif/ui": "3.10.0-beta.128",
"dcmjs": "*",
@@ -51,11 +51,11 @@
},
"dependencies": {
"@babel/runtime": "^7.20.13",
"@cornerstonejs/adapters": "^3.7.0",
"@cornerstonejs/core": "^3.7.0",
"@cornerstonejs/labelmap-interpolation": "^3.7.0",
"@cornerstonejs/polymorphic-segmentation": "^3.7.0",
"@cornerstonejs/tools": "^3.7.0",
"@cornerstonejs/adapters": "^3.7.1",
"@cornerstonejs/core": "^3.7.1",
"@cornerstonejs/labelmap-interpolation": "^3.7.1",
"@cornerstonejs/polymorphic-segmentation": "^3.7.1",
"@cornerstonejs/tools": "^3.7.1",
"@itk-wasm/morphological-contour-interpolation": "1.1.0",
"@kitware/vtk.js": "32.12.0",
"html2canvas": "^1.4.1",
@@ -391,13 +391,13 @@ class SegmentationService extends PubSubService {
): Promise<string> {
const { type } = options;
let { segmentationId } = options;
const { labelmapBufferArray } = segDisplaySet;
const { labelMapImages } = segDisplaySet;
if (type !== LABELMAP) {
throw new Error('Only labelmap type is supported for SEG display sets right now');
}
if (!labelmapBufferArray) {
if (!labelMapImages || !labelMapImages.length) {
throw new Error('SEG reading failed');
}
@@ -414,16 +414,32 @@ class SegmentationService extends PubSubService {
}
const imageIds = images.map(image => image.imageId);
const derivedImages = labelMapImages?.flat();
const derivedImageIds = derivedImages.map(image => image.imageId);
const derivedSegmentationImages = await imageLoader.createAndCacheDerivedLabelmapImages(
imageIds as string[]
);
segDisplaySet.images = derivedSegmentationImages.map(image => ({
segDisplaySet.images = derivedImages.map(image => ({
...image,
...metaData.get('instance', image.referencedImageId),
}));
// We should parse the segmentation as separate slices to support overlapping segments.
// This parsing should occur in the CornerstoneJS library adapters.
// For now, we use the volume returned from the library and chop it here.
let firstSegmentedSliceImageId = null;
for (let i = 0; i < derivedImages.length; i++) {
const voxelManager = derivedImages[i].voxelManager as csTypes.IVoxelManager<number>;
const scalarData = voxelManager.getScalarData();
voxelManager.setScalarData(scalarData);
// Check if this slice has any non-zero voxels and we haven't found one yet
if (!firstSegmentedSliceImageId && scalarData.some(value => value !== 0)) {
firstSegmentedSliceImageId = derivedImages[i].referencedImageId;
}
}
// assign the first non zero voxel image id to the segDisplaySet
segDisplaySet.firstSegmentedSliceImageId = firstSegmentedSliceImageId;
const segmentsInfo = segDisplaySet.segMetadata.data;
const segments: { [segmentIndex: string]: cstTypes.Segment } = {};
@@ -481,30 +497,6 @@ class SegmentationService extends PubSubService {
addColorLUT(colorLUT, colorLUTIndex);
this._segmentationIdToColorLUTIndexMap.set(segmentationId, colorLUTIndex);
// now we need to chop the volume array into chunks and set the scalar data for each derived segmentation image
const volumeScalarData = new Uint8Array(labelmapBufferArray[0]);
// We should parse the segmentation as separate slices to support overlapping segments.
// This parsing should occur in the CornerstoneJS library adapters.
// For now, we use the volume returned from the library and chop it here.
let firstSegmentedSliceImageId = null;
for (let i = 0; i < derivedSegmentationImages.length; i++) {
const voxelManager = derivedSegmentationImages[i]
.voxelManager as csTypes.IVoxelManager<number>;
const scalarData = voxelManager.getScalarData();
const sliceData = volumeScalarData.slice(i * scalarData.length, (i + 1) * scalarData.length);
scalarData.set(sliceData);
voxelManager.setScalarData(scalarData);
// Check if this slice has any non-zero voxels and we haven't found one yet
if (!firstSegmentedSliceImageId && sliceData.some(value => value !== 0)) {
firstSegmentedSliceImageId = derivedSegmentationImages[i].referencedImageId;
}
}
// assign the first non zero voxel image id to the segDisplaySet
segDisplaySet.firstSegmentedSliceImageId = firstSegmentedSliceImageId;
this._broadcastEvent(EVENTS.SEGMENTATION_LOADING_COMPLETE, {
segmentationId,
segDisplaySet,
@@ -515,7 +507,7 @@ class SegmentationService extends PubSubService {
representation: {
type: LABELMAP,
data: {
imageIds: derivedSegmentationImages.map(image => image.imageId),
imageIds: derivedImageIds,
referencedVolumeId: this._getVolumeIdForDisplaySet(referencedDisplaySet),
referencedImageIds: imageIds as string[],
},
@@ -1163,8 +1155,8 @@ class SegmentationService extends PubSubService {
highlightFunctionType = 'ease-in-out' // todo: make animation functions configurable from outside
): void {
const center = this._getSegmentCenter(segmentationId, segmentIndex);
if (!center) {
console.warn('No center found for segmentation', segmentationId, segmentIndex);
return;
}
+2 -2
View File
@@ -32,8 +32,8 @@
"start": "yarn run dev"
},
"peerDependencies": {
"@cornerstonejs/core": "^3.7.0",
"@cornerstonejs/tools": "^3.7.0",
"@cornerstonejs/core": "^3.7.1",
"@cornerstonejs/tools": "^3.7.1",
"@ohif/core": "3.10.0-beta.128",
"@ohif/extension-cornerstone-dicom-sr": "3.10.0-beta.128",
"@ohif/extension-default": "3.10.0-beta.128",