fix(SegmentationStyle): Fix inactive contour visibility and styling. (#5563)

* Opacity, fill and outline properties for active and inactive contours
are now consistent with those for label map segmentations.
The properties for inactive segmentations are applied to BOTH
inactive contours and label maps.

* Activate the selected segmentation for a type when the alpha, outline width, or fill/outline visibility is changed.
This commit is contained in:
Joe Boccanfuso authored and GitHub committed 2025-11-19 17:55:53 -05:00
1 parent 62797acd9a
commit 5c17c262ea
23 files changed
+395 -361

No files matched your search

@@ -46,8 +46,8 @@
},
"dependencies": {
"@babel/runtime": "7.28.2",
"@cornerstonejs/adapters": "4.9.1",
"@cornerstonejs/core": "4.9.1",
"@cornerstonejs/adapters": "4.11.2",
"@cornerstonejs/core": "4.11.2",
"@kitware/vtk.js": "32.12.0",
"react-color": "2.19.3"
}
@@ -46,8 +46,8 @@
},
"dependencies": {
"@babel/runtime": "7.28.2",
"@cornerstonejs/adapters": "4.9.1",
"@cornerstonejs/core": "4.9.1",
"@cornerstonejs/adapters": "4.11.2",
"@cornerstonejs/core": "4.11.2",
"@kitware/vtk.js": "32.12.0",
"react-color": "2.19.3"
}
+3 -3
View File
@@ -46,9 +46,9 @@
},
"dependencies": {
"@babel/runtime": "7.28.2",
"@cornerstonejs/adapters": "4.9.1",
"@cornerstonejs/core": "4.9.1",
"@cornerstonejs/tools": "4.9.1",
"@cornerstonejs/adapters": "4.11.2",
"@cornerstonejs/core": "4.11.2",
"@cornerstonejs/tools": "4.11.2",
"classnames": "2.5.1"
}
}
@@ -42,8 +42,8 @@
},
"dependencies": {
"@babel/runtime": "7.28.2",
"@cornerstonejs/core": "4.9.1",
"@cornerstonejs/tools": "4.9.1",
"@cornerstonejs/core": "4.11.2",
"@cornerstonejs/tools": "4.11.2",
"classnames": "2.5.1"
}
}
+7 -7
View File
@@ -40,7 +40,7 @@
"@cornerstonejs/codec-libjpeg-turbo-8bit": "1.2.2",
"@cornerstonejs/codec-openjpeg": "1.3.0",
"@cornerstonejs/codec-openjph": "2.4.7",
"@cornerstonejs/dicom-image-loader": "4.9.1",
"@cornerstonejs/dicom-image-loader": "4.11.2",
"@ohif/core": "3.12.0-beta.86",
"@ohif/ui": "3.12.0-beta.86",
"dcmjs": "0.43.1",
@@ -53,12 +53,12 @@
},
"dependencies": {
"@babel/runtime": "7.28.2",
"@cornerstonejs/adapters": "4.9.1",
"@cornerstonejs/ai": "4.9.1",
"@cornerstonejs/core": "4.9.1",
"@cornerstonejs/labelmap-interpolation": "4.9.1",
"@cornerstonejs/polymorphic-segmentation": "4.9.1",
"@cornerstonejs/tools": "4.9.1",
"@cornerstonejs/adapters": "4.11.2",
"@cornerstonejs/ai": "4.11.2",
"@cornerstonejs/core": "4.11.2",
"@cornerstonejs/labelmap-interpolation": "4.11.2",
"@cornerstonejs/polymorphic-segmentation": "4.11.2",
"@cornerstonejs/tools": "4.11.2",
"@itk-wasm/morphological-contour-interpolation": "1.1.0",
"@kitware/vtk.js": "32.12.0",
"html2canvas": "1.4.1",
+41 -2
View File
@@ -1760,6 +1760,16 @@ function commandsModule({
segmentationService.setStyle({ type }, { renderFill: value });
},
/**
* Sets whether to render fill for inactive segmentations of a segmentation type
* @param props.type - The type of segmentation
* @param props.value - Whether to render fill for inactive segmentations
*/
setRenderFillInactiveCommand: ({ type, value }) => {
const { segmentationService } = servicesManager.services;
segmentationService.setStyle({ type }, { renderFillInactive: value });
},
/**
* Sets whether to render outline for a segmentation type
* @param props.type - The type of segmentation
@@ -1771,13 +1781,36 @@ function commandsModule({
},
/**
* Sets the fill alpha for inactive segmentations
* Sets whether to render outline for inactive segmentations of a segmentation type
* @param props.type - The type of segmentation
* @param props.value - Whether to render outline for inactive segmentations
*/
setRenderOutlineInactiveCommand: ({ type, value }) => {
const { segmentationService } = servicesManager.services;
segmentationService.setStyle({ type }, { renderOutlineInactive: value });
},
/**
* Sets the fill alpha for inactive segmentations.
* If no type is provided, the fill alpha for all types will be set.
* @param props.type - The type of segmentation
* @param props.value - The alpha value to set
*/
setFillAlphaInactiveCommand: ({ type, value }) => {
const { segmentationService } = servicesManager.services;
segmentationService.setStyle({ type }, { fillAlphaInactive: value });
if (type) {
segmentationService.setStyle({ type }, { fillAlphaInactive: value });
} else {
segmentationService.setStyle(
{ type: SegmentationRepresentations.Labelmap },
{ fillAlphaInactive: value }
);
segmentationService.setStyle(
{ type: SegmentationRepresentations.Contour },
{ fillAlphaInactive: value }
);
}
},
editSegmentLabel: async ({ segmentationId, segmentIndex }) => {
@@ -2708,9 +2741,15 @@ function commandsModule({
setRenderFill: {
commandFn: actions.setRenderFillCommand,
},
setRenderFillInactive: {
commandFn: actions.setRenderFillInactiveCommand,
},
setRenderOutline: {
commandFn: actions.setRenderOutlineCommand,
},
setRenderOutlineInactive: {
commandFn: actions.setRenderOutlineInactiveCommand,
},
setFillAlphaInactive: {
commandFn: actions.setFillAlphaInactiveCommand,
},
@@ -3,9 +3,28 @@ import { CustomSegmentStatisticsHeader } from './CustomSegmentStatisticsHeader';
import SegmentationToolConfig from '../components/SegmentationToolConfig';
import React from 'react';
import { SegmentationRepresentations } from '@cornerstonejs/tools/enums';
import * as cornerstoneTools from '@cornerstonejs/tools';
export default function getSegmentationPanelCustomization({ commandsManager, servicesManager }) {
const { segmentationService } = servicesManager.services;
let contourRenderFillChangedGlobally = false;
// Listen to when the global CONTOUR type renderFill style property is changed.
const { unsubscribe } = segmentationService.subscribe(
segmentationService.EVENTS.SEGMENTATION_STYLE_MODIFIED,
({ specifier, style }) => {
if (
specifier.type === SegmentationRepresentations.Contour &&
specifier.segmentationId == null &&
specifier.viewportId == null &&
style.renderFill != null
) {
unsubscribe();
contourRenderFillChangedGlobally = true;
}
}
);
return {
'panelSegmentation.customDropdownMenuContent': CustomDropdownMenuContent,
'panelSegmentation.customSegmentStatisticsHeader': CustomSegmentStatisticsHeader,
@@ -22,11 +41,51 @@ export default function getSegmentationPanelCustomization({ commandsManager, ser
const segmentationId = await commandsManager.run('createContourForViewport', {
viewportId,
});
cornerstoneTools.segmentation.config.style.setStyle(
{ segmentationId, type: SegmentationRepresentations.Contour },
{
fillAlpha: 0.5,
renderFill: true,
// Override the default (i.e. hydrated RTSTRUCT) style for contours if the global CONTOUR type
// renderFill style property has not been changed.
if (!contourRenderFillChangedGlobally) {
segmentationService.setStyle(
{ segmentationId, type: SegmentationRepresentations.Contour },
{
renderFill: true,
renderFillInactive: true,
},
// Do not merge so that these created contours inherit other type-specific style properties like the fill alpha.
// Merging would otherwise permanently inherit the fill alpha and any inheritance from the type level would be lost.
false
);
}
// If the global CONTOUR type renderFill style property is already set, do not subscribe to the SEGMENTATION_STYLE_MODIFIED event.
if (contourRenderFillChangedGlobally) {
return;
}
// Subscribe to the SEGMENTATION_STYLE_MODIFIED event to listen for changes to the CONTOUR type renderFill style property.
const { unsubscribe } = segmentationService.subscribe(
segmentationService.EVENTS.SEGMENTATION_STYLE_MODIFIED,
({ specifier, style }) => {
if (
specifier.type === SegmentationRepresentations.Contour &&
specifier.segmentationId == null &&
specifier.viewportId == null &&
style.renderFill != null
) {
// We are here because the renderFill style property is globally being changed for ALL contours.
// When this occurs, the desire is for ALL contours to inherit the property. To make this happen,
// we have to clear the style property that was set for this specific segmentation
// when it was created above.
// We can now also unsubscribe because this change only needs to be made when the global CONTOUR type
// renderFill style property is first changed.
contourRenderFillChangedGlobally = true;
unsubscribe();
segmentationService.setStyle(
{ segmentationId, type: SegmentationRepresentations.Contour },
{},
false
);
}
}
);
}
+9 -1
View File
@@ -137,10 +137,18 @@ export default async function init({
getSegmentationPresentationId
);
cornerstoneTools.segmentation.config.style.setStyle(
segmentationService.setStyle(
{ type: SegmentationRepresentations.Contour },
{
// Declare these alpha values at the Contour type level so that they can be set/changed/inherited for all contour segmentations.
fillAlpha: 0.5,
fillAlphaInactive: 0.4,
// In general do not fill contours so that hydrated RTSTRUCTs are not filled in when active or inactive by default.
// However, hydrated RTSTRUCTs are filled in when active or inactive if the user chooses to fill ALL contours.
// Those Contours created in OHIF (i.e. using the Segmentation Panel) will override both fill properties upon creation.
renderFill: false,
renderFillInactive: false,
}
);
@@ -161,18 +161,36 @@ export default function PanelSegmentation({
commandsManager.run('deleteSegmentation', { segmentationId });
},
setFillAlpha: ({ type }, value) => {
commandsManager.run('activateSelectedSegmentationOfType', {
segmentationRepresentationType: type,
});
commandsManager.run('setFillAlpha', { type, value });
},
setOutlineWidth: ({ type }, value) => {
commandsManager.run('activateSelectedSegmentationOfType', {
segmentationRepresentationType: type,
});
commandsManager.run('setOutlineWidth', { type, value });
},
setRenderFill: ({ type }, value) => {
commandsManager.run('activateSelectedSegmentationOfType', {
segmentationRepresentationType: type,
});
commandsManager.run('setRenderFill', { type, value });
},
setRenderFillInactive: ({ type }, value) => {
commandsManager.run('setRenderFillInactive', { type, value });
},
setRenderOutline: ({ type }, value) => {
commandsManager.run('activateSelectedSegmentationOfType', {
segmentationRepresentationType: type,
});
commandsManager.run('setRenderOutline', { type, value });
},
setFillAlphaInactive: ({ type }, value) => {
setRenderOutlineInactive: ({ type }, value) => {
commandsManager.run('setRenderOutlineInactive', { type, value });
},
setFillAlphaInactive: ({ type }: { type?: string }, value) => {
commandsManager.run('setFillAlphaInactive', { type, value });
},
getRenderInactiveSegmentations: () => {
@@ -1954,7 +1954,8 @@ describe('SegmentationService', () => {
type,
segmentIndex,
},
style
style,
true
);
});
});
@@ -2829,7 +2830,8 @@ describe('SegmentationService', () => {
segmentIndex,
type: csToolsEnums.SegmentationRepresentations.Labelmap,
},
{}
{},
false
);
expect(window.requestAnimationFrame).not.toHaveBeenCalledTimes(3);
@@ -84,6 +84,7 @@ const EVENTS = {
// fired when a contour annotation cut merge process is completed
SEGMENTATION_ANNOTATION_CUT_MERGE_PROCESS_COMPLETED:
'event::annotation_cut_merge_process_completed',
SEGMENTATION_STYLE_MODIFIED: 'event::segmentation_style_modified',
};
const VALUE_TYPES = {};
@@ -835,9 +836,15 @@ class SegmentationService extends PubSubService {
segmentationId?: string;
segmentIndex?: number;
},
style: cstTypes.LabelmapStyle | cstTypes.ContourStyle | cstTypes.SurfaceStyle
style: cstTypes.LabelmapStyle | cstTypes.ContourStyle | cstTypes.SurfaceStyle,
merge: boolean = true
) => {
cstSegmentation.config.style.setStyle(specifier, style);
cstSegmentation.config.style.setStyle(specifier, style, merge);
this._broadcastEvent(EVENTS.SEGMENTATION_STYLE_MODIFIED, {
specifier,
style,
merge,
});
};
public resetToGlobalStyle = () => {
@@ -1737,7 +1744,8 @@ class SegmentationService extends PubSubService {
segmentIndex,
type: LABELMAP,
},
{}
{},
false
);
}
};
+2 -2
View File
@@ -32,8 +32,8 @@
"start": "yarn run dev"
},
"peerDependencies": {
"@cornerstonejs/core": "4.9.1",
"@cornerstonejs/tools": "4.9.1",
"@cornerstonejs/core": "4.11.2",
"@cornerstonejs/tools": "4.11.2",
"@ohif/core": "3.12.0-beta.86",
"@ohif/extension-cornerstone-dicom-sr": "3.12.0-beta.86",
"@ohif/extension-default": "3.12.0-beta.86",
+2 -2
View File
@@ -42,8 +42,8 @@
},
"dependencies": {
"@babel/runtime": "7.28.2",
"@cornerstonejs/core": "4.9.1",
"@cornerstonejs/tools": "4.9.1",
"@cornerstonejs/core": "4.11.2",
"@cornerstonejs/tools": "4.11.2",
"@ohif/core": "3.12.0-beta.86",
"@ohif/extension-cornerstone": "3.12.0-beta.86",
"@ohif/extension-default": "3.12.0-beta.86",