feat(segmentation): Enhance Segmentation Tools with Preview and Selection Features (#4870)

This commit is contained in:
Alireza authored and GitHub committed 2025-03-19 18:24:33 -04:00
1 parent 20042f9109
commit 5f112337b6
24 files changed
+392 -236

No files matched your search

+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.1",
"@cornerstonejs/dicom-image-loader": "^3.7.5",
"@ohif/core": "3.10.0-beta.130",
"@ohif/ui": "3.10.0-beta.130",
"dcmjs": "*",
@@ -51,11 +51,11 @@
},
"dependencies": {
"@babel/runtime": "^7.20.13",
"@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",
"@cornerstonejs/adapters": "^3.7.5",
"@cornerstonejs/core": "^3.7.5",
"@cornerstonejs/labelmap-interpolation": "^3.7.5",
"@cornerstonejs/polymorphic-segmentation": "^3.7.5",
"@cornerstonejs/tools": "^3.7.5",
"@itk-wasm/morphological-contour-interpolation": "1.1.0",
"@kitware/vtk.js": "32.12.0",
"html2canvas": "^1.4.1",
@@ -41,6 +41,23 @@ const toggleSyncFunctions = {
voi: toggleVOISliceSync,
};
const getLabelmapTools = ({ toolGroupService }) => {
const labelmapTools = [];
const toolGroupIds = toolGroupService.getToolGroupIds();
toolGroupIds.forEach(toolGroupId => {
const toolGroup = cornerstoneTools.ToolGroupManager.getToolGroup(toolGroupId);
const tools = toolGroup.getToolInstances();
// tools is an object with toolName as the key and tool as the value
Object.keys(tools).forEach(toolName => {
const tool = tools[toolName];
if (tool instanceof cornerstoneTools.LabelmapBaseTool) {
labelmapTools.push(tool);
}
});
});
return labelmapTools;
};
function commandsModule({
servicesManager,
commandsManager,
@@ -57,6 +74,7 @@ function commandsModule({
colorbarService,
hangingProtocolService,
syncGroupService,
toolbarService,
segmentationService,
displaySetService,
} = servicesManager.services as AppTypes.Services;
@@ -1484,6 +1502,41 @@ function commandsModule({
redo: () => {
DefaultHistoryMemo.redo();
},
toggleSegmentPreviewEdit: ({ toggle }) => {
const labelmapTools = getLabelmapTools({ toolGroupService });
labelmapTools.forEach(tool => {
tool.configuration = {
...tool.configuration,
preview: {
...tool.configuration.preview,
enabled: toggle,
},
};
});
},
toggleSegmentSelect: ({ toggle }) => {
const toolGroupIds = toolGroupService.getToolGroupIds();
toolGroupIds.forEach(toolGroupId => {
const toolGroup = cornerstoneTools.ToolGroupManager.getToolGroup(toolGroupId);
if (toggle) {
toolGroup.setToolActive(cornerstoneTools.SegmentSelectTool.toolName);
} else {
toolGroup.setToolDisabled(cornerstoneTools.SegmentSelectTool.toolName);
}
});
},
acceptPreview: () => {
const labelmapTools = getLabelmapTools({ toolGroupService });
labelmapTools.forEach(tool => {
tool.acceptPreview();
});
},
rejectPreview: () => {
const labelmapTools = getLabelmapTools({ toolGroupService });
labelmapTools.forEach(tool => {
tool.rejectPreview();
});
},
};
const definitions = {
@@ -1755,6 +1808,10 @@ function commandsModule({
interpolateLabelmap: actions.interpolateLabelmap,
runSegmentBidirectional: actions.runSegmentBidirectional,
downloadCSVSegmentationReport: actions.downloadCSVSegmentationReport,
toggleSegmentPreviewEdit: actions.toggleSegmentPreviewEdit,
toggleSegmentSelect: actions.toggleSegmentSelect,
acceptPreview: actions.acceptPreview,
rejectPreview: actions.rejectPreview,
};
return {
@@ -1,5 +1,7 @@
import { CustomDropdownMenuContent } from './CustomDropdownMenuContent';
import { CustomSegmentStatisticsHeader } from './CustomSegmentStatisticsHeader';
import React, { useState } from 'react';
import { Switch } from '@ohif/ui-next';
export default function getSegmentationPanelCustomization({ commandsManager, servicesManager }) {
return {
@@ -30,5 +32,40 @@ export default function getSegmentationPanelCustomization({ commandsManager, ser
volume: 'Volume',
lesionGlycolysis: 'Lesion Glycolysis',
},
'segmentationToolbox.config': () => {
// Get initial states based on current configuration
const [previewEdits, setPreviewEdits] = useState(false);
const [toggleSegmentEnabled, setToggleSegmentEnabled] = useState(false);
const handlePreviewEditsChange = checked => {
setPreviewEdits(checked);
commandsManager.run('toggleSegmentPreviewEdit', { toggle: checked });
};
const handleToggleSegmentEnabledChange = checked => {
setToggleSegmentEnabled(checked);
commandsManager.run('toggleSegmentSelect', { toggle: checked });
};
return (
<div className="bg-muted flex flex-col gap-4 border-b border-b-[2px] border-black px-2 py-3">
<div className="flex items-center gap-2">
<Switch
checked={previewEdits}
onCheckedChange={handlePreviewEditsChange}
/>
<span className="text-base text-white">Preview edits before creating</span>
</div>
<div className="flex items-center gap-2">
<Switch
checked={toggleSegmentEnabled}
onCheckedChange={handleToggleSegmentEnabledChange}
/>
<span className="text-base text-white">Highlight segments to select</span>
</div>
</div>
);
},
};
}
@@ -37,6 +37,7 @@ import {
LivewireContourTool,
OrientationMarkerTool,
WindowLevelRegionTool,
SegmentSelectTool,
} from '@cornerstonejs/tools';
import * as polySeg from '@cornerstonejs/polymorphic-segmentation';
@@ -96,7 +97,7 @@ export default function initCornerstoneTools(configuration = {}) {
addTool(OrientationMarkerTool);
addTool(WindowLevelRegionTool);
addTool(PlanarFreehandContourSegmentationTool);
addTool(SegmentSelectTool);
// Modify annotation tools to use dashed lines on SR
const annotationStyle = {
textBoxFontSize: '15px',
@@ -150,6 +151,7 @@ const toolNames = {
WindowLevelRegion: WindowLevelRegionTool.toolName,
PlanarFreehandContourSegmentation: PlanarFreehandContourSegmentationTool.toolName,
SegmentBidirectional: SegmentBidirectionalTool.toolName,
SegmentSelect: SegmentSelectTool.toolName,
};
export { toolNames };
@@ -264,10 +264,14 @@ class SegmentationService extends PubSubService {
{
segmentationId,
type,
config,
suppressEvents = false,
}: {
segmentationId: string;
type?: csToolsEnums.SegmentationRepresentations;
config?: {
blendMode?: csEnums.BlendModes;
};
suppressEvents?: boolean;
}
): Promise<void> {
@@ -301,7 +305,8 @@ class SegmentationService extends PubSubService {
segmentationId,
representationTypeToUse,
colorLUTIndex,
isConverted
isConverted,
config
);
if (!suppressEvents) {
@@ -1349,12 +1354,15 @@ class SegmentationService extends PubSubService {
segmentationId: string,
representationType: csToolsEnums.SegmentationRepresentations,
colorLUTIndex: number,
isConverted: boolean
isConverted: boolean,
config?: {
blendMode?: csEnums.BlendModes;
}
): Promise<void> {
const representation = {
type: representationType,
segmentationId,
config: { colorLUTOrIndex: colorLUTIndex },
config: { colorLUTOrIndex: colorLUTIndex, ...config },
};
const addRepresentation = () =>
@@ -1,4 +1,4 @@
import { Types, getEnabledElementByViewportId } from '@cornerstonejs/core';
import { Enums as CoreEnums, Types, getEnabledElementByViewportId } from '@cornerstonejs/core';
import {
SynchronizerManager,
Synchronizer,
@@ -7,7 +7,8 @@ import {
} from '@cornerstonejs/tools';
const { createSynchronizer } = SynchronizerManager;
const { SEGMENTATION_REPRESENTATION_ADDED } = Enums.Events;
const { SEGMENTATION_REPRESENTATION_MODIFIED } = Enums.Events;
const { BlendModes } = CoreEnums;
export default function createHydrateSegmentationSynchronizer(
synchronizerName: string,
@@ -15,15 +16,16 @@ export default function createHydrateSegmentationSynchronizer(
): Synchronizer {
const stackImageSynchronizer = createSynchronizer(
synchronizerName,
SEGMENTATION_REPRESENTATION_ADDED,
(synchronizerInstance, sourceViewport, targetViewport, sourceEvent) =>
segmentationRepresentationModifiedCallback(
SEGMENTATION_REPRESENTATION_MODIFIED,
(synchronizerInstance, sourceViewport, targetViewport, sourceEvent) => {
return segmentationRepresentationModifiedCallback(
synchronizerInstance,
sourceViewport,
targetViewport,
sourceEvent,
{ servicesManager, options }
),
);
},
{
eventSource: 'eventTarget',
}
@@ -41,8 +43,8 @@ const segmentationRepresentationModifiedCallback = async (
) => {
const event = sourceEvent as ToolsTypes.EventTypes.SegmentationRepresentationModifiedEventType;
const { segmentationId, viewportId } = event.detail;
const { segmentationService, hangingProtocolService } = servicesManager.services;
const { segmentationId } = event.detail;
const { segmentationService } = servicesManager.services;
const targetViewportId = targetViewport.viewportId;
@@ -75,5 +77,9 @@ const segmentationRepresentationModifiedCallback = async (
await segmentationService.addSegmentationRepresentation(targetViewportId, {
segmentationId,
type,
config: {
blendMode:
viewport.getBlendMode() === 1 ? BlendModes.LABELMAP_EDGE_PROJECTION_BLEND : undefined,
},
});
};