feat: improve segment label (#5217)

Co-authored-by: Joe Boccanfuso <joe.boccanfuso@radicalimaging.com>
This commit is contained in:
Pedro Köhler 2025-07-29 23:30:18 -03:00 committed by GitHub
parent dd896fea25
commit 271b84f78f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 163 additions and 44 deletions

View File

@ -1,7 +1,14 @@
function createSEGToolGroupAndAddTools(ToolGroupService, customizationService, toolGroupId) {
function createSEGToolGroupAndAddTools({
commandsManager,
toolGroupService,
customizationService,
toolGroupId,
}) {
const tools = customizationService.getCustomization('cornerstone.overlayViewportTools');
return ToolGroupService.createToolGroupAndAddTools(toolGroupId, tools);
const updatedTools = commandsManager.run('initializeSegmentLabelTool', { tools });
return toolGroupService.createToolGroupAndAddTools(toolGroupId, updatedTools);
}
export default createSEGToolGroupAndAddTools;

View File

@ -214,7 +214,12 @@ function OHIFCornerstoneSEGViewport(props: withAppTypes) {
// This creates a custom tool group which has the lifetime of this view
// only, and does NOT interfere with currently displayed segmentations.
toolGroup = createSEGToolGroupAndAddTools(toolGroupService, customizationService, toolGroupId);
toolGroup = createSEGToolGroupAndAddTools({
commandsManager,
toolGroupService,
customizationService,
toolGroupId,
});
return () => {
// remove the segmentation representations if seg displayset changed

View File

@ -102,6 +102,7 @@ let segmentAIEnabled = false;
function commandsModule({
servicesManager,
commandsManager,
extensionManager,
}: OhifTypes.Extensions.ExtensionParams): OhifTypes.Extensions.CommandsModule {
const {
viewportGridService,
@ -1743,17 +1744,62 @@ function commandsModule({
}
});
},
toggleSegmentLabel: ({ toggle }) => {
toggleSegmentLabel: () => {
const toolName = cornerstoneTools.SegmentLabelTool.toolName;
const toolGroupIds = toolGroupService.getToolGroupIds();
const isOn = toolGroupIds.some(toolGroupId => {
const toolGroup = cornerstoneTools.ToolGroupManager.getToolGroup(toolGroupId);
const mode = toolGroup.getToolInstance(toolName)?.mode;
return mode === 'Active';
});
toolGroupIds.forEach(toolGroupId => {
const toolGroup = cornerstoneTools.ToolGroupManager.getToolGroup(toolGroupId);
if (toggle) {
toolGroup.setToolActive(cornerstoneTools.SegmentLabelTool.toolName);
if (isOn) {
toolGroup.setToolDisabled(toolName);
} else {
toolGroup.setToolDisabled(cornerstoneTools.SegmentLabelTool.toolName);
toolGroup.setToolActive(toolName);
}
});
},
/**
* Used to sync the apps initial state with the config file settings.
*
* Will mutate the tools object of the given tool group and add the segmentLabelTool to the proper place.
*
* Use it before initializing the toolGroup with the tools.
*/
initializeSegmentLabelTool: ({ tools }) => {
const appConfig = extensionManager.appConfig;
const segmentLabelConfig = appConfig.segmentation?.segmentLabel;
if (segmentLabelConfig?.enabledByDefault) {
const activeTools = tools?.active ?? [];
activeTools.push({
toolName: toolNames.SegmentLabel,
configuration: {
hoverTimeout: segmentLabelConfig?.hoverTimeout ?? 1,
color: segmentLabelConfig?.labelColor,
background: segmentLabelConfig?.background,
},
});
tools.active = activeTools;
return tools;
}
const disabledTools = tools?.disabled ?? [];
disabledTools.push({
toolName: toolNames.SegmentLabel,
configuration: {
hoverTimeout: segmentLabelConfig?.hoverTimeout ?? 1,
color: segmentLabelConfig?.labelColor,
},
});
tools.disabled = disabledTools;
return tools;
},
toggleUseCenterSegmentIndex: ({ toggle }) => {
let labelmapTools = getLabelmapTools({ toolGroupService });
labelmapTools = labelmapTools.filter(tool => !tool.toolName.includes('Eraser'));
@ -2369,6 +2415,7 @@ function commandsModule({
startRecordingForAnnotationGroup: actions.startRecordingForAnnotationGroup,
endRecordingForAnnotationGroup: actions.endRecordingForAnnotationGroup,
toggleSegmentLabel: actions.toggleSegmentLabel,
initializeSegmentLabelTool: actions.initializeSegmentLabelTool,
};
return {

View File

@ -38,7 +38,6 @@ export default function getSegmentationPanelCustomization({ commandsManager, ser
const [previewEdits, setPreviewEdits] = useState(false);
const [toggleSegmentEnabled, setToggleSegmentEnabled] = useState(false);
const [useCenterAsSegmentIndex, setUseCenterAsSegmentIndex] = useState(false);
const [shouldShowLabelOnHover, setShouldShowLabelOnHover] = useState(false);
const handlePreviewEditsChange = checked => {
setPreviewEdits(checked);
commandsManager.run('toggleSegmentPreviewEdit', { toggle: checked });
@ -54,11 +53,6 @@ export default function getSegmentationPanelCustomization({ commandsManager, ser
commandsManager.run('toggleUseCenterSegmentIndex', { toggle: checked });
};
const handleToggleShowLabelOnHover = checked => {
setShouldShowLabelOnHover(checked);
commandsManager.run('toggleSegmentLabel', { 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">
@ -84,14 +78,6 @@ export default function getSegmentationPanelCustomization({ commandsManager, ser
/>
<span className="text-base text-white">Hover on segment border to activate</span>
</div>
<div className="flex items-center gap-2">
<Switch
checked={shouldShowLabelOnHover}
onCheckedChange={handleToggleShowLabelOnHover}
/>
<span className="text-base text-white">Show segment label on mouse hover</span>
</div>
</div>
);
},

View File

@ -12,13 +12,17 @@ import miscCustomization from './customizations/miscCustomization';
import captureViewportModalCustomization from './customizations/captureViewportModalCustomization';
import viewportDownloadWarningCustomization from './customizations/viewportDownloadWarningCustomization';
function getCustomizationModule({ commandsManager, servicesManager }) {
function getCustomizationModule({ commandsManager, servicesManager, extensionManager }) {
return [
{
name: 'default',
value: {
...viewportOverlayCustomization,
...getSegmentationPanelCustomization({ commandsManager, servicesManager }),
...getSegmentationPanelCustomization({
commandsManager,
servicesManager,
extensionManager,
}),
...layoutSelectorCustomization,
...viewportToolsCustomization,
...viewportClickCommandsCustomization,

View File

@ -161,6 +161,7 @@ function modeFactory({ modeConfiguration }) {
'AdvancedMagnify',
'UltrasoundDirectionalTool',
'WindowLevelRegion',
'SegmentLabelTool',
]);
customizationService.setCustomizations({

View File

@ -95,16 +95,12 @@ function initDefaultToolGroup(extensionManager, toolGroupService, commandsManage
{
toolName: toolNames.AdvancedMagnify,
},
{
toolName: toolNames.SegmentLabel,
configuration: {
hoverTimeout: 1,
},
},
],
};
toolGroupService.createToolGroupAndAddTools(toolGroupId, tools);
const updatedTools = commandsManager.run('initializeSegmentLabelTool', { tools });
toolGroupService.createToolGroupAndAddTools(toolGroupId, updatedTools);
}
function initSRToolGroup(extensionManager, toolGroupService) {

View File

@ -654,6 +654,30 @@ const toolbarButtons: Button[] = [
},
},
},
// Section containers for the nested toolbox
{
id: 'SegmentationUtilities',
uiType: 'ohif.toolBoxButton',
props: {
buttonSection: true,
},
},
{
id: 'SegmentLabelTool',
uiType: 'ohif.toolBoxButton',
props: {
icon: 'tool-segment-label',
label: 'Segment Label Display',
tooltip: 'Click to show or hide segment labels when hovering with your mouse.',
commands: { commandName: 'toggleSegmentLabel' },
evaluate: [
'evaluate.cornerstoneTool.toggle',
{
name: 'evaluate.cornerstone.hasSegmentation',
},
],
},
},
// {
// id: 'Undo',
// uiType: 'ohif.toolButton',

View File

@ -122,6 +122,7 @@ function modeFactory({ modeConfiguration }) {
'LabelmapSlicePropagation',
'InterpolateLabelmap',
'SegmentBidirectional',
'SegmentLabelTool',
]);
toolbarService.updateSection('SegmentationTools', [
'BrushTools',

View File

@ -10,9 +10,10 @@ const colorsByOrientation = {
coronal: 'rgb(0, 200, 0)',
};
function createTools(utilityModule) {
function createTools({ utilityModule, commandsManager }) {
const { toolNames, Enums } = utilityModule.exports;
return {
const tools = {
active: [
{ toolName: toolNames.WindowLevel, bindings: [{ mouseButton: Enums.MouseBindings.Primary }] },
{ toolName: toolNames.Pan, bindings: [{ mouseButton: Enums.MouseBindings.Auxiliary }] },
@ -114,24 +115,19 @@ function createTools(utilityModule) {
{ toolName: toolNames.UltrasoundDirectional },
],
disabled: [
{ toolName: toolNames.ReferenceLines },
{ toolName: toolNames.AdvancedMagnify },
{
toolName: toolNames.SegmentLabel,
configuration: {
hoverTimeout: 1,
},
},
],
disabled: [{ toolName: toolNames.ReferenceLines }, { toolName: toolNames.AdvancedMagnify }],
};
const updatedTools = commandsManager.run('initializeSegmentLabelTool', { tools });
return updatedTools;
}
function initDefaultToolGroup(extensionManager, toolGroupService, commandsManager, toolGroupId) {
const utilityModule = extensionManager.getModuleEntry(
'@ohif/extension-cornerstone.utilityModule.tools'
);
const tools = createTools(utilityModule);
const tools = createTools({ commandsManager, utilityModule });
toolGroupService.createToolGroupAndAddTools(toolGroupId, tools);
}
@ -141,7 +137,7 @@ function initMPRToolGroup(extensionManager, toolGroupService, commandsManager) {
);
const servicesManager = extensionManager._servicesManager;
const { cornerstoneViewportService } = servicesManager.services;
const tools = createTools(utilityModule);
const tools = createTools({ commandsManager, utilityModule });
tools.disabled.push(
{
toolName: utilityModule.exports.toolNames.Crosshairs,

View File

@ -739,6 +739,22 @@ const toolbarButtons: Button[] = [
],
},
},
{
id: 'SegmentLabelTool',
uiType: 'ohif.toolBoxButton',
props: {
icon: 'tool-segment-label',
label: 'Segment Label Display',
tooltip: 'Click to show or hide segment labels when hovering with your mouse.',
commands: { commandName: 'toggleSegmentLabel' },
evaluate: [
'evaluate.cornerstoneTool.toggle',
{
name: 'evaluate.cornerstone.hasSegmentation',
},
],
},
},
];
export default toolbarButtons;

View File

@ -275,6 +275,14 @@ window.config = {
// Could use services manager here to bring up a dialog/modal if needed.
console.warn('test, navigate to https://ohif.org/');
},
// segmentation: {
// segmentLabel: {
// enabledByDefault: true,
// labelColor: [255, 255, 0, 1], // must be an array
// hoverTimeout: 1,
// background: 'rgba(100, 100, 100, 0.5)', // can be any valid css color
// },
// },
// whiteLabeling: {
// createLogoComponentFn: function (React) {
// return React.createElement(

View File

@ -145,6 +145,7 @@ import {
ToolContract,
ToolExpand,
ToolClickSegment,
ToolSegmentLabel,
} from './Sources/Tools';
import ActionNewDialog from './Sources/ActionNewDialog';
import NotificationInfo from './Sources/NotificationInfo';
@ -730,6 +731,7 @@ export const Icons = {
'tool-ultrasound-bidirectional': (props: IconProps) => ToolUltrasoundBidirectional(props),
'tool-window-level': (props: IconProps) => ToolWindowLevel(props),
'tool-window-region': (props: IconProps) => ToolWindowRegion(props),
'tool-segment-label': (props: IconProps) => ToolSegmentLabel(props),
'icon-tool-window-region': (props: IconProps) => ToolWindowRegion(props),
'icon-tool-ultrasound-bidirectional': (props: IconProps) => ToolUltrasoundBidirectional(props),
'icon-tool-cobb-angle': (props: IconProps) => ToolCobbAngle(props),

View File

@ -2983,6 +2983,32 @@ export const ToolUltrasoundBidirectional = (props: IconProps) => (
</svg>
);
export const ToolSegmentLabel = (props: IconProps) => (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
{...props}
>
<path
d="M2.6636 14.3578C2.58841 14.4105 2.53094 14.4847 2.49878 14.5707C2.46661 14.6567 2.46127 14.7504 2.48345 14.8395C2.50564 14.9286 2.5543 15.0088 2.62302 15.0697C2.69175 15.1305 2.77731 15.1691 2.86842 15.1804L7.42867 16.5722L8.82125 21.1316C8.83209 21.2229 8.87047 21.3088 8.93131 21.3779C8.99214 21.4469 9.07254 21.4958 9.16181 21.518C9.25109 21.5402 9.34502 21.5348 9.43111 21.5023C9.5172 21.4699 9.59139 21.412 9.6438 21.3364L20.4127 10.5683C20.7607 10.1801 20.9661 9.68484 20.9951 9.16423L21 3.82256C21 3.6044 20.9133 3.39518 20.7591 3.24092C20.6048 3.08666 20.3956 3 20.1774 3H14.8382C14.3176 3.02823 13.8223 3.2334 13.4341 3.58155L2.6636 14.3578Z"
stroke="currentColor"
stroke-width="1.2"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M16.8882 8.75787C15.9796 8.75787 15.2431 8.02133 15.2431 7.11276C15.2431 6.20419 15.9796 5.46765 16.8882 5.46765C17.7968 5.46765 18.5333 6.20419 18.5333 7.11276C18.5333 8.02133 17.7968 8.75787 16.8882 8.75787Z"
stroke="currentColor"
stroke-width="1.2"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
);
export const ToolWindowLevel = (props: IconProps) => (
<svg
width="28px"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 195 KiB

After

Width:  |  Height:  |  Size: 195 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 194 KiB

After

Width:  |  Height:  |  Size: 208 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 188 KiB

After

Width:  |  Height:  |  Size: 200 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 189 KiB

After

Width:  |  Height:  |  Size: 190 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 187 KiB

After

Width:  |  Height:  |  Size: 199 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 194 KiB

After

Width:  |  Height:  |  Size: 207 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 138 KiB

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 142 KiB

After

Width:  |  Height:  |  Size: 140 KiB