fix(segmentation): List surface representations in the segmentation table for 3D views. (#5700)

This was done by using an array of representation types instead of a single type for a panel.
The first element of the array is the primary type of the panel, and the rest are secondary types
that can also be displayed in the panel.

PR feedback:
- added test to check number of segments in side panel for 3D only view
- fixed jumping to segment in 3D only view
- fixed exception when adding contour segment in 3D only view
This commit is contained in:
Joe Boccanfuso authored and GitHub committed 2026-01-13 15:57:44 -05:00
1 parent 83c9ce0f5f
commit 82e6a18735
27 files changed
+356 -293

No files matched your search

@@ -46,8 +46,8 @@
},
"dependencies": {
"@babel/runtime": "7.28.2",
"@cornerstonejs/adapters": "4.15.6",
"@cornerstonejs/core": "4.15.6",
"@cornerstonejs/adapters": "4.15.7",
"@cornerstonejs/core": "4.15.7",
"@kitware/vtk.js": "34.15.1",
"react-color": "2.19.3"
}
@@ -46,8 +46,8 @@
},
"dependencies": {
"@babel/runtime": "7.28.2",
"@cornerstonejs/adapters": "4.15.6",
"@cornerstonejs/core": "4.15.6",
"@cornerstonejs/adapters": "4.15.7",
"@cornerstonejs/core": "4.15.7",
"@kitware/vtk.js": "34.15.1",
"react-color": "2.19.3"
}
+3 -3
View File
@@ -46,9 +46,9 @@
},
"dependencies": {
"@babel/runtime": "7.28.2",
"@cornerstonejs/adapters": "4.15.6",
"@cornerstonejs/core": "4.15.6",
"@cornerstonejs/tools": "4.15.6",
"@cornerstonejs/adapters": "4.15.7",
"@cornerstonejs/core": "4.15.7",
"@cornerstonejs/tools": "4.15.7",
"classnames": "2.5.1"
}
}
@@ -42,8 +42,8 @@
},
"dependencies": {
"@babel/runtime": "7.28.2",
"@cornerstonejs/core": "4.15.6",
"@cornerstonejs/tools": "4.15.6",
"@cornerstonejs/core": "4.15.7",
"@cornerstonejs/tools": "4.15.7",
"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.15.6",
"@cornerstonejs/dicom-image-loader": "4.15.7",
"@ohif/core": "3.12.0-beta.121",
"@ohif/ui": "3.12.0-beta.121",
"dcmjs": "0.45.0",
@@ -53,12 +53,12 @@
},
"dependencies": {
"@babel/runtime": "7.28.2",
"@cornerstonejs/adapters": "4.15.6",
"@cornerstonejs/ai": "4.15.6",
"@cornerstonejs/core": "4.15.6",
"@cornerstonejs/labelmap-interpolation": "4.15.6",
"@cornerstonejs/polymorphic-segmentation": "4.15.6",
"@cornerstonejs/tools": "4.15.6",
"@cornerstonejs/adapters": "4.15.7",
"@cornerstonejs/ai": "4.15.7",
"@cornerstonejs/core": "4.15.7",
"@cornerstonejs/labelmap-interpolation": "4.15.7",
"@cornerstonejs/polymorphic-segmentation": "4.15.7",
"@cornerstonejs/tools": "4.15.7",
"@itk-wasm/morphological-contour-interpolation": "1.1.0",
"@kitware/vtk.js": "34.15.1",
"html2canvas": "1.4.1",
@@ -5,6 +5,7 @@ import { utils } from '../..';
import { useViewportSegmentations } from '../../hooks';
import { useMeasurementTracking } from '../../hooks/useMeasurementTracking';
import { useViewportDisplaySets } from '../../hooks/useViewportDisplaySets';
import { SegmentationRepresentations } from '@cornerstonejs/tools/enums';
/**
* NavigationComponent provides navigation controls for viewports containing
@@ -27,7 +28,11 @@ function NavigationComponent({ viewportId }: { viewportId: string }) {
viewportId,
});
const hasSegmentations = segmentationsWithRepresentations.length > 0;
const hasSegmentations =
segmentationsWithRepresentations.length > 0 &&
segmentationsWithRepresentations.some(
segmentation => segmentation?.representation?.type !== SegmentationRepresentations.Surface
);
// prefer segment navigation if available
const navigationMode = hasSegmentations
@@ -26,7 +26,7 @@ export const CustomDropdownMenuContent = () => {
exportOptions,
activeSegmentation,
activeSegmentationId,
segmentationRepresentationType,
segmentationRepresentationTypes,
disableEditing,
} = useSegmentationTableContext('CustomDropdownMenu');
@@ -79,7 +79,12 @@ export const CustomDropdownMenuContent = () => {
<DropdownMenuContent align="start">
{!disableEditing && (
<DropdownMenuItem
onClick={() => onSegmentationAdd({ segmentationId, segmentationRepresentationType })}
onClick={() =>
onSegmentationAdd({
segmentationId,
segmentationRepresentationType: segmentationRepresentationTypes?.[0],
})
}
>
<Icons.Add className="text-foreground" />
<span className="pl-2">{t('Create New Segmentation')}</span>
@@ -97,7 +102,7 @@ export const CustomDropdownMenuContent = () => {
</DropdownMenuItem>
<ExportSegmentationSubMenuItem
segmentationId={segmentationId}
segmentationRepresentationType={segmentationRepresentationType}
segmentationRepresentationType={segmentationRepresentationTypes?.[0]}
allowExport={allowExport}
actions={actions}
/>
+10 -7
View File
@@ -24,7 +24,7 @@ const getPanelModule = ({ commandsManager, servicesManager, extensionManager }:
configuration={{
...props?.configuration,
}}
segmentationRepresentationType={props?.segmentationRepresentationType}
segmentationRepresentationTypes={props?.segmentationRepresentationTypes}
/>
);
};
@@ -38,20 +38,20 @@ const getPanelModule = ({ commandsManager, servicesManager, extensionManager }:
configuration={{
...props?.configuration,
}}
segmentationRepresentationType={props?.segmentationRepresentationType}
segmentationRepresentationTypes={props?.segmentationRepresentationTypes}
/>
);
};
const wrappedPanelSegmentationWithTools = props => {
const { t } = useTranslation('SegmentationPanel');
const tKey = `${props.segmentationRepresentationType ?? 'Segmentation'} tools`;
const tKey = `${props.segmentationRepresentationTypes?.[0] ?? 'Segmentation'} tools`;
const tValue = t(tKey);
return (
<>
<Toolbox
buttonSectionId={toolSectionMap[props.segmentationRepresentationType]}
buttonSectionId={toolSectionMap[props.segmentationRepresentationTypes?.[0]]}
title={tValue}
/>
<PanelSegmentation
@@ -61,7 +61,7 @@ const getPanelModule = ({ commandsManager, servicesManager, extensionManager }:
configuration={{
...props?.configuration,
}}
segmentationRepresentationType={props?.segmentationRepresentationType}
segmentationRepresentationTypes={props?.segmentationRepresentationTypes}
/>
</>
);
@@ -103,7 +103,10 @@ const getPanelModule = ({ commandsManager, servicesManager, extensionManager }:
component: props =>
wrappedPanelSegmentationWithTools({
...props,
segmentationRepresentationType: SegmentationRepresentations.Labelmap,
segmentationRepresentationTypes: [
SegmentationRepresentations.Labelmap,
SegmentationRepresentations.Surface,
],
}),
},
{
@@ -114,7 +117,7 @@ const getPanelModule = ({ commandsManager, servicesManager, extensionManager }:
component: props =>
wrappedPanelSegmentationWithTools({
...props,
segmentationRepresentationType: SegmentationRepresentations.Contour,
segmentationRepresentationTypes: [SegmentationRepresentations.Contour],
}),
},
];
@@ -21,13 +21,14 @@ import {
type PanelSegmentationProps = {
children?: React.ReactNode;
// The representation type for this segmentation panel. Undefined means all types.
segmentationRepresentationType?: SegmentationRepresentations;
// The representation types for this segmentation panel. Undefined means all types.
// The first element is the primary type. Additional elements are secondary types.
segmentationRepresentationTypes?: SegmentationRepresentations[];
} & withAppTypes;
export default function PanelSegmentation({
children,
segmentationRepresentationType,
segmentationRepresentationTypes,
}: PanelSegmentationProps) {
const { commandsManager, servicesManager } = useSystem();
const {
@@ -48,11 +49,20 @@ export default function PanelSegmentation({
store => store.selectedSegmentationsForViewport[activeViewportId]
);
const selectedSegmentationIdForType = segmentationRepresentationType
? selectedSegmentationsForViewportMap?.get(segmentationRepresentationType)
const selectedSegmentationIdForType = segmentationRepresentationTypes
? segmentationRepresentationTypes.reduce(
(selectedSegmentation, type) =>
selectedSegmentation ||
(selectedSegmentationsForViewportMap?.has(type)
? selectedSegmentationsForViewportMap?.get(type)
: undefined),
undefined
)
: segmentationService?.getActiveSegmentation(activeViewportId)?.segmentationId;
const buttonSection = utilitiesSectionMap[segmentationRepresentationType];
const buttonSection = segmentationRepresentationTypes?.[0]
? utilitiesSectionMap[segmentationRepresentationTypes[0]]
: undefined;
const { activeToolOptions: activeUtilityOptions } = useActiveToolOptions({
buttonSectionId: buttonSection,
@@ -129,7 +139,7 @@ export default function PanelSegmentation({
commandsManager.run('deleteSegment', { segmentationId, segmentIndex });
},
onSegmentCopy:
segmentationRepresentationType === SegmentationRepresentations.Contour
segmentationRepresentationTypes?.[0] === SegmentationRepresentations.Contour
? (segmentationId, segmentIndex) => {
commandsManager.run('copyContourSegment', {
sourceSegmentInfo: { segmentationId, segmentIndex },
@@ -227,13 +237,13 @@ export default function PanelSegmentation({
disabled,
data: segmentationsWithRepresentations,
mode: segmentationTableMode,
title: `${segmentationRepresentationType ? `${segmentationRepresentationType} ` : ''}Segmentations`,
title: `${segmentationRepresentationTypes?.[0] ? `${segmentationRepresentationTypes[0]} ` : ''}Segmentations`,
exportOptions,
disableEditing,
onSegmentationAdd,
showAddSegment,
renderInactiveSegmentations: handlers.getRenderInactiveSegmentations(),
segmentationRepresentationType,
segmentationRepresentationTypes,
selectedSegmentationIdForType,
...handlers,
};
@@ -1271,6 +1271,10 @@ class SegmentationService extends PubSubService {
viewportIds.forEach(viewportId => {
const { viewport } = getEnabledElementByViewportId(viewportId);
if (!viewport?.jumpToWorld) {
return;
}
viewport.jumpToWorld(world);
highlightSegment &&
+2 -2
View File
@@ -32,8 +32,8 @@
"start": "yarn run dev"
},
"peerDependencies": {
"@cornerstonejs/core": "4.15.6",
"@cornerstonejs/tools": "4.15.6",
"@cornerstonejs/core": "4.15.7",
"@cornerstonejs/tools": "4.15.7",
"@ohif/core": "3.12.0-beta.121",
"@ohif/extension-cornerstone-dicom-sr": "3.12.0-beta.121",
"@ohif/extension-default": "3.12.0-beta.121",
+2 -2
View File
@@ -42,8 +42,8 @@
},
"dependencies": {
"@babel/runtime": "7.28.2",
"@cornerstonejs/core": "4.15.6",
"@cornerstonejs/tools": "4.15.6",
"@cornerstonejs/core": "4.15.7",
"@cornerstonejs/tools": "4.15.7",
"@ohif/core": "3.12.0-beta.121",
"@ohif/extension-cornerstone": "3.12.0-beta.121",
"@ohif/extension-default": "3.12.0-beta.121",