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

+1 -1
View File
@@ -53,7 +53,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",
"@emotion/serialize": "1.3.3",
"@ohif/core": "3.12.0-beta.121",
"@ohif/extension-cornerstone": "3.12.0-beta.121",
+2 -2
View File
@@ -37,8 +37,8 @@
"@cornerstonejs/codec-libjpeg-turbo-8bit": "1.2.2",
"@cornerstonejs/codec-openjpeg": "1.3.0",
"@cornerstonejs/codec-openjph": "2.4.7",
"@cornerstonejs/core": "4.15.6",
"@cornerstonejs/dicom-image-loader": "4.15.6",
"@cornerstonejs/core": "4.15.7",
"@cornerstonejs/dicom-image-loader": "4.15.7",
"@ohif/ui": "3.12.0-beta.121",
"cornerstone-math": "0.1.9",
"dicom-parser": "1.8.21"
@@ -14,13 +14,13 @@ export const AddSegmentationRow: React.FC<{ children?: React.ReactNode }> = ({
disableEditing,
mode,
disabled,
segmentationRepresentationType,
segmentationRepresentationTypes,
} = useSegmentationTableContext('AddSegmentationRow');
// Check if we have at least one segmentation of the representation type for the panel this component is contained in.
const hasRepresentationType =
(!segmentationRepresentationType && data.length > 0) ||
data.some(info => segmentationRepresentationType === info.representation?.type);
(!segmentationRepresentationTypes && data.length > 0) ||
data.some(info => segmentationRepresentationTypes?.includes(info.representation?.type));
if (hasRepresentationType && mode === 'collapsed') {
return null;
@@ -35,7 +35,11 @@ export const AddSegmentationRow: React.FC<{ children?: React.ReactNode }> = ({
data-cy="addSegmentation"
className={`group ${disabled ? 'pointer-events-none cursor-not-allowed opacity-70' : ''}`}
onClick={() =>
!disabled && onSegmentationAdd({ segmentationId: '', segmentationRepresentationType })
!disabled &&
onSegmentationAdd({
segmentationId: '',
segmentationRepresentationType: segmentationRepresentationTypes?.[0],
})
}
>
{children}
@@ -50,9 +50,8 @@ const SegmentationCollapsedDropdownMenu = ({ children }: { children: React.React
// Selector component - for the segmentation selection dropdown
const SegmentationCollapsedSelector = () => {
const { t } = useTranslation('SegmentationPanel.HeaderCollapsed');
const { data, onSegmentationClick, segmentationRepresentationType } = useSegmentationTableContext(
'SegmentationCollapsedSelector'
);
const { data, onSegmentationClick, segmentationRepresentationTypes } =
useSegmentationTableContext('SegmentationCollapsedSelector');
const { segmentation } = useSegmentationExpanded('SegmentationCollapsedSelector');
if (!data?.length) {
@@ -63,8 +62,8 @@ const SegmentationCollapsedSelector = () => {
// Only show segmentations of the representation type for this panel. Show all segmentations if no type is specified.
.filter(
seg =>
!segmentationRepresentationType ||
segmentationRepresentationType === seg.representation.type
!segmentationRepresentationTypes ||
segmentationRepresentationTypes.includes(seg.representation?.type)
)
.map(seg => ({
id: seg.segmentation.segmentationId,
@@ -132,14 +131,14 @@ const SegmentationCollapsedContent = ({ children }: { children: React.ReactNode
const SegmentationCollapsedRoot: React.FC<{ children?: React.ReactNode }> = ({
children = null,
}) => {
const { mode, data, segmentationRepresentationType, selectedSegmentationIdForType } =
const { mode, data, segmentationRepresentationTypes, selectedSegmentationIdForType } =
useSegmentationTableContext('SegmentationCollapsed');
// Find the segmentations for the representation type for this collapsed view.
const segmentations = data.filter(
segmentation =>
!segmentationRepresentationType ||
segmentationRepresentationType === segmentation.representation?.type
!segmentationRepresentationTypes ||
segmentationRepresentationTypes.includes(segmentation.representation?.type)
);
// Check if we should render.
@@ -93,7 +93,7 @@ const SegmentationExpandedContent = ({ children }: { children: React.ReactNode }
// Main compound component
const SegmentationExpandedRoot = ({ children }) => {
const { data, activeSegmentationId, onSegmentationClick, mode, segmentationRepresentationType } =
const { data, activeSegmentationId, onSegmentationClick, mode, segmentationRepresentationTypes } =
useSegmentationTableContext('SegmentationExpanded');
const { ref: scrollableContainerRef, maxHeight } = useDynamicMaxHeight(data);
@@ -116,8 +116,8 @@ const SegmentationExpandedRoot = ({ children }) => {
{data
.filter(
segmentationInfo =>
!segmentationRepresentationType ||
segmentationInfo.representation.type === segmentationRepresentationType
!segmentationRepresentationTypes ||
segmentationRepresentationTypes.includes(segmentationInfo.representation.type)
)
.map(segmentationInfo => {
const isActive = segmentationInfo.segmentation.segmentationId === activeSegmentationId;
@@ -1,7 +1,10 @@
import React, { ReactNode, useState, Children, isValidElement } from 'react';
import { useTranslation } from 'react-i18next';
import { PanelSection } from '../PanelSection';
import { SegmentationTableProvider, SegmentationTableContextType } from './contexts';
import {
SegmentationTableProvider,
SegmentationTableContextType,
} from './contexts/SegmentationTableContext';
import { SegmentationSegments } from './SegmentationSegments';
import { SegmentStatistics } from './SegmentStatistics';
import { SegmentationTableConfig } from './SegmentationTableConfig';
@@ -25,7 +25,7 @@ export const SegmentationTableConfig: React.FC<{ children?: React.ReactNode }> =
setOutlineWidth,
renderInactiveSegmentations,
toggleRenderInactiveSegmentations,
segmentationRepresentationType,
segmentationRepresentationTypes,
data,
} = useSegmentationTableContext('SegmentationTableConfig');
@@ -49,21 +49,22 @@ export const SegmentationTableConfig: React.FC<{ children?: React.ReactNode }> =
renderFill && renderOutline ? 'fill-and-outline' : renderOutline ? 'outline' : 'fill'
}
onValueChange={value => {
const type = segmentationRepresentationTypes?.[0];
if (value === 'fill-and-outline') {
setRenderFill({ type: segmentationRepresentationType }, true);
setRenderOutline({ type: segmentationRepresentationType }, true);
setRenderFillInactive({ type: segmentationRepresentationType }, true);
setRenderOutlineInactive({ type: segmentationRepresentationType }, true);
setRenderFill({ type }, true);
setRenderOutline({ type }, true);
setRenderFillInactive({ type }, true);
setRenderOutlineInactive({ type }, true);
} else if (value === 'outline') {
setRenderFill({ type: segmentationRepresentationType }, false);
setRenderOutline({ type: segmentationRepresentationType }, true);
setRenderFillInactive({ type: segmentationRepresentationType }, false);
setRenderOutlineInactive({ type: segmentationRepresentationType }, true);
setRenderFill({ type }, false);
setRenderOutline({ type }, true);
setRenderFillInactive({ type }, false);
setRenderOutlineInactive({ type }, true);
} else {
setRenderFill({ type: segmentationRepresentationType }, true);
setRenderOutline({ type: segmentationRepresentationType }, false);
setRenderFillInactive({ type: segmentationRepresentationType }, true);
setRenderOutlineInactive({ type: segmentationRepresentationType }, false);
setRenderFill({ type }, true);
setRenderOutline({ type }, false);
setRenderFillInactive({ type }, true);
setRenderOutlineInactive({ type }, false);
}
}}
>
@@ -90,7 +91,7 @@ export const SegmentationTableConfig: React.FC<{ children?: React.ReactNode }> =
className="mx-1 flex-1"
value={[fillAlpha]}
onValueChange={([value]) =>
setFillAlpha({ type: segmentationRepresentationType }, value)
setFillAlpha({ type: segmentationRepresentationTypes?.[0] }, value)
}
max={1}
min={0}
@@ -100,7 +101,7 @@ export const SegmentationTableConfig: React.FC<{ children?: React.ReactNode }> =
className="mx-1 w-10 flex-none"
value={fillAlpha}
onChange={e =>
setFillAlpha({ type: segmentationRepresentationType }, Number(e.target.value))
setFillAlpha({ type: segmentationRepresentationTypes?.[0] }, Number(e.target.value))
}
/>
</div>
@@ -112,7 +113,7 @@ export const SegmentationTableConfig: React.FC<{ children?: React.ReactNode }> =
<Slider
value={[outlineWidth]}
onValueChange={([value]) =>
setOutlineWidth({ type: segmentationRepresentationType }, value)
setOutlineWidth({ type: segmentationRepresentationTypes?.[0] }, value)
}
max={10}
min={0}
@@ -122,7 +123,10 @@ export const SegmentationTableConfig: React.FC<{ children?: React.ReactNode }> =
<Input
value={outlineWidth}
onChange={e =>
setOutlineWidth({ type: segmentationRepresentationType }, Number(e.target.value))
setOutlineWidth(
{ type: segmentationRepresentationTypes?.[0] },
Number(e.target.value)
)
}
className="mx-1 w-10 flex-none text-center"
/>
@@ -65,11 +65,12 @@ export interface SegmentationTableContextType {
showSegmentIndex?: boolean;
renderInactiveSegmentations?: boolean;
// The type segmentations displayed/filtered in this table. If undefined, show all types.
segmentationRepresentationType?: string;
// The types of segmentations displayed/filtered in this table. If undefined, show all types.
// The first element is the primary type. Additional elements are secondary types.
segmentationRepresentationTypes?: string[];
// The (last) selected segmentation ID for the representation type above.
// If the type above is undefined, then it will store the last active segmentation ID.
// The (last) selected segmentation ID for the representation types above.
// If the representation types above is undefined, then it will store the last active segmentation ID.
selectedSegmentationIdForType?: string;
// Function handlers