import { getEnabledElement, utilities as csUtils, Types as CoreTypes, getRenderingEngines, } from '@cornerstonejs/core'; import { ToolGroupManager, Enums, utilities as cstUtils, annotation, Types as ToolTypes, SplineContourSegmentationTool, cancelActiveManipulations, } from '@cornerstonejs/tools'; import { SegmentInfo, LogicalOperation, OperatorOptions, } from '@cornerstonejs/tools/utilities/contourSegmentation/logicalOperators'; import * as cornerstoneTools from '@cornerstonejs/tools'; import * as labelmapInterpolation from '@cornerstonejs/labelmap-interpolation'; import { ONNXSegmentationController } from '@cornerstonejs/ai'; import { Types as OhifTypes, utils } from '@ohif/core'; import { callInputDialogAutoComplete, createReportAsync, colorPickerDialog, callInputDialog, } from '@ohif/extension-default'; import toggleImageSliceSync from './utils/imageSliceSync/toggleImageSliceSync'; // Sanctioned flag read: RTSTRUCT contour hydration pins the referenced image to // stack mode on the native ("next") path, a decision made before a target viewport exists. import { getHydrationViewportTypeForModality } from './utils/nextViewportPolicies'; import { getFirstAnnotationSelected } from './utils/measurementServiceMappings/utils/selection'; import { getViewportEnabledElement } from './utils/getViewportEnabledElement'; import getActiveViewportEnabledElement from './utils/getActiveViewportEnabledElement'; import toggleVOISliceSync from './utils/toggleVOISliceSync'; import { isStackViewportType, isOrthographicViewportType, isVolume3DViewportType, isVolumeViewportType, } from './utils/getLegacyViewportType'; import { viewportOperations as ops } from './services/ViewportService/backends/viewportOperations'; import { getViewportAdapter } from './services/ViewportService/adapter'; import { usePositionPresentationStore, useSegmentationPresentationStore, useSelectedSegmentationsForViewportStore, } from './stores'; import { toolNames } from './initCornerstoneTools'; import CornerstoneViewportDownloadForm from './utils/CornerstoneViewportDownloadForm'; import { updateSegmentBidirectionalStats } from './utils/updateSegmentationStats'; import { generateSegmentationCSVReport } from './utils/generateSegmentationCSVReport'; import { getUpdatedViewportsForSegmentation } from './utils/hydrationUtils'; import { SegmentationRepresentations } from '@cornerstonejs/tools/enums'; import { EasingFunctionEnum } from './utils/transitions'; import { createSegmentationForViewport } from './utils/createSegmentationForViewport'; import { utilities as segmentationUtilities } from '@cornerstonejs/tools/segmentation'; import i18n from '@ohif/i18n'; const { add, intersect, subtract, copy } = cstUtils.contourSegmentation; const { DefaultHistoryMemo } = csUtils.HistoryMemo; const toggleSyncFunctions = { imageSlice: toggleImageSliceSync, voi: toggleVOISliceSync, }; const { segmentation: segmentationUtils } = cstUtils; const getLabelmapTools = ({ toolGroupService }) => { const labelmapTools = []; const toolGroupIds = toolGroupService.getToolGroupIds(); toolGroupIds.forEach(toolGroupId => { const toolGroup = cornerstoneTools.ToolGroupManager.getToolGroup(toolGroupId); const tools = toolGroup.getToolInstances(); // tools is an object with toolName as the key and tool as the value Object.keys(tools).forEach(toolName => { const tool = tools[toolName]; if (tool instanceof cornerstoneTools.LabelmapBaseTool) { labelmapTools.push(tool); } }); }); return labelmapTools; }; const getPreviewTools = ({ toolGroupService }) => { const labelmapTools = getLabelmapTools({ toolGroupService }); const previewTools = labelmapTools.filter(tool => tool.acceptPreview || tool.rejectPreview); return previewTools; }; const segmentAI = new ONNXSegmentationController({ autoSegmentMode: true, models: { sam_b: [ { name: 'sam-b-encoder', url: 'https://huggingface.co/schmuell/sam-b-fp16/resolve/main/sam_vit_b_01ec64.encoder-fp16.onnx', size: 180, key: 'encoder', }, { name: 'sam-b-decoder', url: 'https://huggingface.co/schmuell/sam-b-fp16/resolve/main/sam_vit_b_01ec64.decoder.onnx', size: 17, key: 'decoder', }, ], }, modelName: 'sam_b', }); let segmentAIEnabled = false; function commandsModule({ servicesManager, commandsManager, extensionManager, }: OhifTypes.Extensions.ExtensionParams): OhifTypes.Extensions.CommandsModule { const { viewportGridService, toolbarService, toolGroupService, cineService, uiDialogService, cornerstoneViewportService, uiNotificationService, measurementService, customizationService, colorbarService, hangingProtocolService, syncGroupService, segmentationService, displaySetService, } = servicesManager.services as AppTypes.Services; function _getActiveViewportEnabledElement() { return getActiveViewportEnabledElement(viewportGridService); } function _getViewportEnabledElement(viewportId: string) { return getViewportEnabledElement(viewportId); } // Resolves the cornerstone viewport for a command: the given viewport id, else the // active one. Returns undefined when nothing is enabled. function _resolveViewport(viewportId?: string) { const enabledElement = viewportId ? _getViewportEnabledElement(viewportId) : _getActiveViewportEnabledElement(); return enabledElement?.viewport; } function _getActiveViewportToolGroupId() { const viewport = _getActiveViewportEnabledElement(); const toolGroup = viewport && toolGroupService.getToolGroupForViewport(viewport.id); return toolGroup?.id; } function _usesPrimaryActivation(bindings) { if (!bindings?.length) { return true; } return bindings.some( binding => binding.mouseButton === Enums.MouseBindings.Primary && binding.modifierKey == null && binding.numTouchPoints == null ); } function _getActiveSegmentationInfo() { const viewportId = viewportGridService.getActiveViewportId(); const activeSegmentation = segmentationService.getActiveSegmentation(viewportId); const segmentationId = activeSegmentation?.segmentationId; const activeSegmentIndex = segmentationService.getActiveSegment(viewportId).segmentIndex; return { segmentationId, segmentIndex: activeSegmentIndex, }; } function _handleBrushSizeAction(action: 'increase' | 'decrease') { const toolGroupIds = toolGroupService.getToolGroupIds(); if (!toolGroupIds?.length) { return; } for (const toolGroupId of toolGroupIds) { const brushSize = segmentationUtils.getBrushSizeForToolGroup(toolGroupId); const newBrushSize = action === 'increase' ? brushSize + 3 : brushSize - 3; if (brushSize) { segmentationUtils.setBrushSizeForToolGroup(toolGroupId, newBrushSize); toolbarService.refreshToolbarState({ toolGroupId }); } } } /** * Creates a command function that sets a style property for segmentation types. * If type is provided, sets the property for that type only. * If type is not provided, sets the property for both Labelmap and Contour types. * @param propertyName - The name of the style property to set * @returns A command function that takes { type, value } */ const createSetStyleCommand = (propertyName: string) => { return ({ type, value }) => { const { segmentationService } = servicesManager.services; if (type) { segmentationService.setStyle({ type }, { [propertyName]: value }); } else { segmentationService.setStyle( { type: SegmentationRepresentations.Labelmap }, { [propertyName]: value } ); segmentationService.setStyle( { type: SegmentationRepresentations.Contour }, { [propertyName]: value } ); } }; }; const actions = { jumpToMeasurementViewport: ({ annotationUID, measurement }) => { cornerstoneTools.annotation.selection.setAnnotationSelected(annotationUID, true); const { metadata } = measurement; const activeViewportId = viewportGridService.getActiveViewportId(); // Finds the best viewport to jump to for showing the annotation view reference // This may be different from active if there is a viewport already showing the display set. const viewportId = cornerstoneViewportService.findNavigationCompatibleViewportId( activeViewportId, metadata ); if (viewportId) { const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId); viewport.setViewReference(metadata); viewport.render(); // If the measurement is not visible inside the current viewport, move the // camera to it. The operations backend handles the lane: legacy re-centers // in-plane (getCamera/setCamera), native skips it (no in-plane pan yet, CS-14) // since setViewReference above already navigated to the measurement's slice. if (ops.centerOnMeasurement(viewport, measurement)) { viewport.render(); } return; } const { displaySetInstanceUID: referencedDisplaySetInstanceUID } = measurement; if (!referencedDisplaySetInstanceUID) { console.warn('ViewportGrid::No display set found in', measurement); return; } // Finds the viewport to update to show the given displayset/orientation. // This will choose a view already containing the measurement display set // if possible, otherwise will fallback to the active. const viewportToUpdate = cornerstoneViewportService.findUpdateableViewportConfiguration( activeViewportId, measurement ); if (!viewportToUpdate) { console.warn('Unable to find a viewport to show this in'); return; } const updatedViewports = hangingProtocolService.getViewportsRequireUpdate( viewportToUpdate.viewportId, referencedDisplaySetInstanceUID ); if (!updatedViewports?.[0]) { console.warn( 'ViewportGrid::Unable to navigate to viewport containing', referencedDisplaySetInstanceUID ); return; } updatedViewports[0].viewportOptions = viewportToUpdate.viewportOptions; // Update stored position presentation commandsManager.run('updateStoredPositionPresentation', { viewportId: viewportToUpdate.viewportId, displaySetInstanceUIDs: [referencedDisplaySetInstanceUID], referencedImageId: measurement.referencedImageId, options: { ...measurement.metadata, }, }); commandsManager.run('setDisplaySetsForViewports', { viewportsToUpdate: updatedViewports }); }, /** * Cancels any in-progress annotation manipulation (e.g. drawing a Spline, * Livewire or PlanarFreehand contour) on the active viewport. Reached on * Escape via the `cancelActiveOperation` command. `cancelActiveManipulations` * invokes the `cancel` method of each active/passive tool that has an * in-progress annotation, so it is a no-op when nothing is being drawn. */ cancelMeasurement: () => { const element = _getActiveViewportEnabledElement()?.viewport?.element; if (element) { cancelActiveManipulations(element); } }, hydrateSecondaryDisplaySet: async ({ displaySet, viewportId }) => { if (!displaySet) { return; } const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId); if (!viewport) { return; } if (displaySet.isOverlayDisplaySet) { // update the previously stored segmentationPresentation with the new viewportId // presentation so that when we put the referencedDisplaySet back in the viewport // it will have the correct segmentation representation hydrated const segmentationType = // Todo: check if PMAP modality should be handled such as SEG displaySet.Modality !== 'SEG' ? SegmentationRepresentations.Contour : isVolume3DViewportType(viewport) ? SegmentationRepresentations.Surface : SegmentationRepresentations.Labelmap; commandsManager.runCommand('updateStoredSegmentationPresentation', { displaySet, type: segmentationType, }); } const referencedDisplaySetInstanceUID = displaySet.referencedDisplaySetInstanceUID; const storePositionPresentation = refDisplaySet => { // update the previously stored positionPresentation with the new viewportId // presentation so that when we put the referencedDisplaySet back in the viewport // it will be in the correct position zoom and pan commandsManager.runCommand('updateStoredPositionPresentation', { viewportId, displaySetInstanceUIDs: [refDisplaySet.displaySetInstanceUID], }); }; if (displaySet.Modality === 'SEG' || displaySet.Modality === 'RTSTRUCT') { const referencedDisplaySet = displaySetService.getDisplaySetByUID( referencedDisplaySetInstanceUID ); storePositionPresentation(referencedDisplaySet); const results = commandsManager.runCommand('loadSegmentationDisplaySetsForViewport', { viewportId, displaySetInstanceUIDs: [referencedDisplaySet.displaySetInstanceUID], // RTSTRUCT-on-next pins the referenced image to stack mode on hydrate; // see the policy's rationale in utils/nextViewportPolicies. viewportType: getHydrationViewportTypeForModality(displaySet.Modality), }); const disableEditing = customizationService.getCustomization( 'panelSegmentation.disableEditing' ); if (disableEditing) { const segmentationRepresentations = segmentationService.getSegmentationRepresentations( viewportId, { segmentationId: displaySet.displaySetInstanceUID, } ); segmentationRepresentations.forEach(representation => { const segmentIndices = Object.keys(representation.segments); segmentIndices.forEach(segmentIndex => { segmentationService.setSegmentLocked( representation.segmentationId, parseInt(segmentIndex), true ); }); }); } return results; } else if (displaySet.Modality === 'SR') { const results = commandsManager.runCommand('hydrateStructuredReport', { displaySetInstanceUID: displaySet.displaySetInstanceUID, }); const { SeriesInstanceUIDs } = results; const referencedDisplaySets = displaySetService.getDisplaySetsForSeries( SeriesInstanceUIDs[0] ); referencedDisplaySets.forEach(storePositionPresentation); if (referencedDisplaySets.length) { actions.setDisplaySetsForViewports({ viewportsToUpdate: [ { viewportId: viewportGridService.getActiveViewportId(), displaySetInstanceUIDs: [referencedDisplaySets[0].displaySetInstanceUID], }, ], }); } return results; } }, runSegmentBidirectional: async ({ segmentationId, segmentIndex } = {}) => { // Get active segmentation if not specified const targetSegmentation = segmentationId && segmentIndex ? { segmentationId, segmentIndex } : _getActiveSegmentationInfo(); const { segmentationId: targetId, segmentIndex: targetIndex } = targetSegmentation; // Check if the segment has voxels before computing bidirectional measurement const uniqueSegmentIndices = cstUtils.segmentation.getUniqueSegmentIndices(targetId); const hasVoxels = uniqueSegmentIndices.includes(targetIndex); if (!hasVoxels) { uiNotificationService.show({ title: i18n.t('SegmentationPanel:Segment Bidirectional'), message: i18n.t( 'SegmentationPanel:Draw a segment before using bidirectional measurement' ), type: 'warning', }); return; } // Get bidirectional measurement data const bidirectionalData = await cstUtils.segmentation.getSegmentLargestBidirectional({ segmentationId: targetId, segmentIndices: [targetIndex], }); if (!bidirectionalData.length) { return; } const activeViewportId = viewportGridService.getActiveViewportId(); // Process each bidirectional measurement bidirectionalData.forEach(measurement => { const { segmentIndex, majorAxis, minorAxis } = measurement; // Create annotation const annotation = cornerstoneTools.SegmentBidirectionalTool.hydrate( activeViewportId, [majorAxis, minorAxis], { segmentIndex, segmentationId: targetId, } ); measurement.annotationUID = annotation.annotationUID; // Update segmentation stats const updatedSegmentation = updateSegmentBidirectionalStats({ segmentationId: targetId, segmentIndex: targetIndex, bidirectionalData: measurement, segmentationService, annotation, }); // Save changes if needed if (updatedSegmentation) { segmentationService.addOrUpdateSegmentation({ segmentationId: targetId, segments: updatedSegmentation.segments, }); } }); // get the active segmentIndex bidirectional annotation and jump to it const activeBidirectional = bidirectionalData.find( measurement => measurement.segmentIndex === targetIndex ); commandsManager.run('jumpToMeasurement', { uid: activeBidirectional?.annotationUID, }); }, interpolateLabelmap: () => { const { segmentationId, segmentIndex } = _getActiveSegmentationInfo(); labelmapInterpolation.interpolate({ segmentationId, segmentIndex, }); }, /** * Generates the selector props for the context menu, specific to * the cornerstone viewport, and then runs the context menu. */ showCornerstoneContextMenu: options => { const element = _getActiveViewportEnabledElement()?.viewport?.element; const optionsToUse = { ...options, element }; const { useSelectedAnnotation, nearbyToolData, event } = optionsToUse; // This code is used to invoke the context menu via keyboard shortcuts if (useSelectedAnnotation && !nearbyToolData) { const firstAnnotationSelected = getFirstAnnotationSelected(element); // filter by allowed selected tools from config property (if there is any) const isToolAllowed = !optionsToUse.allowedSelectedTools || optionsToUse.allowedSelectedTools.includes(firstAnnotationSelected?.metadata?.toolName); if (isToolAllowed) { optionsToUse.nearbyToolData = firstAnnotationSelected; } else { return; } } optionsToUse.defaultPointsPosition = []; // if (optionsToUse.nearbyToolData) { // optionsToUse.defaultPointsPosition = commandsManager.runCommand( // 'getToolDataActiveCanvasPoints', // { toolData: optionsToUse.nearbyToolData } // ); // } // TODO - make the selectorProps richer by including the study metadata and display set. optionsToUse.selectorProps = { toolName: optionsToUse.nearbyToolData?.metadata?.toolName, value: optionsToUse.nearbyToolData, uid: optionsToUse.nearbyToolData?.annotationUID, nearbyToolData: optionsToUse.nearbyToolData, event, ...optionsToUse.selectorProps, }; commandsManager.run(options, optionsToUse); }, updateStoredSegmentationPresentation: ({ displaySet, type }) => { const { addSegmentationPresentationItem } = useSegmentationPresentationStore.getState(); const referencedDisplaySetInstanceUID = displaySet.referencedDisplaySetInstanceUID; addSegmentationPresentationItem(referencedDisplaySetInstanceUID, { segmentationId: displaySet.displaySetInstanceUID, hydrated: true, type, }); }, /** Stores the changed position presentation */ updateStoredPositionPresentation: ({ viewportId, displaySetInstanceUIDs, referencedImageId, options, }) => { const presentations = cornerstoneViewportService.getPresentations(viewportId); const { positionPresentationStore, setPositionPresentation, getPositionPresentationId } = usePositionPresentationStore.getState(); // Look inside positionPresentationStore and find the key that includes ALL the displaySetInstanceUIDs // and the value has viewportId as activeViewportId. let previousReferencedDisplaySetStoreKey; if ( displaySetInstanceUIDs && Array.isArray(displaySetInstanceUIDs) && displaySetInstanceUIDs.length > 0 ) { previousReferencedDisplaySetStoreKey = Object.entries(positionPresentationStore).find( ([key, value]) => { return ( displaySetInstanceUIDs.every(uid => key.includes(uid)) && value?.viewportId === viewportId ); } )?.[0]; } // Create presentation data with referencedImageId and options if provided const presentationData = referencedImageId || options?.FrameOfReferenceUID ? { ...presentations.positionPresentation, viewReference: { referencedImageId, ...options, }, } : presentations.positionPresentation; if (previousReferencedDisplaySetStoreKey) { setPositionPresentation(previousReferencedDisplaySetStoreKey, presentationData); return; } // if not found means we have not visited that referencedDisplaySetInstanceUID before // so we need to grab the positionPresentationId directly from the store, // Todo: this is really hacky, we should have a better way for this const positionPresentationId = getPositionPresentationId({ displaySetInstanceUIDs, viewportId, }); setPositionPresentation(positionPresentationId, presentationData); }, getNearbyToolData({ nearbyToolData, element, canvasCoordinates }) { return nearbyToolData ?? cstUtils.getAnnotationNearPoint(element, canvasCoordinates); }, getNearbyAnnotation({ element, canvasCoordinates }) { const nearbyToolData = actions.getNearbyToolData({ nearbyToolData: null, element, canvasCoordinates, }); const isAnnotation = toolName => { const enabledElement = getEnabledElement(element); if (!enabledElement) { return; } const { renderingEngineId, viewportId } = enabledElement; const toolGroup = ToolGroupManager.getToolGroupForViewport(viewportId, renderingEngineId); const toolInstance = toolGroup.getToolInstance(toolName); return toolInstance?.constructor?.isAnnotation ?? true; }; return nearbyToolData?.metadata?.toolName && isAnnotation(nearbyToolData.metadata.toolName) ? nearbyToolData : null; }, /** * Common logic for handling measurement label updates through dialog * @param uid - measurement uid * @returns Promise that resolves when the label is updated */ _handleMeasurementLabelDialog: async uid => { const labelConfig = customizationService.getCustomization('measurementLabels'); const renderContent = customizationService.getCustomization('ui.labellingComponent'); const measurement = measurementService.getMeasurement(uid); if (!measurement) { console.debug('No measurement found for label editing'); return; } if (!labelConfig) { const label = await callInputDialog({ uiDialogService, title: i18n.t('Tools:Edit Measurement Label'), placeholder: measurement.label || i18n.t('Tools:Enter new label'), defaultValue: measurement.label, }); if (label !== undefined && label !== null) { measurementService.update(uid, { ...measurement, label }, true); } return; } const val = await callInputDialogAutoComplete({ measurement, uiDialogService, labelConfig, renderContent, }); if (val !== undefined && val !== null) { measurementService.update(uid, { ...measurement, label: val }, true); } }, /** * Show the measurement labelling input dialog and update the label * on the measurement with a response if not cancelled. */ setMeasurementLabel: async ({ uid }) => { await actions._handleMeasurementLabelDialog(uid); }, renameMeasurement: async ({ uid }) => { await actions._handleMeasurementLabelDialog(uid); }, /** * * @param props - containing the updates to apply * @param props.measurementKey - chooses the measurement key to apply the * code to. This will typically be finding or site to apply a * finding code or a findingSites code. * @param props.code - A coding scheme value from DICOM, including: * * CodeValue - the language independent code, for example '1234' * * CodingSchemeDesignator - the issue of the code value * * CodeMeaning - the text value shown to the user * * ref - a string reference in the form `:` * * type - defaulting to 'finding'. Will replace other codes of same type * * style - a styling object to use * * Other fields * Note it is a valid option to remove the finding or site values by * supplying null for the code. * @param props.uid - the measurement UID to find it with * @param props.label - the text value for the code. Has NOTHING to do with * the measurement label, which can be set with textLabel * @param props.textLabel is the measurement label to apply. Set to null to * delete. * * If the measurementKey is `site`, then the code will also be added/replace * the 0 element of findingSites. This behaviour is expected to be enhanced * in the future with ability to set other site information. */ updateMeasurement: props => { const { code, uid, textLabel, label } = props; let { style } = props; const measurement = measurementService.getMeasurement(uid); if (!measurement) { console.warn('No measurement found to update', uid); return; } const updatedMeasurement = { ...measurement, }; // Call it textLabel as the label value // TODO - remove the label setting when direct rendering of findingSites is enabled if (textLabel !== undefined) { updatedMeasurement.label = textLabel; } if (code !== undefined) { const measurementKey = code.type || 'finding'; if (code.ref && !code.CodeValue) { const split = code.ref.indexOf(':'); code.CodeValue = code.ref.substring(split + 1); code.CodeMeaning = code.text || label; code.CodingSchemeDesignator = code.ref.substring(0, split); } updatedMeasurement[measurementKey] = code; if (measurementKey !== 'finding') { if (updatedMeasurement.findingSites) { updatedMeasurement.findingSites = updatedMeasurement.findingSites.filter( it => it.type !== measurementKey ); updatedMeasurement.findingSites.push(code); } else { updatedMeasurement.findingSites = [code]; } } } style ||= updatedMeasurement.finding?.style; style ||= updatedMeasurement.findingSites?.find(site => site?.style)?.style; if (style) { // Reset the selected values to preserve appearance on selection style.lineDashSelected ||= style.lineDash; annotation.config.style.setAnnotationStyles(measurement.uid, style); // this is a bit ugly, but given the underlying behavior, this is how it needs to work. switch (measurement.toolName) { case toolNames.PlanarFreehandROI: { const targetAnnotation = annotation.state.getAnnotation(measurement.uid); targetAnnotation.data.isOpenUShapeContour = !!style.isOpenUShapeContour; break; } default: break; } } measurementService.update(updatedMeasurement.uid, updatedMeasurement, true); }, /** * Jumps to the specified (by uid) measurement in the active viewport. * Also marks any provided display measurements isActive value */ jumpToMeasurement: ({ uid, displayMeasurements = [] }) => { if (!uid) { return; } measurementService.jumpToMeasurement(viewportGridService.getActiveViewportId(), uid); for (const measurement of displayMeasurements) { measurement.isActive = measurement.uid === uid; } }, removeMeasurement: ({ uid }) => { if (Array.isArray(uid)) { measurementService.removeMany(uid); } else { measurementService.remove(uid); } }, toggleLockMeasurement: ({ uid }) => { measurementService.toggleLockMeasurement(uid); }, toggleVisibilityMeasurement: ({ uid, items, visibility }) => { if (visibility === undefined && items?.length) { visibility = !items[0].isVisible; } if (Array.isArray(uid)) { measurementService.toggleVisibilityMeasurementMany(uid, visibility); } else { measurementService.toggleVisibilityMeasurement(uid, visibility); } }, /** * Download the CSV report for the measurements. */ downloadCSVMeasurementsReport: ({ measurementFilter }) => { utils.downloadCSVReport(measurementService.getMeasurements(measurementFilter)); }, downloadCSVSegmentationReport: ({ segmentationId }) => { const segmentation = segmentationService.getSegmentation(segmentationId); const { representationData } = segmentation; const { Labelmap } = representationData; const { referencedImageIds } = Labelmap; const firstImageId = referencedImageIds[0]; // find displaySet for firstImageId const displaySet = displaySetService .getActiveDisplaySets() .find(ds => ds.imageIds?.some(i => i === firstImageId)); const { SeriesNumber, SeriesInstanceUID, StudyInstanceUID, SeriesDate, SeriesTime, SeriesDescription, } = displaySet; const additionalInfo = { reference: { SeriesNumber, SeriesInstanceUID, StudyInstanceUID, SeriesDate, SeriesTime, SeriesDescription, }, }; generateSegmentationCSVReport(segmentation, additionalInfo); }, // Retrieve value commands getActiveViewportEnabledElement: _getActiveViewportEnabledElement, setViewportActive: ({ viewportId }) => { const viewportInfo = cornerstoneViewportService.getViewportInfo(viewportId); if (!viewportInfo) { console.warn('No viewport found for viewportId:', viewportId); return; } viewportGridService.setActiveViewportId(viewportId); }, arrowTextCallback: async ({ callback, data }) => { const labelConfig = customizationService.getCustomization('measurementLabels'); const renderContent = customizationService.getCustomization('ui.labellingComponent'); if (!labelConfig) { const label = await callInputDialog({ uiDialogService, title: i18n.t('Tools:Edit Arrow Text'), placeholder: data?.data?.label || i18n.t('Tools:Enter new text'), defaultValue: data?.data?.label || '', }); callback?.(label); return; } const value = await callInputDialogAutoComplete({ uiDialogService, labelConfig, renderContent, }); callback?.(value); }, toggleCine: () => { const { viewports } = viewportGridService.getState(); const { isCineEnabled } = cineService.getState(); cineService.setIsCineEnabled(!isCineEnabled); viewports.forEach((_, index) => cineService.setCine({ id: index, isPlaying: false })); }, setViewportWindowLevel({ viewportId, windowWidth, windowCenter, displaySetInstanceUID, }: { viewportId: string; windowWidth: number; windowCenter: number; displaySetInstanceUID?: string; }) { // convert to numbers const windowWidthNum = Number(windowWidth); const windowCenterNum = Number(windowCenter); const renderingEngine = cornerstoneViewportService.getRenderingEngine(); const viewport = renderingEngine.getViewport(viewportId); // Stale/invalid viewport ids resolve to undefined; bail out before the VOI // apply + render below would throw. if (!viewport) { return; } // Legacy volume viewports target a specific volume; the command owns that // resolution (it needs the service). The operations backend applies the VOI // (legacy setProperties vs native setDisplaySetPresentation on the active binding). const volumeId = isVolumeViewportType(viewport) ? actions.getVolumeIdForDisplaySet({ viewportId, displaySetInstanceUID }) : undefined; ops.setWindowLevel(viewport, { windowWidth: windowWidthNum, windowCenter: windowCenterNum, volumeId, displaySetInstanceUID, }); viewport.render(); }, toggleViewportColorbar: ({ viewportId, displaySetInstanceUIDs, options = {} }) => { const hasColorbar = colorbarService.hasColorbar(viewportId); if (hasColorbar) { colorbarService.removeColorbar(viewportId); return; } colorbarService.addColorbar(viewportId, displaySetInstanceUIDs, options); }, setWindowLevel(props) { const { toolGroupId } = props; const { viewportId } = _getActiveViewportEnabledElement(); const viewportToolGroupId = toolGroupService.getToolGroupForViewport(viewportId); if (toolGroupId && toolGroupId !== viewportToolGroupId) { return; } actions.setViewportWindowLevel({ ...props, viewportId }); }, setWindowLevelPreset: ({ presetName, presetIndex }) => { const windowLevelPresets = customizationService.getCustomization( 'cornerstone.windowLevelPresets' ); const activeViewport = viewportGridService.getActiveViewportId(); const viewport = cornerstoneViewportService.getCornerstoneViewport(activeViewport); const metadata = viewport.getImageData().metadata; const modality = metadata.Modality; if (!modality) { return; } const windowLevelPresetForModality = windowLevelPresets[modality]; if (!windowLevelPresetForModality) { return; } const windowLevelPreset = windowLevelPresetForModality[presetName] ?? Object.values(windowLevelPresetForModality)[presetIndex]; actions.setViewportWindowLevel({ viewportId: activeViewport, windowWidth: windowLevelPreset.window, windowCenter: windowLevelPreset.level, }); }, getVolumeIdForDisplaySet: ({ viewportId, displaySetInstanceUID }) => { const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId); // `instanceof BaseVolumeViewport` is false for GenericViewport compat adapters, // so use the capability guard instead (cornerstone 5.x generic-viewport guide). // A stack-mode adapter passes the guard but getAllVolumeIds() returns [], so the // result is the same null as before on stacks. if (csUtils.viewportSupportsVolumeId(viewport)) { const volumeIds = (viewport as CoreTypes.IVolumeViewport).getAllVolumeIds(); const volumeId = volumeIds.find(id => id.includes(displaySetInstanceUID)); return volumeId; } return null; }, setToolEnabled: ({ toolName, toggle, toolGroupId }) => { const { viewports } = viewportGridService.getState(); if (!viewports.size) { return; } const toolGroup = toolGroupService.getToolGroup(toolGroupId ?? null); if (!toolGroup || !toolGroup.hasTool(toolName)) { return; } const toolIsEnabled = toolGroup.getToolOptions(toolName).mode === Enums.ToolModes.Enabled; // Toggle the tool's state only if the toggle is true if (toggle) { toolIsEnabled ? toolGroup.setToolDisabled(toolName) : toolGroup.setToolEnabled(toolName); } else { toolGroup.setToolEnabled(toolName); } const renderingEngine = cornerstoneViewportService.getRenderingEngine(); renderingEngine.render(); }, toggleEnabledDisabledToolbar({ value, itemId, toolGroupId }) { const toolName = itemId || value; toolGroupId = toolGroupId ?? _getActiveViewportToolGroupId(); const toolGroup = toolGroupService.getToolGroup(toolGroupId); if (!toolGroup || !toolGroup.hasTool(toolName)) { return; } const toolIsEnabled = toolGroup.getToolOptions(toolName).mode === Enums.ToolModes.Enabled; toolIsEnabled ? toolGroup.setToolDisabled(toolName) : toolGroup.setToolEnabled(toolName); }, toggleActiveDisabledToolbar({ value, itemId, toolGroupId, toolGroupIds }) { const toolName = itemId || value; const resolvedToolGroupIds = toolGroupIds?.length ? toolGroupIds : [toolGroupId ?? _getActiveViewportToolGroupId()]; resolvedToolGroupIds.forEach(toolGroupId => { const toolGroup = toolGroupService.getToolGroup(toolGroupId); if (!toolGroup || !toolGroup.hasTool(toolName)) { return; } const toolIsActive = [ Enums.ToolModes.Active, Enums.ToolModes.Enabled, Enums.ToolModes.Passive, ].includes(toolGroup.getToolOptions(toolName).mode); if (toolIsActive) { toolGroup.setToolDisabled(toolName); const bindings = toolGroupService.getToolBindings(toolGroupId, toolName); if (_usesPrimaryActivation(bindings)) { // we should set the previously active tool to active after we set the // current tool disabled const prevToolName = toolGroup.getPrevActivePrimaryToolName(); if (prevToolName !== toolName) { actions.setToolActive({ toolName: prevToolName, toolGroupId }); } } return; } const bindings = toolGroupService.getToolBindings(toolGroupId, toolName); if (_usesPrimaryActivation(bindings)) { actions.setToolActive({ toolName, toolGroupId, bindings }); } else { toolGroup.setToolActive(toolName, { bindings }); } }); }, setToolActiveToolbar: ({ value, itemId, toolName, toolGroupIds = [], bindings }) => { // Sometimes it is passed as value (tools with options), sometimes as itemId (toolbar buttons) toolName = toolName || itemId || value; toolGroupIds = toolGroupIds.length ? toolGroupIds : toolGroupService.getToolGroupIds(); toolGroupIds.forEach(toolGroupId => { actions.setToolActive({ toolName, toolGroupId, bindings }); }); }, setToolActive: ({ toolName, toolGroupId = null, bindings = [{ mouseButton: Enums.MouseBindings.Primary }], }) => { const { viewports } = viewportGridService.getState(); if (!viewports.size) { return; } const toolGroup = toolGroupService.getToolGroup(toolGroupId); if (!toolGroup) { return; } if (!toolGroup?.hasTool(toolName)) { return; } const activeToolName = toolGroup.getActivePrimaryMouseButtonTool(); if (activeToolName) { const activeToolOptions = toolGroup.getToolConfiguration(activeToolName); activeToolOptions?.disableOnPassive ? toolGroup.setToolDisabled(activeToolName) : toolGroup.setToolPassive(activeToolName); } // Set the new toolName to be active toolGroup.setToolActive(toolName, { bindings, }); }, // capture viewport showDownloadViewportModal: () => { const { activeViewportId } = viewportGridService.getState(); if (!cornerstoneViewportService.getCornerstoneViewport(activeViewportId)) { // Cannot download a non-cornerstone viewport (image). uiNotificationService.show({ title: i18n.t('Tools:Download Image'), message: i18n.t('Tools:Image cannot be downloaded'), type: 'error', }); return; } const { uiModalService } = servicesManager.services; if (uiModalService) { uiModalService.show({ content: CornerstoneViewportDownloadForm, title: i18n.t('Tools:Download High Quality Image'), contentProps: { activeViewportId, cornerstoneViewportService, }, containerClassName: 'max-w-4xl p-4', }); } }, /** * Rotates the viewport by `rotation` relative to its current rotation. */ rotateViewportBy: ({ rotation, viewportId }: { rotation: number; viewportId?: string }) => { actions._rotateViewport({ rotation, viewportId, rotationMode: 'apply' }); }, /** * Sets the viewport rotation to an absolute value `rotation`. */ setViewportRotation: ({ rotation, viewportId }: { rotation: number; viewportId?: string }) => { actions._rotateViewport({ rotation, viewportId, rotationMode: 'set' }); }, flipViewportHorizontal: ({ viewportId, newValue = 'toggle', }: { viewportId?: string; newValue?: 'toggle' | boolean; }) => { const viewport = _resolveViewport(viewportId); if (!viewport) { return; } ops.flipHorizontal(viewport, newValue); viewport.render(); }, flipViewportVertical: ({ viewportId, newValue = 'toggle', }: { viewportId?: string; newValue?: 'toggle' | boolean; }) => { const viewport = _resolveViewport(viewportId); if (!viewport) { return; } ops.flipVertical(viewport, newValue); viewport.render(); }, invertViewport: ({ element }) => { const viewport = element === undefined ? _resolveViewport() : element.viewport; if (!viewport) { return; } ops.invert(viewport); viewport.render(); }, resetViewport: () => { const viewport = _resolveViewport(); if (!viewport) { return; } ops.reset(viewport); viewport.render(); }, scaleViewport: ({ direction }) => { const viewport = _resolveViewport(); if (!viewport) { return; } ops.scaleBy(viewport, direction); viewport.render(); }, /** Jumps the active viewport or the specified one to the given slice index */ jumpToImage: ({ imageIndex, viewport: gridViewport }): void => { // Get current active viewport (return if none active) let viewport; if (!gridViewport) { const enabledElement = _getActiveViewportEnabledElement(); if (!enabledElement) { return; } viewport = enabledElement.viewport; } else { viewport = cornerstoneViewportService.getCornerstoneViewport(gridViewport.id); } // Get number of slices // -> Copied from cornerstone3D jumpToSlice\_getImageSliceData() let numberOfSlices = 0; if (isStackViewportType(viewport)) { numberOfSlices = viewport.getImageIds().length; } else if (isOrthographicViewportType(viewport)) { numberOfSlices = csUtils.getImageSliceDataForVolumeViewport(viewport).numberOfSlices; } else { throw new Error('Unsupported viewport type'); } const jumpIndex = imageIndex < 0 ? numberOfSlices + imageIndex : imageIndex; if (jumpIndex >= numberOfSlices || jumpIndex < 0) { throw new Error(`Can't jump to ${imageIndex}`); } // Set slice to last slice const options = { imageIndex: jumpIndex }; csUtils.jumpToSlice(viewport.element, options); }, scroll: (options: ToolTypes.ScrollOptions) => { const enabledElement = _getActiveViewportEnabledElement(); // Allow either or direction for consistency in scroll implementation options.delta ??= options.direction || 1; options.direction ??= options.delta; if (!enabledElement) { return; } const { viewport } = enabledElement; csUtils.scroll(viewport, options); }, setViewportColormap: ({ viewportId, displaySetInstanceUID, colormap, opacity = 1, immediate = false, }) => { const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId); let hpOpacity; // Retrieve active protocol's viewport match details const { viewportMatchDetails } = hangingProtocolService.getActiveProtocol(); // Get display set options for the specified viewport ID const displaySetsInfo = viewportMatchDetails.get(viewportId)?.displaySetsInfo; if (displaySetsInfo) { // Find the display set that matches the given UID const matchingDisplaySet = displaySetsInfo.find( displaySet => displaySet.displaySetInstanceUID === displaySetInstanceUID ); // If a matching display set is found, update the opacity with its value hpOpacity = matchingDisplaySet?.displaySetOptions?.options?.colormap?.opacity; } // HP takes priority over the default opacity colormap = { ...colormap, opacity: hpOpacity || opacity }; // The legacy orthographic branch resolves the volumeId from the display set; // fall back to the viewport's first display set (needs viewportGridService, so // it is resolved here in the command rather than in the operations backend). if (isOrthographicViewportType(viewport) && !displaySetInstanceUID) { const { viewports } = viewportGridService.getState(); displaySetInstanceUID = viewports.get(viewportId)?.displaySetInstanceUIDs[0]; } ops.setColormap(viewport, { colormap, displaySetInstanceUID }); if (immediate) { viewport.render(); } }, changeActiveViewport: ({ direction = 1 }) => { const { activeViewportId, viewports } = viewportGridService.getState(); const viewportIds = Array.from(viewports.keys()); const currentIndex = viewportIds.indexOf(activeViewportId); const nextViewportIndex = (currentIndex + direction + viewportIds.length) % viewportIds.length; viewportGridService.setActiveViewportId(viewportIds[nextViewportIndex] as string); }, /** * If the syncId is given and a synchronizer with that ID already exists, it will * toggle it on/off for the provided viewports. If not, it will attempt to create * a new synchronizer using the given syncId and type for the specified viewports. * If no viewports are provided, you may notice some default behavior. * - 'voi' type, we will aim to synchronize all viewports with the same modality * -'imageSlice' type, we will aim to synchronize all viewports with the same orientation. * * @param options * @param options.viewports - The viewports to synchronize * @param options.syncId - The synchronization group ID * @param options.type - The type of synchronization to perform */ toggleSynchronizer: ({ type, viewports, syncId }) => { const synchronizer = syncGroupService.getSynchronizer(syncId); if (synchronizer) { synchronizer.isDisabled() ? synchronizer.setEnabled(true) : synchronizer.setEnabled(false); return; } const fn = toggleSyncFunctions[type]; if (fn) { fn({ servicesManager, viewports, syncId, }); } }, setViewportForToolConfiguration: ({ viewportId, toolName }) => { if (!viewportId) { const { activeViewportId } = viewportGridService.getState(); viewportId = activeViewportId ?? 'default'; } const toolGroup = toolGroupService.getToolGroupForViewport(viewportId); if (!toolGroup?.hasTool(toolName)) { return; } const prevConfig = toolGroup?.getToolConfiguration(toolName); toolGroup?.setToolConfiguration( toolName, { ...prevConfig, sourceViewportId: viewportId, }, true // overwrite ); const renderingEngine = cornerstoneViewportService.getRenderingEngine(); renderingEngine.render(); }, storePresentation: ({ viewportId }) => { cornerstoneViewportService.storePresentation({ viewportId }); }, updateVolumeData: ({ volume }) => { // update vtkOpenGLTexture and imageData of computed volume const { imageData, vtkOpenGLTexture } = volume; const numSlices = imageData.getDimensions()[2]; const slicesToUpdate = [...Array(numSlices).keys()]; slicesToUpdate.forEach(i => { vtkOpenGLTexture.setUpdatedFrame(i); }); imageData.modified(); }, attachProtocolViewportDataListener: ({ protocol, stageIndex }) => { const EVENT = cornerstoneViewportService.EVENTS.VIEWPORT_DATA_CHANGED; const command = protocol.callbacks.onViewportDataInitialized; const numPanes = protocol.stages?.[stageIndex]?.viewports.length ?? 1; let numPanesWithData = 0; const { unsubscribe } = cornerstoneViewportService.subscribe(EVENT, evt => { numPanesWithData++; if (numPanesWithData === numPanes) { commandsManager.run(...command); // Unsubscribe from the event unsubscribe(EVENT); } }); }, setViewportPreset: ({ viewportId, preset }) => { const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId); if (!viewport) { return; } ops.setPreset(viewport, preset); viewport.render(); }, /** * Sets the volume quality for a given viewport. * @param {string} viewportId - The ID of the viewport to set the volume quality. * @param {number} volumeQuality - The desired quality level of the volume rendering. */ setVolumeRenderingQulaity: ({ viewportId, volumeQuality }) => { const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId); if (!viewport) { return; } ops.setVolumeRenderingQuality(viewport, volumeQuality); viewport.render(); }, /** * Shifts opacity points for a given viewport id. * @param {string} viewportId - The ID of the viewport to set the mapping range. * @param {number} shift - The shift value to shift the points by. */ shiftVolumeOpacityPoints: ({ viewportId, shift }) => { const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId); if (!viewport) { return; } ops.shiftVolumeOpacityPoints(viewport, shift); viewport.render(); }, /** * Sets the volume lighting settings for a given viewport. * @param {string} viewportId - The ID of the viewport to set the lighting settings. * @param {Object} options - The lighting settings to be set. * @param {boolean} options.shade - The shade setting for the lighting. * @param {number} options.ambient - The ambient setting for the lighting. * @param {number} options.diffuse - The diffuse setting for the lighting. * @param {number} options.specular - The specular setting for the lighting. **/ setVolumeLighting: ({ viewportId, options }) => { const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId); if (!viewport) { return; } ops.setVolumeLighting(viewport, options); viewport.render(); }, resetCrosshairs: ({ viewportId }) => { const crosshairInstances = []; const getCrosshairInstances = toolGroupId => { const toolGroup = toolGroupService.getToolGroup(toolGroupId); // Only fetch the instance when Crosshairs is registered in this tool // group. getToolInstance logs a warning for an unregistered tool, and a // viewport's default tool group does not always include Crosshairs (e.g. // next viewports), which made Reset Viewport log a spurious warning. if (toolGroup?.hasTool('Crosshairs')) { crosshairInstances.push(toolGroup.getToolInstance('Crosshairs')); } }; if (!viewportId) { const toolGroupIds = toolGroupService.getToolGroupIds(); toolGroupIds.forEach(getCrosshairInstances); } else { const toolGroup = toolGroupService.getToolGroupForViewport(viewportId); if (toolGroup) { getCrosshairInstances(toolGroup.id); } } crosshairInstances.forEach(ins => { ins?.computeToolCenter(); }); }, /** * Creates a labelmap for the active viewport * * The created labelmap will be registered as a display set and also added * as a segmentation representation to the viewport. */ createLabelmapForViewport: async ({ viewportId, options = {} }) => { return createSegmentationForViewport(servicesManager, { viewportId, options, segmentationType: SegmentationRepresentations.Labelmap, }); }, /** * Creates a contour for the active viewport * * The created contour will be registered as a display set and also added * as a segmentation representation to the viewport. */ createContourForViewport: async ({ viewportId, options = {} }) => { return createSegmentationForViewport(servicesManager, { viewportId, options, segmentationType: SegmentationRepresentations.Contour, }); }, /** * Sets the active segmentation for a viewport * @param props.segmentationId - The ID of the segmentation to set as active */ setActiveSegmentation: ({ segmentationId }) => { const { viewportGridService, segmentationService } = servicesManager.services; segmentationService.setActiveSegmentation( viewportGridService.getActiveViewportId(), segmentationId ); }, /** * Adds a new segment to a segmentation * @param props.segmentationId - The ID of the segmentation to add the segment to */ addSegmentCommand: ({ segmentationId, config }) => { const { segmentationService } = servicesManager.services; segmentationService.addSegment(segmentationId, config); }, /** * Sets the active segment and jumps to its center * @param props.segmentationId - The ID of the segmentation * @param props.segmentIndex - The index of the segment to activate */ setActiveSegmentAndCenterCommand: ({ segmentationId, segmentIndex }) => { const { segmentationService, viewportGridService } = servicesManager.services; // set both active segmentation and active segment segmentationService.setActiveSegmentation( viewportGridService.getActiveViewportId(), segmentationId ); segmentationService.setActiveSegment(segmentationId, segmentIndex); const { highlightAlpha, highlightSegment, animationLength, animationFunctionType } = (customizationService.getCustomization( 'panelSegmentation.jumpToSegmentHighlightAnimationConfig' ) as Object as { highlightAlpha?: number; highlightSegment?: boolean; animationLength?: number; animationFunctionType?: EasingFunctionEnum; }) ?? {}; const validAnimationFunctionType = Object.values(EasingFunctionEnum).includes( animationFunctionType ) ? animationFunctionType : undefined; segmentationService.jumpToSegmentNext( segmentationId, segmentIndex, undefined, highlightAlpha, highlightSegment, animationLength, undefined, validAnimationFunctionType ); }, /** * Toggles the visibility of a segment * @param props.segmentationId - The ID of the segmentation * @param props.segmentIndex - The index of the segment * @param props.type - The type of visibility to toggle */ toggleSegmentVisibilityCommand: ({ segmentationId, segmentIndex, type }) => { const { segmentationService, viewportGridService } = servicesManager.services; segmentationService.toggleSegmentVisibility( viewportGridService.getActiveViewportId(), segmentationId, segmentIndex, type ); }, /** * Toggles the lock state of a segment * @param props.segmentationId - The ID of the segmentation * @param props.segmentIndex - The index of the segment */ toggleSegmentLockCommand: ({ segmentationId, segmentIndex }) => { const { segmentationService } = servicesManager.services; segmentationService.toggleSegmentLocked(segmentationId, segmentIndex); }, /** * Toggles the visibility of a segmentation representation * @param props.segmentationId - The ID of the segmentation * @param props.type - The type of representation */ toggleSegmentationVisibilityCommand: ({ segmentationId, type }) => { const { segmentationService, viewportGridService } = servicesManager.services; segmentationService.toggleSegmentationRepresentationVisibility( viewportGridService.getActiveViewportId(), { segmentationId, type } ); }, /** * Downloads a segmentation * @param props.segmentationId - The ID of the segmentation to download */ downloadSegmentationCommand: ({ segmentationId }) => { const { segmentationService } = servicesManager.services; segmentationService.downloadSegmentation(segmentationId); }, /** * Stores a segmentation and shows it in the viewport * @param props.segmentationId - The ID of the segmentation to store */ storeSegmentationCommand: async args => { const { segmentationId } = args; const { segmentationService, viewportGridService } = servicesManager.services; const displaySetInstanceUIDs = await createReportAsync({ servicesManager, getReport: () => commandsManager.runCommand('storeSegmentation', args), reportType: 'Segmentation', }); if (displaySetInstanceUIDs) { segmentationService.remove(segmentationId); viewportGridService.setDisplaySetsForViewport({ viewportId: viewportGridService.getActiveViewportId(), displaySetInstanceUIDs, }); } }, /** * Downloads a segmentation as RTSS * @param props.segmentationId - The ID of the segmentation */ downloadRTSSCommand: ({ segmentationId }) => { const { segmentationService } = servicesManager.services; segmentationService.downloadRTSS(segmentationId); }, /** * Sets the style for a segmentation * @param props.segmentationId - The ID of the segmentation * @param props.type - The type of style * @param props.key - The style key to set * @param props.value - The style value */ setSegmentationStyleCommand: ({ type, key, value }) => { const { segmentationService } = servicesManager.services; segmentationService.setStyle({ type }, { [key]: value }); }, /** * Deletes a segment from a segmentation * @param props.segmentationId - The ID of the segmentation * @param props.segmentIndex - The index of the segment to delete */ deleteSegmentCommand: ({ segmentationId, segmentIndex }) => { const { segmentationService } = servicesManager.services; segmentationService.removeSegment(segmentationId, segmentIndex); }, /** * Deletes an entire segmentation * @param props.segmentationId - The ID of the segmentation to delete */ deleteSegmentationCommand: ({ segmentationId }) => { const { segmentationService } = servicesManager.services; segmentationService.remove(segmentationId); }, /** * Removes a segmentation from the viewport * @param props.segmentationId - The ID of the segmentation to remove */ removeSegmentationFromViewportCommand: ({ segmentationId: displaySetInstanceUID }) => { const { viewportGridService } = servicesManager.services; const viewportId = viewportGridService.getActiveViewportId(); commandsManager.runCommand('removeDisplaySetLayer', { viewportId, displaySetInstanceUID, }); }, /** * Toggles rendering of inactive segmentations */ toggleRenderInactiveSegmentationsCommand: () => { const { segmentationService, viewportGridService } = servicesManager.services; const viewportId = viewportGridService.getActiveViewportId(); const renderInactive = segmentationService.getRenderInactiveSegmentations(viewportId); segmentationService.setRenderInactiveSegmentations(viewportId, !renderInactive); }, editSegmentLabel: async ({ segmentationId, segmentIndex }) => { const { segmentationService, uiDialogService } = servicesManager.services; const segmentation = segmentationService.getSegmentation(segmentationId); if (!segmentation) { return; } const segment = segmentation.segments[segmentIndex]; callInputDialog({ uiDialogService, title: i18n.t('Tools:Edit Segment Label'), placeholder: i18n.t('Tools:Enter new label'), defaultValue: segment.label, }).then(label => { segmentationService.setSegmentLabel(segmentationId, segmentIndex, label); }); }, editSegmentationLabel: ({ segmentationId }) => { const { segmentationService, uiDialogService } = servicesManager.services; const segmentation = segmentationService.getSegmentation(segmentationId); if (!segmentation) { return; } const { label } = segmentation; callInputDialog({ uiDialogService, title: i18n.t('Tools:Edit Segmentation Label'), placeholder: i18n.t('Tools:Enter new label'), defaultValue: label, }).then(label => { segmentationService.addOrUpdateSegmentation({ segmentationId, label }); }); }, editSegmentColor: ({ segmentationId, segmentIndex }) => { const { segmentationService, uiDialogService, viewportGridService } = servicesManager.services; const viewportId = viewportGridService.getActiveViewportId(); const color = segmentationService.getSegmentColor(viewportId, segmentationId, segmentIndex); const rgbaColor = { r: color[0], g: color[1], b: color[2], a: color[3] / 255.0, }; uiDialogService.show({ content: colorPickerDialog, title: i18n.t('Tools:Segment Color'), contentProps: { value: rgbaColor, onSave: newRgbaColor => { const color = [newRgbaColor.r, newRgbaColor.g, newRgbaColor.b, newRgbaColor.a * 255.0]; segmentationService.setSegmentColor(viewportId, segmentationId, segmentIndex, color); }, }, }); }, getRenderInactiveSegmentations: () => { const { segmentationService, viewportGridService } = servicesManager.services; return segmentationService.getRenderInactiveSegmentations( viewportGridService.getActiveViewportId() ); }, deleteActiveAnnotation: () => { const activeAnnotationsUID = cornerstoneTools.annotation.selection.getAnnotationsSelected(); activeAnnotationsUID.forEach(activeAnnotationUID => { measurementService.remove(activeAnnotationUID); }); }, setDisplaySetsForViewports: ({ viewportsToUpdate }) => { const { cineService, viewportGridService } = servicesManager.services; // Stopping the cine of modified viewports before changing the viewports to // avoid inconsistent state and lost references viewportsToUpdate.forEach(viewport => { const state = cineService.getState(); const currentCineState = state.cines?.[viewport.viewportId]; cineService.setCine({ id: viewport.viewportId, frameRate: currentCineState?.frameRate ?? state.default?.frameRate ?? 24, isPlaying: false, }); }); viewportGridService.setDisplaySetsForViewports(viewportsToUpdate); }, undo: () => { DefaultHistoryMemo.undo(); }, redo: () => { DefaultHistoryMemo.redo(); }, toggleSegmentPreviewEdit: ({ toggle }) => { let labelmapTools = getLabelmapTools({ toolGroupService }); labelmapTools = labelmapTools.filter(tool => !tool.toolName.includes('Eraser')); labelmapTools.forEach(tool => { tool.configuration = { ...tool.configuration, preview: { ...tool.configuration.preview, enabled: toggle, }, }; }); }, toggleSegmentSelect: ({ toggle }) => { const toolGroupIds = toolGroupService.getToolGroupIds(); toolGroupIds.forEach(toolGroupId => { const toolGroup = cornerstoneTools.ToolGroupManager.getToolGroup(toolGroupId); if (toggle) { toolGroup.setToolActive(cornerstoneTools.SegmentSelectTool.toolName); } else { toolGroup.setToolDisabled(cornerstoneTools.SegmentSelectTool.toolName); } }); }, toggleSegmentLabel: ({ enabled }: { enabled?: boolean }) => { const toolName = cornerstoneTools.SegmentLabelTool.toolName; const toolGroupIds = toolGroupService.getToolGroupIds(); const isToolOn = toolGroupIds.some(toolGroupId => { const toolGroup = cornerstoneTools.ToolGroupManager.getToolGroup(toolGroupId); const mode = toolGroup.getToolInstance(toolName)?.mode; return mode === 'Active'; }); const enableTool = enabled !== undefined ? enabled : !isToolOn; toolGroupIds.forEach(toolGroupId => { const toolGroup = cornerstoneTools.ToolGroupManager.getToolGroup(toolGroupId); if (enableTool) { toolGroup.setToolActive(toolName); } else { toolGroup.setToolDisabled(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 { customizationService } = servicesManager.services; const segmentLabelConfig = customizationService.getCustomization( 'segmentation.segmentLabel' ) as { enabledByDefault?: boolean; labelColor?: number[]; hoverTimeout?: number; background?: string; }; 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')); labelmapTools.forEach(tool => { tool.configuration = { ...tool.configuration, useCenterSegmentIndex: toggle, }; }); }, _handlePreviewAction: action => { const { viewport } = _getActiveViewportEnabledElement(); const previewTools = getPreviewTools({ toolGroupService }); previewTools.forEach(tool => { try { tool[`${action}Preview`](); } catch (error) { console.debug('Error accepting preview for tool', tool.toolName); } }); if (segmentAI.enabled) { segmentAI[`${action}Preview`](viewport.element); } }, acceptPreview: () => { actions._handlePreviewAction('accept'); }, rejectPreview: () => { actions._handlePreviewAction('reject'); }, /** * Generic Escape handler. A single Escape press should discard whatever the * user has in progress, but that can be one of two unrelated things: a * provisional segmentation preview, or an annotation being drawn. Rather * than bind both `rejectPreview` and `cancelMeasurement` to `esc` (Mousetrap * keeps only one handler per key, so the second silently shadows the first), * this command orchestrates both single-purpose commands. Each is a no-op * when its state is not active, so running both is safe and order-independent. */ cancelActiveOperation: () => { try { actions.rejectPreview(); } catch (error) { console.debug('Error rejecting active preview', error); } finally { actions.cancelMeasurement(); } }, clearMarkersForMarkerLabelmap: () => { const { viewport } = _getActiveViewportEnabledElement(); const toolGroup = cornerstoneTools.ToolGroupManager.getToolGroupForViewport(viewport.id); const toolInstance = toolGroup.getToolInstance('MarkerLabelmap'); if (!toolInstance) { return; } toolInstance.clearMarkers(viewport); }, interpolateScrollForMarkerLabelmap: () => { const { viewport } = _getActiveViewportEnabledElement(); const toolGroup = cornerstoneTools.ToolGroupManager.getToolGroupForViewport(viewport.id); const toolInstance = toolGroup.getToolInstance('MarkerLabelmap'); if (!toolInstance) { return; } toolInstance.interpolateScroll(viewport, 1); }, toggleLabelmapAssist: async () => { const { viewport } = _getActiveViewportEnabledElement(); const newState = !segmentAI.enabled; segmentAI.enabled = newState; if (!segmentAIEnabled) { await segmentAI.initModel(); segmentAIEnabled = true; } // set the brush tool to active const toolGroupIds = toolGroupService.getToolGroupIds(); if (newState) { actions.setToolActiveToolbar({ toolName: 'CircularBrushForAutoSegmentAI', toolGroupIds: toolGroupIds, }); } else { toolGroupIds.forEach(toolGroupId => { const toolGroup = cornerstoneTools.ToolGroupManager.getToolGroup(toolGroupId); toolGroup.setToolPassive('CircularBrushForAutoSegmentAI'); }); } if (segmentAI.enabled) { segmentAI.initViewport(viewport); } }, setBrushSize: ({ value }) => { const brushSize = Number(value); toolGroupService.getToolGroupIds()?.forEach(toolGroupId => { segmentationUtils.setBrushSizeForToolGroup(toolGroupId, brushSize); }); }, setThresholdRange: ({ value, toolNames = [ 'ThresholdCircularBrush', 'ThresholdSphereBrush', 'ThresholdCircularBrushDynamic', 'ThresholdSphereBrushDynamic', ], }) => { const toolGroupIds = toolGroupService.getToolGroupIds(); if (!toolGroupIds?.length) { return; } for (const toolGroupId of toolGroupIds) { const toolGroup = toolGroupService.getToolGroup(toolGroupId); toolNames?.forEach(toolName => { toolGroup.setToolConfiguration(toolName, { threshold: { range: value, }, }); }); } }, increaseBrushSize: () => { _handleBrushSizeAction('increase'); }, decreaseBrushSize: () => { _handleBrushSizeAction('decrease'); }, addNewSegment: () => { const { segmentationService } = servicesManager.services; const { activeViewportId } = viewportGridService.getState(); const activeSegmentation = segmentationService.getActiveSegmentation(activeViewportId); if (!activeSegmentation) { return; } segmentationService.addSegment(activeSegmentation.segmentationId); }, loadSegmentationDisplaySetsForViewport: ({ viewportId, displaySetInstanceUIDs, viewportType, }) => { const updatedViewports = getUpdatedViewportsForSegmentation({ viewportId, servicesManager, displaySetInstanceUIDs, }); if (!updatedViewports?.length) { return; } updatedViewports.forEach(({ viewportId: csViewportId }) => { const csViewport = cornerstoneViewportService.getCornerstoneViewport(csViewportId); csViewport?.setNeedsRender?.(); }); actions.setDisplaySetsForViewports({ viewportsToUpdate: updatedViewports.map(viewport => ({ viewportId: viewport.viewportId, displaySetInstanceUIDs: viewport.displaySetInstanceUIDs, // When the caller pins a viewportType (RTSTRUCT contour hydration on a // native "next" viewport requests 'stack'), force it so the referenced // image stays in that render mode instead of resolving to a volume slice. ...(viewportType ? { viewportOptions: { ...viewport.viewportOptions, viewportType } } : {}), })), }); }, setViewportOrientation: ({ viewportId, orientation }) => { const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId); // Accept any viewport already rendering volume content (legacy ORTHOGRAPHIC // or a native viewport in volume mode) — both expose setOrientation(). if (!viewport || !getViewportAdapter(viewport).canReorientInPlace()) { console.warn('Orientation can only be set on volume viewports'); return; } // Get display sets for this viewport to verify at least one is reconstructable const displaySetUIDs = viewportGridService.getDisplaySetsUIDsForViewport(viewportId); const displaySets = displaySetUIDs.map(uid => displaySetService.getDisplaySetByUID(uid)); if (!displaySets.some(ds => ds.isReconstructable)) { console.warn('Cannot change orientation: No reconstructable display sets in viewport'); return; } viewport.setOrientation(orientation); viewport.render(); // update the orientation in the viewport info const viewportInfo = cornerstoneViewportService.getViewportInfo(viewportId); viewportInfo.setOrientation(orientation); }, /** * Toggles the horizontal flip state of the viewport. */ toggleViewportHorizontalFlip: ({ viewportId }: { viewportId?: string } = {}) => { actions.flipViewportHorizontal({ viewportId, newValue: 'toggle' }); }, /** * Explicitly sets the horizontal flip state of the viewport. */ setViewportHorizontalFlip: ({ flipped, viewportId, }: { flipped: boolean; viewportId?: string; }) => { actions.flipViewportHorizontal({ viewportId, newValue: flipped }); }, /** * Toggles the vertical flip state of the viewport. */ toggleViewportVerticalFlip: ({ viewportId }: { viewportId?: string } = {}) => { actions.flipViewportVertical({ viewportId, newValue: 'toggle' }); }, /** * Explicitly sets the vertical flip state of the viewport. */ setViewportVerticalFlip: ({ flipped, viewportId, }: { flipped: boolean; viewportId?: string; }) => { actions.flipViewportVertical({ viewportId, newValue: flipped }); }, /** * Internal helper to rotate or set absolute rotation for a viewport. */ _rotateViewport: ({ rotation, viewportId, rotationMode = 'apply', }: { rotation: number; viewportId?: string; rotationMode?: 'apply' | 'set'; }) => { const viewport = _resolveViewport(viewportId); if (!viewport) { return; } ops.rotate(viewport, rotation, rotationMode); viewport.render(); }, startRecordingForAnnotationGroup: () => { cornerstoneTools.AnnotationTool.startGroupRecording(); }, endRecordingForAnnotationGroup: () => { cornerstoneTools.AnnotationTool.endGroupRecording(); }, triggerCreateAnnotationMemo: ({ annotation, FrameOfReferenceUID, options, }: { annotation: ToolTypes.Annotation; FrameOfReferenceUID: string; options: { newAnnotation?: boolean; deleting?: boolean }; }): void => { const { newAnnotation, deleting } = options; const renderingEngines = getRenderingEngines(); const viewports = renderingEngines.flatMap(re => re.getViewports()); const validViewport = viewports.find( vp => vp.getFrameOfReferenceUID() === FrameOfReferenceUID ); if (!validViewport) { return; } cornerstoneTools.AnnotationTool.createAnnotationMemo(validViewport.element, annotation, { newAnnotation, deleting, }); }, activateSelectedSegmentationOfType: ({ segmentationRepresentationType }) => { const { segmentationService, viewportGridService } = servicesManager.services; const activeViewportId = viewportGridService.getActiveViewportId(); const { selectedSegmentationsForViewport } = useSelectedSegmentationsForViewportStore.getState(); const segmentationId = selectedSegmentationsForViewport[activeViewportId]?.get( segmentationRepresentationType ); if (!segmentationId) { return; } segmentationService.setActiveSegmentation(activeViewportId, segmentationId); }, setDynamicCursorSizeForSculptorTool: ({ value: isDynamicCursorSize }) => { const viewportId = viewportGridService.getActiveViewportId(); const toolGroup = toolGroupService.getToolGroupForViewport(viewportId); const sculptorToolInstance = toolGroup.getToolInstance(toolNames.SculptorTool); const oldConfiguration = sculptorToolInstance.configuration; sculptorToolInstance.configuration = { ...oldConfiguration, updateCursorSize: isDynamicCursorSize ? 'dynamic' : '', }; }, setInterpolationToolConfiguration: ({ value: interpolateContours, toolNames }) => { const viewportId = viewportGridService.getActiveViewportId(); const toolGroup = toolGroupService.getToolGroupForViewport(viewportId); // Set the interpolation configuration for the active tool. const activeTool = toolGroupService.getActiveToolForViewport(viewportId); const interpolationConfig = { interpolation: { enabled: interpolateContours, }, }; toolGroup.setToolConfiguration(activeTool, interpolationConfig); // Now set the interpolation configuration for the other tools specified. if (toolNames) { Object.values(toolGroup.getToolInstances()).forEach(toolInstance => { if (toolNames?.includes(toolInstance.toolName)) { toolGroup.setToolConfiguration(toolInstance.toolName, interpolationConfig); } }); } }, setSimplifiedSplineForSplineContourSegmentationTool: ({ value: simplifiedSpline }) => { const viewportId = viewportGridService.getActiveViewportId(); const toolGroup = toolGroupService.getToolGroupForViewport(viewportId); Object.values(toolGroup.getToolInstances()).forEach(toolInstance => { if (toolInstance instanceof SplineContourSegmentationTool) { const oldConfiguration = toolInstance.configuration; toolInstance.configuration = { ...oldConfiguration, simplifiedSpline, }; } }); }, removeSmallContours: ({ areaThreshold: threshold }) => { const viewportId = viewportGridService.getActiveViewportId(); const activeSegmentation = segmentationService.getActiveSegmentation(viewportId); const activeSegment = segmentationService.getActiveSegment(viewportId); if (!activeSegmentation || !activeSegment) { return; } const { removeContourIslands } = segmentationUtilities; removeContourIslands(activeSegmentation.segmentationId, activeSegment.segmentIndex, { threshold, }); const renderingEngine = cornerstoneViewportService.getRenderingEngine(); renderingEngine.render(); }, applyLogicalContourOperation: ({ segmentAInfo, segmentBInfo, resultSegmentInfo, logicalOperation, }: { segmentAInfo: SegmentInfo; segmentBInfo: SegmentInfo; resultSegmentInfo: OperatorOptions; logicalOperation: LogicalOperation; }) => { switch (logicalOperation) { case LogicalOperation.Union: add(segmentAInfo, segmentBInfo, resultSegmentInfo); break; case LogicalOperation.Intersect: intersect(segmentAInfo, segmentBInfo, resultSegmentInfo); break; case LogicalOperation.Subtract: subtract(segmentAInfo, segmentBInfo, resultSegmentInfo); break; default: throw new Error('Unsupported logical operation'); } }, copyContourSegment: ({ sourceSegmentInfo, targetSegmentInfo, }: { sourceSegmentInfo: SegmentInfo; targetSegmentInfo?: SegmentInfo; }) => { if (!targetSegmentInfo) { const sourceSegmentation = segmentationService.getSegmentation( sourceSegmentInfo.segmentationId ); const sourceCachedStats = sourceSegmentation?.segments?.[sourceSegmentInfo.segmentIndex]?.cachedStats; targetSegmentInfo = { segmentationId: sourceSegmentInfo.segmentationId, segmentIndex: segmentationService.getNextAvailableSegmentIndex( sourceSegmentInfo.segmentationId ), }; // Copy source cachedStats so jump-to-segment navigation works on the duplicate segment. segmentationService.addSegment(targetSegmentInfo.segmentationId, { segmentIndex: targetSegmentInfo.segmentIndex, ...(sourceCachedStats && { cachedStats: csUtils.deepClone(sourceCachedStats) as Record, }), }); } copy(sourceSegmentInfo, targetSegmentInfo); }, smoothContours: () => { const viewportId = viewportGridService.getActiveViewportId(); const activeSegmentation = segmentationService.getActiveSegmentation(viewportId); const activeSegment = segmentationService.getActiveSegment(viewportId); if (!activeSegmentation || !activeSegment) { return; } const { smoothContours } = segmentationUtilities; smoothContours(activeSegmentation.segmentationId, activeSegment.segmentIndex); const renderingEngine = cornerstoneViewportService.getRenderingEngine(); renderingEngine.render(); }, removeContourHoles: () => { const viewportId = viewportGridService.getActiveViewportId(); const activeSegmentation = segmentationService.getActiveSegmentation(viewportId); const activeSegment = segmentationService.getActiveSegment(viewportId); if (!activeSegmentation || !activeSegment) { return; } const { removeContourHoles } = segmentationUtilities; removeContourHoles(activeSegmentation.segmentationId, activeSegment.segmentIndex); const renderingEngine = cornerstoneViewportService.getRenderingEngine(); renderingEngine.render(); }, decimateContours: () => { const viewportId = viewportGridService.getActiveViewportId(); const activeSegmentation = segmentationService.getActiveSegmentation(viewportId); const activeSegment = segmentationService.getActiveSegment(viewportId); if (!activeSegmentation || !activeSegment) { return; } const { decimateContours } = segmentationUtilities; decimateContours(activeSegmentation.segmentationId, activeSegment.segmentIndex); const renderingEngine = cornerstoneViewportService.getRenderingEngine(); renderingEngine.render(); }, convertContourHoles: () => { const viewportId = viewportGridService.getActiveViewportId(); const activeSegmentation = segmentationService.getActiveSegmentation(viewportId); const activeSegment = segmentationService.getActiveSegment(viewportId); if (!activeSegmentation || !activeSegment) { return; } const targetSegmentInfo = { segmentationId: activeSegmentation.segmentationId, segmentIndex: segmentationService.getNextAvailableSegmentIndex( activeSegmentation.segmentationId ), }; segmentationService.addSegment(targetSegmentInfo.segmentationId, { segmentIndex: targetSegmentInfo.segmentIndex, }); const { convertContourHoles } = segmentationUtilities; convertContourHoles( activeSegmentation.segmentationId, activeSegment.segmentIndex, targetSegmentInfo.segmentationId, targetSegmentInfo.segmentIndex ); const renderingEngine = cornerstoneViewportService.getRenderingEngine(); renderingEngine.render(); }, }; const definitions = { // The command here is to show the viewer context menu, as being the // context menu showCornerstoneContextMenu: { commandFn: actions.showCornerstoneContextMenu, options: { menuCustomizationId: 'measurementsContextMenu', commands: [ { commandName: 'showContextMenu', }, ], }, }, getNearbyToolData: { commandFn: actions.getNearbyToolData, }, getNearbyAnnotation: { commandFn: actions.getNearbyAnnotation, storeContexts: [], options: {}, }, toggleViewportColorbar: { commandFn: actions.toggleViewportColorbar, }, setMeasurementLabel: { commandFn: actions.setMeasurementLabel, }, renameMeasurement: { commandFn: actions.renameMeasurement, }, updateMeasurement: { commandFn: actions.updateMeasurement, }, jumpToMeasurement: actions.jumpToMeasurement, removeMeasurement: { commandFn: actions.removeMeasurement, }, cancelMeasurement: { commandFn: actions.cancelMeasurement, }, toggleLockMeasurement: { commandFn: actions.toggleLockMeasurement, }, toggleVisibilityMeasurement: { commandFn: actions.toggleVisibilityMeasurement, }, downloadCSVMeasurementsReport: { commandFn: actions.downloadCSVMeasurementsReport, }, setViewportWindowLevel: { commandFn: actions.setViewportWindowLevel, }, setWindowLevel: { commandFn: actions.setWindowLevel, }, setWindowLevelPreset: { commandFn: actions.setWindowLevelPreset, }, setToolActive: { commandFn: actions.setToolActive, }, setToolActiveToolbar: { commandFn: actions.setToolActiveToolbar, }, setToolEnabled: { commandFn: actions.setToolEnabled, }, rotateViewportCW: { commandFn: actions.rotateViewportBy, options: { rotation: 90 }, }, rotateViewportCCW: { commandFn: actions.rotateViewportBy, options: { rotation: -90 }, }, rotateViewportCWSet: { commandFn: actions.setViewportRotation, options: { rotation: 90 }, }, incrementActiveViewport: { commandFn: actions.changeActiveViewport, }, decrementActiveViewport: { commandFn: actions.changeActiveViewport, options: { direction: -1 }, }, flipViewportHorizontal: { commandFn: actions.toggleViewportHorizontalFlip, }, flipViewportVertical: { commandFn: actions.toggleViewportVerticalFlip, }, setViewportHorizontalFlip: { commandFn: actions.setViewportHorizontalFlip, options: { flipped: true }, }, setViewportVerticalFlip: { commandFn: actions.setViewportVerticalFlip, options: { flipped: true }, }, invertViewport: { commandFn: actions.invertViewport, }, resetViewport: { commandFn: actions.resetViewport, }, scaleUpViewport: { commandFn: actions.scaleViewport, options: { direction: 1 }, }, scaleDownViewport: { commandFn: actions.scaleViewport, options: { direction: -1 }, }, fitViewportToWindow: { commandFn: actions.scaleViewport, options: { direction: 0 }, }, nextImage: { commandFn: actions.scroll, options: { direction: 1 }, }, previousImage: { commandFn: actions.scroll, options: { direction: -1 }, }, firstImage: { commandFn: actions.jumpToImage, options: { imageIndex: 0 }, }, lastImage: { commandFn: actions.jumpToImage, options: { imageIndex: -1 }, }, jumpToImage: { commandFn: actions.jumpToImage, }, showDownloadViewportModal: { commandFn: actions.showDownloadViewportModal, }, toggleCine: { commandFn: actions.toggleCine, }, arrowTextCallback: { commandFn: actions.arrowTextCallback, }, setViewportActive: { commandFn: actions.setViewportActive, }, setViewportColormap: { commandFn: actions.setViewportColormap, }, setViewportForToolConfiguration: { commandFn: actions.setViewportForToolConfiguration, }, storePresentation: { commandFn: actions.storePresentation, }, attachProtocolViewportDataListener: { commandFn: actions.attachProtocolViewportDataListener, }, setViewportPreset: { commandFn: actions.setViewportPreset, }, setVolumeRenderingQulaity: { commandFn: actions.setVolumeRenderingQulaity, }, shiftVolumeOpacityPoints: { commandFn: actions.shiftVolumeOpacityPoints, }, setVolumeLighting: { commandFn: actions.setVolumeLighting, }, resetCrosshairs: { commandFn: actions.resetCrosshairs, }, toggleSynchronizer: { commandFn: actions.toggleSynchronizer, }, updateVolumeData: { commandFn: actions.updateVolumeData, }, toggleEnabledDisabledToolbar: { commandFn: actions.toggleEnabledDisabledToolbar, }, toggleActiveDisabledToolbar: { commandFn: actions.toggleActiveDisabledToolbar, }, updateStoredPositionPresentation: { commandFn: actions.updateStoredPositionPresentation, }, updateStoredSegmentationPresentation: { commandFn: actions.updateStoredSegmentationPresentation, }, createLabelmapForViewport: { commandFn: actions.createLabelmapForViewport, }, createContourForViewport: { commandFn: actions.createContourForViewport, }, setActiveSegmentation: { commandFn: actions.setActiveSegmentation, }, addSegment: { commandFn: actions.addSegmentCommand, }, setActiveSegmentAndCenter: { commandFn: actions.setActiveSegmentAndCenterCommand, }, toggleSegmentVisibility: { commandFn: actions.toggleSegmentVisibilityCommand, }, toggleSegmentLock: { commandFn: actions.toggleSegmentLockCommand, }, toggleSegmentationVisibility: { commandFn: actions.toggleSegmentationVisibilityCommand, }, downloadSegmentation: { commandFn: actions.downloadSegmentationCommand, }, storeSegmentation: { commandFn: actions.storeSegmentationCommand, }, downloadRTSS: { commandFn: actions.downloadRTSSCommand, }, setSegmentationStyle: { commandFn: actions.setSegmentationStyleCommand, }, deleteSegment: { commandFn: actions.deleteSegmentCommand, }, deleteSegmentation: { commandFn: actions.deleteSegmentationCommand, }, removeSegmentationFromViewport: { commandFn: actions.removeSegmentationFromViewportCommand, }, toggleRenderInactiveSegmentations: { commandFn: actions.toggleRenderInactiveSegmentationsCommand, }, setFillAlpha: { commandFn: createSetStyleCommand('fillAlpha'), }, setOutlineWidth: { commandFn: createSetStyleCommand('outlineWidth'), }, setRenderFill: { commandFn: createSetStyleCommand('renderFill'), }, setRenderFillInactive: { commandFn: createSetStyleCommand('renderFillInactive'), }, setRenderOutline: { commandFn: createSetStyleCommand('renderOutline'), }, setRenderOutlineInactive: { commandFn: createSetStyleCommand('renderOutlineInactive'), }, setFillAlphaInactive: { commandFn: createSetStyleCommand('fillAlphaInactive'), }, editSegmentLabel: { commandFn: actions.editSegmentLabel, }, editSegmentationLabel: { commandFn: actions.editSegmentationLabel, }, editSegmentColor: { commandFn: actions.editSegmentColor, }, getRenderInactiveSegmentations: { commandFn: actions.getRenderInactiveSegmentations, }, deleteActiveAnnotation: { commandFn: actions.deleteActiveAnnotation, }, setDisplaySetsForViewports: actions.setDisplaySetsForViewports, undo: actions.undo, redo: actions.redo, interpolateLabelmap: actions.interpolateLabelmap, runSegmentBidirectional: actions.runSegmentBidirectional, downloadCSVSegmentationReport: actions.downloadCSVSegmentationReport, toggleSegmentPreviewEdit: actions.toggleSegmentPreviewEdit, toggleSegmentSelect: actions.toggleSegmentSelect, acceptPreview: actions.acceptPreview, rejectPreview: actions.rejectPreview, cancelActiveOperation: actions.cancelActiveOperation, toggleUseCenterSegmentIndex: actions.toggleUseCenterSegmentIndex, toggleLabelmapAssist: actions.toggleLabelmapAssist, interpolateScrollForMarkerLabelmap: actions.interpolateScrollForMarkerLabelmap, clearMarkersForMarkerLabelmap: actions.clearMarkersForMarkerLabelmap, setBrushSize: actions.setBrushSize, setThresholdRange: actions.setThresholdRange, increaseBrushSize: actions.increaseBrushSize, decreaseBrushSize: actions.decreaseBrushSize, addNewSegment: actions.addNewSegment, loadSegmentationDisplaySetsForViewport: actions.loadSegmentationDisplaySetsForViewport, setViewportOrientation: actions.setViewportOrientation, hydrateSecondaryDisplaySet: actions.hydrateSecondaryDisplaySet, getVolumeIdForDisplaySet: actions.getVolumeIdForDisplaySet, triggerCreateAnnotationMemo: actions.triggerCreateAnnotationMemo, startRecordingForAnnotationGroup: actions.startRecordingForAnnotationGroup, endRecordingForAnnotationGroup: actions.endRecordingForAnnotationGroup, toggleSegmentLabel: actions.toggleSegmentLabel, jumpToMeasurementViewport: actions.jumpToMeasurementViewport, initializeSegmentLabelTool: actions.initializeSegmentLabelTool, activateSelectedSegmentationOfType: actions.activateSelectedSegmentationOfType, setDynamicCursorSizeForSculptorTool: actions.setDynamicCursorSizeForSculptorTool, setSimplifiedSplineForSplineContourSegmentationTool: actions.setSimplifiedSplineForSplineContourSegmentationTool, removeSmallContours: actions.removeSmallContours, applyLogicalContourOperation: actions.applyLogicalContourOperation, copyContourSegment: actions.copyContourSegment, smoothContours: actions.smoothContours, removeContourHoles: actions.removeContourHoles, decimateContours: actions.decimateContours, convertContourHoles: actions.convertContourHoles, setInterpolationToolConfiguration: actions.setInterpolationToolConfiguration, }; return { actions, definitions, defaultContext: 'CORNERSTONE', }; } export default commandsModule;