1642 lines
55 KiB
TypeScript
1642 lines
55 KiB
TypeScript
import {
|
|
getEnabledElement,
|
|
StackViewport,
|
|
VolumeViewport,
|
|
utilities as csUtils,
|
|
Types as CoreTypes,
|
|
BaseVolumeViewport,
|
|
} from '@cornerstonejs/core';
|
|
import {
|
|
ToolGroupManager,
|
|
Enums,
|
|
utilities as cstUtils,
|
|
ReferenceLinesTool,
|
|
annotation,
|
|
} from '@cornerstonejs/tools';
|
|
import * as cornerstoneTools from '@cornerstonejs/tools';
|
|
|
|
import { Types as OhifTypes, utils } from '@ohif/core';
|
|
import i18n from '@ohif/i18n';
|
|
import {
|
|
callLabelAutocompleteDialog,
|
|
showLabelAnnotationPopup,
|
|
createReportAsync,
|
|
callInputDialog,
|
|
colorPickerDialog,
|
|
} from '@ohif/extension-default';
|
|
import { vec3, mat4 } from 'gl-matrix';
|
|
import CornerstoneViewportDownloadForm from './utils/CornerstoneViewportDownloadForm';
|
|
import toggleImageSliceSync from './utils/imageSliceSync/toggleImageSliceSync';
|
|
import { getFirstAnnotationSelected } from './utils/measurementServiceMappings/utils/selection';
|
|
import getActiveViewportEnabledElement from './utils/getActiveViewportEnabledElement';
|
|
import toggleVOISliceSync from './utils/toggleVOISliceSync';
|
|
import { usePositionPresentationStore, useSegmentationPresentationStore } from './stores';
|
|
import { toolNames } from './initCornerstoneTools';
|
|
|
|
const { DefaultHistoryMemo } = csUtils.HistoryMemo;
|
|
const toggleSyncFunctions = {
|
|
imageSlice: toggleImageSliceSync,
|
|
voi: toggleVOISliceSync,
|
|
};
|
|
|
|
function commandsModule({
|
|
servicesManager,
|
|
extensionManager,
|
|
commandsManager,
|
|
}: OhifTypes.Extensions.ExtensionParams): OhifTypes.Extensions.CommandsModule {
|
|
const {
|
|
viewportGridService,
|
|
toolGroupService,
|
|
cineService,
|
|
uiDialogService,
|
|
cornerstoneViewportService,
|
|
uiNotificationService,
|
|
measurementService,
|
|
customizationService,
|
|
colorbarService,
|
|
hangingProtocolService,
|
|
syncGroupService,
|
|
} = servicesManager.services;
|
|
|
|
const { measurementServiceSource } = this;
|
|
|
|
function _getActiveViewportEnabledElement() {
|
|
return getActiveViewportEnabledElement(viewportGridService);
|
|
}
|
|
|
|
function _getActiveViewportToolGroupId() {
|
|
const viewport = _getActiveViewportEnabledElement();
|
|
return toolGroupService.getToolGroupForViewport(viewport.id);
|
|
}
|
|
|
|
const actions = {
|
|
/**
|
|
* 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,
|
|
});
|
|
},
|
|
updateStoredPositionPresentation: ({
|
|
viewportId,
|
|
displaySetInstanceUID,
|
|
referencedImageId,
|
|
}) => {
|
|
const presentations = cornerstoneViewportService.getPresentations(viewportId);
|
|
const { positionPresentationStore, setPositionPresentation, getPositionPresentationId } =
|
|
usePositionPresentationStore.getState();
|
|
|
|
// Look inside positionPresentationStore and find the key that includes the displaySetInstanceUID
|
|
// and the value has viewportId as activeViewportId.
|
|
const previousReferencedDisplaySetStoreKey = Object.entries(positionPresentationStore).find(
|
|
([key, value]) => key.includes(displaySetInstanceUID) && value.viewportId === viewportId
|
|
)?.[0];
|
|
|
|
if (previousReferencedDisplaySetStoreKey) {
|
|
const presentationData = referencedImageId
|
|
? {
|
|
...presentations.positionPresentation,
|
|
viewReference: {
|
|
referencedImageId,
|
|
},
|
|
}
|
|
: presentations.positionPresentation;
|
|
|
|
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: [displaySetInstanceUID],
|
|
viewportId,
|
|
});
|
|
|
|
setPositionPresentation(positionPresentationId, presentations.positionPresentation);
|
|
},
|
|
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;
|
|
},
|
|
/** Delete the given measurement */
|
|
deleteMeasurement: ({ uid }) => {
|
|
if (uid) {
|
|
measurementServiceSource.remove(uid);
|
|
}
|
|
},
|
|
/**
|
|
* Show the measurement labelling input dialog and update the label
|
|
* on the measurement with a response if not cancelled.
|
|
*/
|
|
setMeasurementLabel: ({ uid }) => {
|
|
const labelConfig = customizationService.getCustomization('measurementLabels');
|
|
const renderContent = customizationService.getCustomization('ui.labellingComponent');
|
|
const measurement = measurementService.getMeasurement(uid);
|
|
showLabelAnnotationPopup(measurement, uiDialogService, labelConfig, renderContent).then(
|
|
(val: Map<any, any>) => {
|
|
measurementService.update(
|
|
uid,
|
|
{
|
|
...val,
|
|
},
|
|
true
|
|
);
|
|
}
|
|
);
|
|
},
|
|
|
|
/**
|
|
*
|
|
* @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 `<designator>:<codeValue>`
|
|
* * 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 = [] }) => {
|
|
measurementService.jumpToMeasurement(viewportGridService.getActiveViewportId(), uid);
|
|
for (const measurement of displayMeasurements) {
|
|
measurement.isActive = measurement.uid === uid;
|
|
}
|
|
},
|
|
|
|
removeMeasurement: ({ uid }) => {
|
|
measurementService.remove(uid);
|
|
},
|
|
|
|
renameMeasurement: ({ uid }) => {
|
|
const labelConfig = customizationService.getCustomization('measurementLabels');
|
|
const renderContent = customizationService.getCustomization('ui.labellingComponent');
|
|
const measurement = measurementService.getMeasurement(uid);
|
|
showLabelAnnotationPopup(measurement, uiDialogService, labelConfig, renderContent).then(
|
|
val => {
|
|
measurementService.update(
|
|
uid,
|
|
{
|
|
...val,
|
|
},
|
|
true
|
|
);
|
|
}
|
|
);
|
|
},
|
|
|
|
toggleLockMeasurement: ({ uid }) => {
|
|
measurementService.toggleLockMeasurement(uid);
|
|
},
|
|
|
|
toggleVisibilityMeasurement: ({ uid }) => {
|
|
measurementService.toggleVisibilityMeasurement(uid);
|
|
},
|
|
|
|
/**
|
|
* Clear the measurements
|
|
*/
|
|
clearMeasurements: options => {
|
|
const { measurementFilter } = options;
|
|
measurementService.clearMeasurements(
|
|
measurementFilter ? measurementFilter.bind(options) : null
|
|
);
|
|
},
|
|
|
|
/**
|
|
* Download the CSV report for the measurements.
|
|
*/
|
|
downloadCSVMeasurementsReport: ({ measurementFilter }) => {
|
|
utils.downloadCSVReport(measurementService.getMeasurements(measurementFilter));
|
|
},
|
|
|
|
// 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: ({ callback, data, uid }) => {
|
|
const labelConfig = customizationService.getCustomization('measurementLabels');
|
|
const renderContent = customizationService.getCustomization('ui.labellingComponent');
|
|
callLabelAutocompleteDialog(uiDialogService, callback, {}, labelConfig, renderContent);
|
|
},
|
|
toggleCine: () => {
|
|
const { viewports } = viewportGridService.getState();
|
|
const { isCineEnabled } = cineService.getState();
|
|
cineService.setIsCineEnabled(!isCineEnabled);
|
|
viewports.forEach((_, index) => cineService.setCine({ id: index, isPlaying: false }));
|
|
},
|
|
|
|
setViewportWindowLevel({ viewportId, window, level }) {
|
|
// convert to numbers
|
|
const windowWidthNum = Number(window);
|
|
const windowCenterNum = Number(level);
|
|
|
|
// get actor from the viewport
|
|
const renderingEngine = cornerstoneViewportService.getRenderingEngine();
|
|
const viewport = renderingEngine.getViewport(viewportId);
|
|
|
|
const { lower, upper } = csUtils.windowLevel.toLowHighRange(windowWidthNum, windowCenterNum);
|
|
|
|
viewport.setProperties({
|
|
voiRange: {
|
|
upper,
|
|
lower,
|
|
},
|
|
});
|
|
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,
|
|
window: windowLevelPreset.window,
|
|
level: windowLevelPreset.level,
|
|
});
|
|
},
|
|
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 }) {
|
|
const toolName = itemId || value;
|
|
toolGroupId = toolGroupId ?? _getActiveViewportToolGroupId();
|
|
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);
|
|
|
|
toolIsActive
|
|
? toolGroup.setToolDisabled(toolName)
|
|
: actions.setToolActive({ toolName, toolGroupId });
|
|
|
|
// we should set the previously active tool to active after we set the
|
|
// current tool disabled
|
|
if (toolIsActive) {
|
|
const prevToolName = toolGroup.getPrevActivePrimaryToolName();
|
|
if (prevToolName !== toolName) {
|
|
actions.setToolActive({ toolName: prevToolName, toolGroupId });
|
|
}
|
|
}
|
|
},
|
|
setToolActiveToolbar: ({ value, itemId, toolName, toolGroupIds = [] }) => {
|
|
// 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 });
|
|
});
|
|
},
|
|
setToolActive: ({ toolName, toolGroupId = null }) => {
|
|
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: [
|
|
{
|
|
mouseButton: Enums.MouseBindings.Primary,
|
|
},
|
|
],
|
|
});
|
|
},
|
|
showDownloadViewportModal: () => {
|
|
const { activeViewportId } = viewportGridService.getState();
|
|
|
|
if (!cornerstoneViewportService.getCornerstoneViewport(activeViewportId)) {
|
|
// Cannot download a non-cornerstone viewport (image).
|
|
uiNotificationService.show({
|
|
title: 'Download Image',
|
|
message: 'Image cannot be downloaded',
|
|
type: 'error',
|
|
});
|
|
return;
|
|
}
|
|
|
|
const { uiModalService } = servicesManager.services;
|
|
|
|
if (uiModalService) {
|
|
uiModalService.show({
|
|
content: CornerstoneViewportDownloadForm,
|
|
title: 'Download High Quality Image',
|
|
contentProps: {
|
|
activeViewportId,
|
|
onClose: uiModalService.hide,
|
|
cornerstoneViewportService,
|
|
},
|
|
containerDimensions: 'w-[70%] max-w-[900px]',
|
|
});
|
|
}
|
|
},
|
|
rotateViewport: ({ rotation }) => {
|
|
const enabledElement = _getActiveViewportEnabledElement();
|
|
if (!enabledElement) {
|
|
return;
|
|
}
|
|
|
|
const { viewport } = enabledElement;
|
|
|
|
if (viewport instanceof BaseVolumeViewport) {
|
|
const camera = viewport.getCamera();
|
|
const rotAngle = (rotation * Math.PI) / 180;
|
|
const rotMat = mat4.identity(new Float32Array(16));
|
|
mat4.rotate(rotMat, rotMat, rotAngle, camera.viewPlaneNormal);
|
|
const rotatedViewUp = vec3.transformMat4(vec3.create(), camera.viewUp, rotMat);
|
|
viewport.setCamera({ viewUp: rotatedViewUp as CoreTypes.Point3 });
|
|
viewport.render();
|
|
} else if (viewport.getRotation !== undefined) {
|
|
const presentation = viewport.getViewPresentation();
|
|
const { rotation: currentRotation } = presentation;
|
|
const newRotation = (currentRotation + rotation + 360) % 360;
|
|
viewport.setViewPresentation({ rotation: newRotation });
|
|
viewport.render();
|
|
}
|
|
},
|
|
flipViewportHorizontal: () => {
|
|
const enabledElement = _getActiveViewportEnabledElement();
|
|
|
|
if (!enabledElement) {
|
|
return;
|
|
}
|
|
|
|
const { viewport } = enabledElement;
|
|
|
|
const { flipHorizontal } = viewport.getCamera();
|
|
viewport.setCamera({ flipHorizontal: !flipHorizontal });
|
|
viewport.render();
|
|
},
|
|
flipViewportVertical: () => {
|
|
const enabledElement = _getActiveViewportEnabledElement();
|
|
|
|
if (!enabledElement) {
|
|
return;
|
|
}
|
|
|
|
const { viewport } = enabledElement;
|
|
|
|
const { flipVertical } = viewport.getCamera();
|
|
viewport.setCamera({ flipVertical: !flipVertical });
|
|
viewport.render();
|
|
},
|
|
invertViewport: ({ element }) => {
|
|
let enabledElement;
|
|
|
|
if (element === undefined) {
|
|
enabledElement = _getActiveViewportEnabledElement();
|
|
} else {
|
|
enabledElement = element;
|
|
}
|
|
|
|
if (!enabledElement) {
|
|
return;
|
|
}
|
|
|
|
const { viewport } = enabledElement;
|
|
|
|
const { invert } = viewport.getProperties();
|
|
viewport.setProperties({ invert: !invert });
|
|
viewport.render();
|
|
},
|
|
resetViewport: () => {
|
|
const enabledElement = _getActiveViewportEnabledElement();
|
|
|
|
if (!enabledElement) {
|
|
return;
|
|
}
|
|
|
|
const { viewport } = enabledElement;
|
|
|
|
viewport.resetProperties?.();
|
|
viewport.resetCamera();
|
|
|
|
viewport.render();
|
|
},
|
|
scaleViewport: ({ direction }) => {
|
|
const enabledElement = _getActiveViewportEnabledElement();
|
|
const scaleFactor = direction > 0 ? 0.9 : 1.1;
|
|
|
|
if (!enabledElement) {
|
|
return;
|
|
}
|
|
const { viewport } = enabledElement;
|
|
|
|
if (viewport instanceof StackViewport) {
|
|
if (direction) {
|
|
const { parallelScale } = viewport.getCamera();
|
|
viewport.setCamera({ parallelScale: parallelScale * scaleFactor });
|
|
viewport.render();
|
|
} else {
|
|
viewport.resetCamera();
|
|
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 (viewport instanceof StackViewport) {
|
|
numberOfSlices = viewport.getImageIds().length;
|
|
} else if (viewport instanceof VolumeViewport) {
|
|
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: ({ direction }) => {
|
|
const enabledElement = _getActiveViewportEnabledElement();
|
|
|
|
if (!enabledElement) {
|
|
return;
|
|
}
|
|
|
|
const { viewport } = enabledElement;
|
|
const options = { delta: direction };
|
|
|
|
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 };
|
|
|
|
if (viewport instanceof StackViewport) {
|
|
viewport.setProperties({ colormap });
|
|
}
|
|
|
|
if (viewport instanceof VolumeViewport) {
|
|
if (!displaySetInstanceUID) {
|
|
const { viewports } = viewportGridService.getState();
|
|
displaySetInstanceUID = viewports.get(viewportId)?.displaySetInstanceUIDs[0];
|
|
}
|
|
|
|
// ToDo: Find a better way of obtaining the volumeId that corresponds to the displaySetInstanceUID
|
|
const volumeId =
|
|
viewport
|
|
.getAllVolumeIds()
|
|
.find((_volumeId: string) => _volumeId.includes(displaySetInstanceUID)) ??
|
|
viewport.getVolumeId();
|
|
viewport.setProperties({ colormap }, volumeId);
|
|
}
|
|
|
|
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,
|
|
});
|
|
}
|
|
},
|
|
setSourceViewportForReferenceLinesTool: ({ viewportId }) => {
|
|
if (!viewportId) {
|
|
const { activeViewportId } = viewportGridService.getState();
|
|
viewportId = activeViewportId ?? 'default';
|
|
}
|
|
|
|
const toolGroup = toolGroupService.getToolGroupForViewport(viewportId);
|
|
|
|
toolGroup?.setToolConfiguration(
|
|
ReferenceLinesTool.toolName,
|
|
{
|
|
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;
|
|
}
|
|
viewport.setProperties({
|
|
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);
|
|
const { actor } = viewport.getActors()[0];
|
|
const mapper = actor.getMapper();
|
|
const image = mapper.getInputData();
|
|
const dims = image.getDimensions();
|
|
const spacing = image.getSpacing();
|
|
const spatialDiagonal = vec3.length(
|
|
vec3.fromValues(dims[0] * spacing[0], dims[1] * spacing[1], dims[2] * spacing[2])
|
|
);
|
|
|
|
let sampleDistance = spacing.reduce((a, b) => a + b) / 3.0;
|
|
sampleDistance /= volumeQuality > 1 ? 0.5 * volumeQuality ** 2 : 1.0;
|
|
const samplesPerRay = spatialDiagonal / sampleDistance + 1;
|
|
mapper.setMaximumSamplesPerRay(samplesPerRay);
|
|
mapper.setSampleDistance(sampleDistance);
|
|
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);
|
|
const { actor } = viewport.getActors()[0];
|
|
const ofun = actor.getProperty().getScalarOpacity(0);
|
|
|
|
const opacityPointValues = []; // Array to hold values
|
|
// Gather Existing Values
|
|
const size = ofun.getSize();
|
|
for (let pointIdx = 0; pointIdx < size; pointIdx++) {
|
|
const opacityPointValue = [0, 0, 0, 0];
|
|
ofun.getNodeValue(pointIdx, opacityPointValue);
|
|
// opacityPointValue now holds [xLocation, opacity, midpoint, sharpness]
|
|
opacityPointValues.push(opacityPointValue);
|
|
}
|
|
// Add offset
|
|
opacityPointValues.forEach(opacityPointValue => {
|
|
opacityPointValue[0] += shift; // Change the location value
|
|
});
|
|
// Set new values
|
|
ofun.removeAllPoints();
|
|
opacityPointValues.forEach(opacityPointValue => {
|
|
ofun.addPoint(...opacityPointValue);
|
|
});
|
|
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);
|
|
const { actor } = viewport.getActors()[0];
|
|
const property = actor.getProperty();
|
|
|
|
if (options.shade !== undefined) {
|
|
property.setShade(options.shade);
|
|
}
|
|
|
|
if (options.ambient !== undefined) {
|
|
property.setAmbient(options.ambient);
|
|
}
|
|
|
|
if (options.diffuse !== undefined) {
|
|
property.setDiffuse(options.diffuse);
|
|
}
|
|
|
|
if (options.specular !== undefined) {
|
|
property.setSpecular(options.specular);
|
|
}
|
|
|
|
viewport.render();
|
|
},
|
|
resetCrosshairs: ({ viewportId }) => {
|
|
const crosshairInstances = [];
|
|
|
|
const getCrosshairInstances = toolGroupId => {
|
|
const toolGroup = toolGroupService.getToolGroup(toolGroupId);
|
|
crosshairInstances.push(toolGroup.getToolInstance('Crosshairs'));
|
|
};
|
|
|
|
if (!viewportId) {
|
|
const toolGroupIds = toolGroupService.getToolGroupIds();
|
|
toolGroupIds.forEach(getCrosshairInstances);
|
|
} else {
|
|
const toolGroup = toolGroupService.getToolGroupForViewport(viewportId);
|
|
getCrosshairInstances(toolGroup.id);
|
|
}
|
|
|
|
crosshairInstances.forEach(ins => {
|
|
ins?.computeToolCenter();
|
|
});
|
|
},
|
|
/**
|
|
* Creates a labelmap for the active viewport
|
|
*/
|
|
createLabelmapForViewport: async ({ viewportId, options = {} }) => {
|
|
const { viewportGridService, displaySetService, segmentationService } =
|
|
servicesManager.services;
|
|
const { viewports } = viewportGridService.getState();
|
|
const targetViewportId = viewportId;
|
|
|
|
const viewport = viewports.get(targetViewportId);
|
|
|
|
// Todo: add support for multiple display sets
|
|
const displaySetInstanceUID =
|
|
options.displaySetInstanceUID || viewport.displaySetInstanceUIDs[0];
|
|
|
|
const segs = segmentationService.getSegmentations();
|
|
|
|
const label = options.label || `Segmentation ${segs.length + 1}`;
|
|
const segmentationId = options.segmentationId || `${csUtils.uuidv4()}`;
|
|
|
|
const displaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID);
|
|
|
|
const generatedSegmentationId = await segmentationService.createLabelmapForDisplaySet(
|
|
displaySet,
|
|
{
|
|
label,
|
|
segmentationId,
|
|
segments: options.createInitialSegment
|
|
? {
|
|
1: {
|
|
label: `${i18n.t('Segment')} 1`,
|
|
active: true,
|
|
},
|
|
}
|
|
: {},
|
|
}
|
|
);
|
|
|
|
await segmentationService.addSegmentationRepresentation(viewportId, {
|
|
segmentationId,
|
|
type: Enums.SegmentationRepresentations.Labelmap,
|
|
});
|
|
|
|
return generatedSegmentationId;
|
|
},
|
|
|
|
/**
|
|
* 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 }) => {
|
|
const { segmentationService } = servicesManager.services;
|
|
segmentationService.addSegment(segmentationId);
|
|
},
|
|
|
|
/**
|
|
* 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 } = servicesManager.services;
|
|
segmentationService.setActiveSegment(segmentationId, segmentIndex);
|
|
segmentationService.jumpToSegmentCenter(segmentationId, segmentIndex);
|
|
},
|
|
|
|
/**
|
|
* 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 ({ segmentationId }) => {
|
|
const { segmentationService, viewportGridService } = servicesManager.services;
|
|
const datasources = extensionManager.getActiveDataSource();
|
|
|
|
const displaySetInstanceUIDs = await createReportAsync({
|
|
servicesManager,
|
|
getReport: () =>
|
|
commandsManager.runCommand('storeSegmentation', {
|
|
segmentationId,
|
|
dataSource: datasources[0],
|
|
}),
|
|
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 }) => {
|
|
const { segmentationService, viewportGridService } = servicesManager.services;
|
|
segmentationService.removeSegmentationRepresentations(
|
|
viewportGridService.getActiveViewportId(),
|
|
{ segmentationId }
|
|
);
|
|
},
|
|
|
|
/**
|
|
* Toggles rendering of inactive segmentations
|
|
*/
|
|
toggleRenderInactiveSegmentationsCommand: () => {
|
|
const { segmentationService, viewportGridService } = servicesManager.services;
|
|
const viewportId = viewportGridService.getActiveViewportId();
|
|
const renderInactive = segmentationService.getRenderInactiveSegmentations(viewportId);
|
|
segmentationService.setRenderInactiveSegmentations(viewportId, !renderInactive);
|
|
},
|
|
|
|
/**
|
|
* Sets the fill alpha value for a segmentation type
|
|
* @param props.type - The type of segmentation
|
|
* @param props.value - The alpha value to set
|
|
*/
|
|
setFillAlphaCommand: ({ type, value }) => {
|
|
const { segmentationService } = servicesManager.services;
|
|
segmentationService.setStyle({ type }, { fillAlpha: value });
|
|
},
|
|
|
|
/**
|
|
* Sets the outline width for a segmentation type
|
|
* @param props.type - The type of segmentation
|
|
* @param props.value - The width value to set
|
|
*/
|
|
setOutlineWidthCommand: ({ type, value }) => {
|
|
const { segmentationService } = servicesManager.services;
|
|
segmentationService.setStyle({ type }, { outlineWidth: value });
|
|
},
|
|
|
|
/**
|
|
* Sets whether to render fill for a segmentation type
|
|
* @param props.type - The type of segmentation
|
|
* @param props.value - Whether to render fill
|
|
*/
|
|
setRenderFillCommand: ({ type, value }) => {
|
|
const { segmentationService } = servicesManager.services;
|
|
segmentationService.setStyle({ type }, { renderFill: value });
|
|
},
|
|
|
|
/**
|
|
* Sets whether to render outline for a segmentation type
|
|
* @param props.type - The type of segmentation
|
|
* @param props.value - Whether to render outline
|
|
*/
|
|
setRenderOutlineCommand: ({ type, value }) => {
|
|
const { segmentationService } = servicesManager.services;
|
|
segmentationService.setStyle({ type }, { renderOutline: value });
|
|
},
|
|
|
|
/**
|
|
* Sets the fill alpha for inactive segmentations
|
|
* @param props.type - The type of segmentation
|
|
* @param props.value - The alpha value to set
|
|
*/
|
|
setFillAlphaInactiveCommand: ({ type, value }) => {
|
|
const { segmentationService } = servicesManager.services;
|
|
segmentationService.setStyle({ type }, { fillAlphaInactive: value });
|
|
},
|
|
|
|
editSegmentLabel: ({ segmentationId, segmentIndex }) => {
|
|
const { segmentationService, uiDialogService } = servicesManager.services;
|
|
const segmentation = segmentationService.getSegmentation(segmentationId);
|
|
|
|
if (!segmentation) {
|
|
return;
|
|
}
|
|
|
|
const segment = segmentation.segments[segmentIndex];
|
|
const { label } = segment;
|
|
|
|
const callback = (label, actionId) => {
|
|
if (label === '') {
|
|
return;
|
|
}
|
|
|
|
segmentationService.setSegmentLabel(segmentationId, segmentIndex, label);
|
|
};
|
|
|
|
callInputDialog(uiDialogService, label, callback, false, {
|
|
dialogTitle: 'Edit Segment Label',
|
|
inputLabel: 'Enter new label',
|
|
});
|
|
},
|
|
|
|
editSegmentationLabel: ({ segmentationId }) => {
|
|
const { segmentationService, uiDialogService } = servicesManager.services;
|
|
const segmentation = segmentationService.getSegmentation(segmentationId);
|
|
|
|
if (!segmentation) {
|
|
return;
|
|
}
|
|
|
|
const { label } = segmentation;
|
|
|
|
const callback = (label, actionId) => {
|
|
if (label === '') {
|
|
return;
|
|
}
|
|
|
|
segmentationService.addOrUpdateSegmentation({ segmentationId, label });
|
|
};
|
|
|
|
callInputDialog(uiDialogService, label, callback, false, {
|
|
dialogTitle: 'Edit Segmentation Label',
|
|
inputLabel: 'Enter new 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,
|
|
};
|
|
|
|
colorPickerDialog(uiDialogService, rgbaColor, (newRgbaColor, actionId) => {
|
|
if (actionId === 'cancel') {
|
|
return;
|
|
}
|
|
|
|
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);
|
|
});
|
|
},
|
|
undo: () => {
|
|
DefaultHistoryMemo.undo();
|
|
},
|
|
redo: () => {
|
|
DefaultHistoryMemo.redo();
|
|
},
|
|
};
|
|
|
|
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,
|
|
},
|
|
deleteMeasurement: {
|
|
commandFn: actions.deleteMeasurement,
|
|
},
|
|
setMeasurementLabel: {
|
|
commandFn: actions.setMeasurementLabel,
|
|
},
|
|
updateMeasurement: {
|
|
commandFn: actions.updateMeasurement,
|
|
},
|
|
clearMeasurements: {
|
|
commandFn: actions.clearMeasurements,
|
|
},
|
|
jumpToMeasurement: {
|
|
commandFn: actions.jumpToMeasurement,
|
|
},
|
|
removeMeasurement: {
|
|
commandFn: actions.removeMeasurement,
|
|
},
|
|
renameMeasurement: {
|
|
commandFn: actions.renameMeasurement,
|
|
},
|
|
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.rotateViewport,
|
|
options: { rotation: 90 },
|
|
},
|
|
rotateViewportCCW: {
|
|
commandFn: actions.rotateViewport,
|
|
options: { rotation: -90 },
|
|
},
|
|
incrementActiveViewport: {
|
|
commandFn: actions.changeActiveViewport,
|
|
},
|
|
decrementActiveViewport: {
|
|
commandFn: actions.changeActiveViewport,
|
|
options: { direction: -1 },
|
|
},
|
|
flipViewportHorizontal: {
|
|
commandFn: actions.flipViewportHorizontal,
|
|
},
|
|
flipViewportVertical: {
|
|
commandFn: actions.flipViewportVertical,
|
|
},
|
|
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,
|
|
},
|
|
setSourceViewportForReferenceLinesTool: {
|
|
commandFn: actions.setSourceViewportForReferenceLinesTool,
|
|
},
|
|
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,
|
|
},
|
|
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: actions.setFillAlphaCommand,
|
|
},
|
|
setOutlineWidth: {
|
|
commandFn: actions.setOutlineWidthCommand,
|
|
},
|
|
setRenderFill: {
|
|
commandFn: actions.setRenderFillCommand,
|
|
},
|
|
setRenderOutline: {
|
|
commandFn: actions.setRenderOutlineCommand,
|
|
},
|
|
setFillAlphaInactive: {
|
|
commandFn: actions.setFillAlphaInactiveCommand,
|
|
},
|
|
editSegmentLabel: {
|
|
commandFn: actions.editSegmentLabel,
|
|
},
|
|
editSegmentationLabel: {
|
|
commandFn: actions.editSegmentationLabel,
|
|
},
|
|
editSegmentColor: {
|
|
commandFn: actions.editSegmentColor,
|
|
},
|
|
getRenderInactiveSegmentations: {
|
|
commandFn: actions.getRenderInactiveSegmentations,
|
|
},
|
|
deleteActiveAnnotation: {
|
|
commandFn: actions.deleteActiveAnnotation,
|
|
},
|
|
undo: actions.undo,
|
|
redo: actions.redo,
|
|
};
|
|
|
|
return {
|
|
actions,
|
|
definitions,
|
|
defaultContext: 'CORNERSTONE',
|
|
};
|
|
}
|
|
|
|
export default commandsModule;
|