ohif-viewer/modes/segmentation/src/index.tsx
Bill Wallace 57205703bb
Fix contour annotations RTSS saving (#5545)
* [WIP] Fix contour annotations RTSS saving

* fix: Export image sop image reference provider

* Fixes for download with filename

* Updates to fix metadata issues with CS3D

* Updates to save menu

* Fixing some additional re-save metadata

* Add support for saving with predecessor image sequence

* PR comment fixes

* fix: Load RTSS after save

* fix segmentation save

* fix load of mixed seg/rt studies

* Use frame module to get information on the frame shown

* Fix study browser to use instances.length when numImageFrames not present

* fix: Load of display set split from multiple non-FOR referenes

* Fix RT and SEG applying to wrong series because of using
referenced series sequence without checkout sop instances

* Fix save seg

* Update to released CS3D versions

* test: Add revokeObjectURL to test download blob

* Add hard dependency on mode-basic to fix build order issues

* fix: build

* Try to fix build segmentation

* PR comments

* PR comment update
2025-11-21 13:07:26 -05:00

250 lines
8.0 KiB
TypeScript

import { id } from './id';
import toolbarButtons from './toolbarButtons';
import initToolGroups from './initToolGroups';
import setUpAutoTabSwitchHandler from './utils/setUpAutoTabSwitchHandler';
import { ohif, cornerstone, extensionDependencies, dicomRT, segmentation } from '@ohif/mode-basic';
export * from './toolbarButtons';
function modeFactory({ modeConfiguration }) {
const _unsubscriptions = [];
return {
/**
* Mode ID, which should be unique among modes used by the viewer. This ID
* is used to identify the mode in the viewer's state.
*/
id,
routeName: 'segmentation',
/**
* Mode name, which is displayed in the viewer's UI in the workList, for the
* user to select the mode.
*/
displayName: 'Segmentation',
/**
* Runs when the Mode Route is mounted to the DOM. Usually used to initialize
* Services and other resources.
*/
onModeEnter: ({ servicesManager, extensionManager, commandsManager }: withAppTypes) => {
const {
measurementService,
toolbarService,
toolGroupService,
segmentationService,
viewportGridService,
panelService,
} = servicesManager.services;
measurementService.clearMeasurements();
// Init Default and SR ToolGroups
initToolGroups(extensionManager, toolGroupService, commandsManager);
toolbarService.register(toolbarButtons);
toolbarService.updateSection(toolbarService.sections.primary, [
'WindowLevel',
'Pan',
'Zoom',
'TrackballRotate',
'Capture',
'Layout',
'Crosshairs',
'MoreTools',
]);
toolbarService.updateSection(toolbarService.sections.viewportActionMenu.topLeft, [
'orientationMenu',
'dataOverlayMenu',
]);
toolbarService.updateSection(toolbarService.sections.viewportActionMenu.bottomMiddle, [
'AdvancedRenderingControls',
]);
toolbarService.updateSection('AdvancedRenderingControls', [
'windowLevelMenuEmbedded',
'voiManualControlMenu',
'Colorbar',
'opacityMenu',
'thresholdMenu',
]);
toolbarService.updateSection(toolbarService.sections.viewportActionMenu.topRight, [
'modalityLoadBadge',
'trackingStatus',
'navigationComponent',
]);
toolbarService.updateSection(toolbarService.sections.viewportActionMenu.bottomLeft, [
'windowLevelMenu',
]);
toolbarService.updateSection('MoreTools', [
'Reset',
'rotate-right',
'flipHorizontal',
'ReferenceLines',
'ImageOverlayViewer',
'StackScroll',
'invert',
'Cine',
'Magnify',
'TagBrowser',
]);
toolbarService.updateSection(toolbarService.sections.labelMapSegmentationToolbox, [
'LabelMapTools',
]);
toolbarService.updateSection(toolbarService.sections.contourSegmentationToolbox, [
'ContourTools',
]);
toolbarService.updateSection('LabelMapTools', [
'LabelmapSlicePropagation',
'BrushTools',
'MarkerLabelmap',
'RegionSegmentPlus',
'Shapes',
'LabelMapEditWithContour',
]);
toolbarService.updateSection('ContourTools', [
'PlanarFreehandContourSegmentationTool',
'SculptorTool',
'SplineContourSegmentationTool',
'LivewireContourSegmentationTool',
]);
toolbarService.updateSection(toolbarService.sections.labelMapSegmentationUtilities, [
'LabelMapUtilities',
]);
toolbarService.updateSection(toolbarService.sections.contourSegmentationUtilities, [
'ContourUtilities',
]);
toolbarService.updateSection('LabelMapUtilities', [
'InterpolateLabelmap',
'SegmentBidirectional',
]);
toolbarService.updateSection('ContourUtilities', [
'LogicalContourOperations',
'SimplifyContours',
'SmoothContours',
]);
toolbarService.updateSection('BrushTools', ['Brush', 'Eraser', 'Threshold']);
const { unsubscribeAutoTabSwitchEvents } = setUpAutoTabSwitchHandler({
segmentationService,
viewportGridService,
panelService,
});
_unsubscriptions.push(...unsubscribeAutoTabSwitchEvents);
},
onModeExit: ({ servicesManager }: withAppTypes) => {
const {
toolGroupService,
syncGroupService,
segmentationService,
cornerstoneViewportService,
uiDialogService,
uiModalService,
} = servicesManager.services;
_unsubscriptions.forEach(unsubscribe => unsubscribe());
_unsubscriptions.length = 0;
uiDialogService.hideAll();
uiModalService.hide();
toolGroupService.destroy();
syncGroupService.destroy();
segmentationService.destroy();
cornerstoneViewportService.destroy();
},
/** */
validationTags: {
study: [],
series: [],
},
/**
* A boolean return value that indicates whether the mode is valid for the
* modalities of the selected studies. Currently we don't have stack viewport
* segmentations and we should exclude them
*/
isValidMode: ({ modalities }) => {
// Don't show the mode if the selected studies have only one modality
// that is not supported by the mode
const modalitiesArray = modalities.split('\\');
return {
valid:
modalitiesArray.length === 1
? !['SM', 'ECG', 'OT', 'DOC'].includes(modalitiesArray[0])
: true,
description:
'The mode does not support studies that ONLY include the following modalities: SM, OT, DOC',
};
},
/**
* Mode Routes are used to define the mode's behavior. A list of Mode Route
* that includes the mode's path and the layout to be used. The layout will
* include the components that are used in the layout. For instance, if the
* default layoutTemplate is used (id: '@ohif/extension-default.layoutTemplateModule.viewerLayout')
* it will include the leftPanels, rightPanels, and viewports. However, if
* you define another layoutTemplate that includes a Footer for instance,
* you should provide the Footer component here too. Note: We use Strings
* to reference the component's ID as they are registered in the internal
* ExtensionManager. The template for the string is:
* `${extensionId}.{moduleType}.${componentId}`.
*/
routes: [
{
path: 'template',
layoutTemplate: ({ location, servicesManager }) => {
return {
id: ohif.layout,
props: {
leftPanels: [ohif.thumbnailList],
leftPanelResizable: true,
rightPanels: [
cornerstone.labelMapSegmentationPanel,
cornerstone.contourSegmentationPanel,
],
rightPanelResizable: true,
// leftPanelClosed: true,
viewports: [
{
namespace: cornerstone.viewport,
displaySetsToDisplay: [ohif.sopClassHandler],
},
{
namespace: segmentation.viewport,
displaySetsToDisplay: [segmentation.sopClassHandler],
},
{
namespace: dicomRT.viewport,
displaySetsToDisplay: [dicomRT.sopClassHandler],
},
],
},
};
},
},
],
/** List of extensions that are used by the mode */
extensions: extensionDependencies,
/** HangingProtocol used by the mode */
// Commented out to just use the most applicable registered hanging protocol
// The example is used for a grid layout to specify that as a preferred layout
hangingProtocol: ['@ohif/mnGrid'],
/** SopClassHandlers used by the mode */
sopClassHandlers: [ohif.sopClassHandler, segmentation.sopClassHandler, dicomRT.sopClassHandler],
};
}
const mode = {
id,
modeFactory,
extensionDependencies,
};
export default mode;