Files
ohif-viewer/modes/preclinical-4d/src/index.tsx
2025-05-27 11:05:07 +07:00

197 lines
7.0 KiB
TypeScript

import { id } from './id';
import { hotkeys } from '@ohif/core';
import initWorkflowSteps from './initWorkflowSteps';
import initToolGroups from './initToolGroups';
import toolbarButtons from './toolbarButtons';
import segmentationButtons from './segmentationButtons';
const extensionDependencies = {
'@ohif/extension-default': '3.7.0-beta.76',
'@ohif/extension-cornerstone': '3.7.0-beta.76',
'@ohif/extension-cornerstone-dynamic-volume': '3.7.0-beta.76',
'@ohif/extension-cornerstone-dicom-seg': '3.7.0-beta.76',
'@ohif/extension-tmtv': '3.7.0-beta.76',
};
const ohif = {
layout: '@ohif/extension-default.layoutTemplateModule.viewerLayout',
defaultSopClassHandler: '@ohif/extension-default.sopClassHandlerModule.stack',
chartSopClassHandler: '@ohif/extension-default.sopClassHandlerModule.chart',
hangingProtocol: '@ohif/extension-default.hangingProtocolModule.default',
leftPanel: '@ohif/extension-default.panelModule.seriesList',
rightPanel: '@ohif/extension-default.panelModule.measure',
chartViewport: '@ohif/extension-default.viewportModule.chartViewport',
};
const dynamicVolume = {
leftPanel: '@ohif/extension-cornerstone-dynamic-volume.panelModule.dynamic-volume',
};
const cornerstone = {
viewport: '@ohif/extension-cornerstone.viewportModule.cornerstone',
activeViewportWindowLevel: '@ohif/extension-cornerstone.panelModule.activeViewportWindowLevel',
};
function modeFactory({ modeConfiguration }) {
return {
id,
routeName: 'dynamic-volume',
displayName: 'Preclinical 4D',
onModeEnter: function ({ servicesManager, extensionManager, commandsManager }: withAppTypes) {
const {
measurementService,
toolbarService,
cineService,
cornerstoneViewportService,
toolGroupService,
customizationService,
viewportGridService,
} = servicesManager.services;
const utilityModule = extensionManager.getModuleEntry(
'@ohif/extension-cornerstone.utilityModule.tools'
);
const { toolNames, Enums } = utilityModule.exports;
measurementService.clearMeasurements();
initToolGroups({ toolNames, Enums, toolGroupService, commandsManager, servicesManager });
toolbarService.addButtons([...toolbarButtons, ...segmentationButtons]);
toolbarService.createButtonSection('secondary', ['ProgressDropdown']);
// the primary button section is created in the workflow steps
// specific to the step
customizationService.addModeCustomizations([
{
id: 'PanelSegmentation.tableMode',
mode: 'expanded',
},
{
id: 'PanelSegmentation.onSegmentationAdd',
onSegmentationAdd: () => {
commandsManager.run('createNewLabelMapForDynamicVolume');
},
},
{
id: 'PanelSegmentation.showAddSegment',
showAddSegment: false,
},
{
id: 'PanelSegmentation.readableText',
// remove following if you are not interested in that stats
readableText: {
lesionStats: 'Lesion Statistics',
minValue: 'Minimum Value',
maxValue: 'Maximum Value',
meanValue: 'Mean Value',
volume: 'Volume',
suvPeak: 'SUV Peak',
suvMax: 'Maximum SUV',
suvMaxIJK: 'SUV Max IJK',
},
},
]);
// Auto play the clip initially when the volumes are loaded
const { unsubscribe } = cornerstoneViewportService.subscribe(
cornerstoneViewportService.EVENTS.VIEWPORT_VOLUMES_CHANGED,
() => {
const viewportId = viewportGridService.getActiveViewportId();
const csViewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
cineService.playClip(csViewport.element, { viewportId });
// cineService.setIsCineEnabled(true);
unsubscribe();
}
);
},
onSetupRouteComplete: ({ servicesManager }: withAppTypes) => {
// This needs to run after hanging protocol matching process because
// it may change the protocol/stage based on workflow stage settings
initWorkflowSteps({ servicesManager });
},
onModeExit: ({ servicesManager }: withAppTypes) => {
const {
toolGroupService,
syncGroupService,
segmentationService,
cornerstoneViewportService,
} = servicesManager.services;
toolGroupService.destroy();
syncGroupService.destroy();
segmentationService.destroy();
cornerstoneViewportService.destroy();
},
get validationTags() {
return {
study: [],
series: [],
};
},
isValidMode: ({ modalities, study }) => {
// Todo: we need to find a better way to validate the mode
return {
valid: study.mrn === 'M1',
description: 'This mode is only available for 4D PET/CT studies.',
};
},
/**
* 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: 'preclinical-4d',
layoutTemplate: ({ location, servicesManager }) => {
return {
id: ohif.layout,
props: {
leftPanels: [[dynamicVolume.leftPanel, cornerstone.activeViewportWindowLevel]],
rightPanels: [],
rightPanelClosed: true,
viewports: [
{
namespace: cornerstone.viewport,
displaySetsToDisplay: [ohif.defaultSopClassHandler],
},
{
namespace: ohif.chartViewport,
displaySetsToDisplay: [ohif.chartSopClassHandler],
},
],
},
};
},
},
],
extensions: extensionDependencies,
// Default protocol gets self-registered by default in the init
hangingProtocol: 'default4D',
// Order is important in sop class handlers when two handlers both use
// the same sop class under different situations. In that case, the more
// general handler needs to come last. For this case, the dicomvideo must
// come first to remove video transfer syntax before ohif uses images
sopClassHandlers: [ohif.chartSopClassHandler, ohif.defaultSopClassHandler],
hotkeys: [...hotkeys.defaults.hotkeyBindings],
};
}
const mode = {
id,
modeFactory,
extensionDependencies,
};
export default mode;