From ead38aa502d25d18305707f1686a42a635a9ad17 Mon Sep 17 00:00:00 2001 From: Bill Wallace Date: Tue, 13 Feb 2024 15:58:28 -0500 Subject: [PATCH] fix: Stack/volume paired contour segmentations (#1078) * Fix volume/stack paired segmentations * Fix video interpolation as well * Tweak the interval for points --- .../interpolationContourSegmentation/index.ts | 192 +++++++++++------- .../videoContourSegmentation/index.ts | 9 + .../contours/interpolation/selectHandles.ts | 3 +- 3 files changed, 123 insertions(+), 81 deletions(-) diff --git a/packages/tools/examples/interpolationContourSegmentation/index.ts b/packages/tools/examples/interpolationContourSegmentation/index.ts index 5d21de8438..acd4b54732 100644 --- a/packages/tools/examples/interpolationContourSegmentation/index.ts +++ b/packages/tools/examples/interpolationContourSegmentation/index.ts @@ -43,7 +43,9 @@ const renderingEngineId = 'myRenderingEngine'; let renderingEngine; const viewportIds = ['CT_STACK', 'CT_VOLUME_SAGITTAL']; const viewports = []; -const segmentationId = `SEGMENTATION_ID`; +const segmentationIdStack = `SEGMENTATION_ID_STACK`; +const segmentationIdVolume = `SEGMENTATION_ID_VOLUME`; +const segmentationIds = [segmentationIdStack, segmentationIdVolume]; let frameOfReferenceUID; const interpolationTools = new Map(); @@ -155,24 +157,25 @@ addDropdownToToolbar({ options: { values: toolsNames, defaultValue: selectedToolName }, onSelectedValueChange: (newSelectedToolNameAsStringOrNumber) => { const newSelectedToolName = String(newSelectedToolNameAsStringOrNumber); - const toolGroup = ToolGroupManager.getToolGroup(toolGroupId); - - // Set the new tool active - toolGroup.setToolActive(newSelectedToolName, { - bindings: [ - { - mouseButton: MouseBindings.Primary, // Left Click - }, - { - mouseButton: MouseBindings.Primary, // Shift + Left Click - modifierKey: KeyboardBindings.Shift, - }, - ], - }); - - // Set the old tool passive - toolGroup.setToolPassive(selectedToolName, { removeAllBindings: true }); + for (const toolGroupId of toolGroupIds) { + const toolGroup = ToolGroupManager.getToolGroup(toolGroupId); + + // Set the new tool active + toolGroup.setToolActive(newSelectedToolName, { + bindings: [ + { + mouseButton: MouseBindings.Primary, // Left Click + }, + { + mouseButton: MouseBindings.Primary, // Shift + Left Click + modifierKey: KeyboardBindings.Shift, + }, + ], + }); + // Set the old tool passive + toolGroup.setToolPassive(selectedToolName, { removeAllBindings: true }); + } selectedToolName = newSelectedToolName; }, }); @@ -180,31 +183,36 @@ addDropdownToToolbar({ addButtonToToolbar({ title: 'Accept All', onClick: () => { - cornerstoneTools.utilities.contours.acceptAutogeneratedInterpolations( - frameOfReferenceUID, - { - segmentIndex: - segmentation.segmentIndex.getActiveSegmentIndex(segmentationId), - segmentationId, - // Could also specify only for the active tool, but that doesn't seem useful here. - } - ); + for (const segmentationId of segmentationIds) { + cornerstoneTools.utilities.contours.acceptAutogeneratedInterpolations( + frameOfReferenceUID, + { + segmentIndex: + segmentation.segmentIndex.getActiveSegmentIndex(segmentationId), + segmentationId, + // Could also specify only for the active tool, but that doesn't seem useful here. + } + ); + } renderingEngine.render(); }, }); function acceptCurrent() { viewports.forEach((viewport) => { - cornerstoneTools.utilities.contours.acceptAutogeneratedInterpolations( - viewport.element, - { - segmentIndex: - segmentation.segmentIndex.getActiveSegmentIndex(segmentationId), - segmentationId, - sliceIndex: viewport.getCurrentImageIdIndex(), - } - ); + for (const segmentationId of segmentationIds) { + cornerstoneTools.utilities.contours.acceptAutogeneratedInterpolations( + viewport.element, + { + segmentIndex: + segmentation.segmentIndex.getActiveSegmentIndex(segmentationId), + segmentationId: segmentationIdStack, + sliceIndex: viewport.getCurrentImageIdIndex(), + } + ); + } }); + renderingEngine.render(); } @@ -240,47 +248,34 @@ Deleting: content.append(instructions); let shouldInterpolate = false; -function addToggleInterpolationButton(toolGroup) { +function addToggleInterpolationButton(toolGroupIds) { addButtonToToolbar({ title: 'Toggle interpolation', onClick: () => { shouldInterpolate = !shouldInterpolate; - toolGroup.setToolConfiguration(interpolationToolName, { - interpolation: { - enabled: shouldInterpolate, - }, + toolGroupIds.forEach((toolGroupId) => { + const toolGroup = ToolGroupManager.getToolGroup(toolGroupId); + toolGroup.setToolConfiguration(interpolationToolName, { + interpolation: { + enabled: shouldInterpolate, + }, + }); }); }, }); } -const toolGroupId = 'STACK_TOOL_GROUP_ID'; - -/** - * Runs the demo - */ -async function run() { - // Init Cornerstone and related libraries - await initDemo(); - - // Add tools to Cornerstone3D - cornerstoneTools.addTool(PlanarFreehandContourSegmentationTool); - cornerstoneTools.addTool(PlanarFreehandROITool); - cornerstoneTools.addTool(SplineContourSegmentationTool); - cornerstoneTools.addTool(SplineROITool); - cornerstoneTools.addTool(LivewireContourSegmentationTool); - cornerstoneTools.addTool(LivewireContourTool); +const toolGroupIds = ['STACK_TOOL_GROUP_ID', 'VOLUME_TOOL_GROUP_ID']; - cornerstoneTools.addTool(SegmentationDisplayTool); - - // Define a tool group, which defines how mouse events map to tool commands for - // Any viewport using the group +/** Adds the bindings for the tool group */ +function addBindings(toolGroupId) { const toolGroup = ToolGroupManager.createToolGroup(toolGroupId); addManipulationBindings(toolGroup); // Add the tools to the tool group toolGroup.addTool(SegmentationDisplayTool.toolName); + toolGroup.setToolEnabled(SegmentationDisplayTool.toolName); for (const [toolName, config] of interpolationTools.entries()) { if (config.baseTool) { @@ -315,9 +310,30 @@ async function run() { }, ], }); +} +/** + * Runs the demo + */ +async function run() { + // Init Cornerstone and related libraries + await initDemo(); + + // Add tools to Cornerstone3D + cornerstoneTools.addTool(PlanarFreehandContourSegmentationTool); + cornerstoneTools.addTool(PlanarFreehandROITool); + cornerstoneTools.addTool(SplineContourSegmentationTool); + cornerstoneTools.addTool(SplineROITool); + cornerstoneTools.addTool(LivewireContourSegmentationTool); + cornerstoneTools.addTool(LivewireContourTool); + + cornerstoneTools.addTool(SegmentationDisplayTool); + // Define a tool group, which defines how mouse events map to tool commands for + // Any viewport using the group + addBindings(toolGroupIds[0]); + addBindings(toolGroupIds[1]); // set up toggle interpolation tool button. - addToggleInterpolationButton(toolGroup); + addToggleInterpolationButton(toolGroupIds); // Get Cornerstone imageIds and fetch metadata into RAM const stackImageIds = await createImageIdsAndCacheMetaData({ @@ -363,9 +379,18 @@ async function run() { renderingEngine.setViewports(viewportInputArray); // Set the tool group on the viewport - viewportIds.forEach((viewportId) => - toolGroup.addViewport(viewportId, renderingEngineId) - ); + viewportIds.forEach((viewportId, index) => { + console.log( + 'Setting tool group/viewport id', + index, + viewportId, + toolGroupIds[index] + ); + ToolGroupManager.getToolGroup(toolGroupIds[index]).addViewport( + viewportId, + renderingEngineId + ); + }); // Define a volume in memory const volume = await volumeLoader.createAndCacheVolume(volumeId, { @@ -400,27 +425,36 @@ async function run() { renderingEngine.renderViewports(viewportIds); // Add a segmentation that will contains the contour annotations - segmentation.addSegmentations([ - { - segmentationId, - representation: { + let index = 0; + for (const segmentationId of segmentationIds) { + segmentation.addSegmentations([ + { + segmentationId, + representation: { + type: csToolsEnums.SegmentationRepresentations.Contour, + }, + }, + ]); + // Create a segmentation representation associated to the toolGroupId + // Add the segmentation representation to the toolgroup + await segmentation.addSegmentationRepresentations(toolGroupIds[index++], [ + { + segmentationId, type: csToolsEnums.SegmentationRepresentations.Contour, }, - }, - ]); + ]); + } - // Create a segmentation representation associated to the toolGroupId - await segmentation.addSegmentationRepresentations(toolGroupId, [ - { - segmentationId, - type: csToolsEnums.SegmentationRepresentations.Contour, - }, - ]); frameOfReferenceUID = volumeViewport.getFrameOfReferenceUID(); } function updateActiveSegmentIndex(segmentIndex: number): void { - segmentation.segmentIndex.setActiveSegmentIndex(segmentationId, segmentIndex); + for (const segmentationId of segmentationIds) { + segmentation.segmentIndex.setActiveSegmentIndex( + segmentationId, + segmentIndex + ); + } } run(); diff --git a/packages/tools/examples/videoContourSegmentation/index.ts b/packages/tools/examples/videoContourSegmentation/index.ts index f77e352d56..2c134d09f2 100644 --- a/packages/tools/examples/videoContourSegmentation/index.ts +++ b/packages/tools/examples/videoContourSegmentation/index.ts @@ -55,6 +55,15 @@ const segmentVisibilityMap = new Map(); const configuredTools = new Map(); const interpolationConfiguration = { interpolation: { enabled: true }, + decimate: { + enabled: true, + /** A maximum given distance 'epsilon' to decide if a point should or + * shouldn't be added the resulting polyline which will have a lower + * number of points for higher `epsilon` values. + * Larger values work well for this video example + */ + epsilon: 0.5, + }, }; configuredTools.set('CatmullRomSplineROI', { diff --git a/packages/tools/src/utilities/contours/interpolation/selectHandles.ts b/packages/tools/src/utilities/contours/interpolation/selectHandles.ts index 04477b798e..674551d833 100644 --- a/packages/tools/src/utilities/contours/interpolation/selectHandles.ts +++ b/packages/tools/src/utilities/contours/interpolation/selectHandles.ts @@ -137,8 +137,7 @@ function findMinimumRegions(dotValues, handleCount) { const { length } = dotValues; // Fallback for very uniform ojects. if (deviation < 0.01 || length < handleCount * 3) { - // TODO - create handleCount evenly spaced handles - return [0, Math.floor(length / 3), Math.floor((length * 2) / 3)]; + return []; } const inflection = [];