ohif-viewer/extensions/usAnnotation/src/getCommandsModule.ts

353 lines
12 KiB
TypeScript

import { UltrasoundPleuraBLineTool, Enums as csToolsEnums } from '@cornerstonejs/tools';
import { Types as OhifTypes } from '@ohif/core';
import { eventTarget, triggerEvent, utilities } from '@cornerstonejs/core';
import getInstanceByImageId from './getInstanceByImageId';
import { setShowPercentage } from './PleuraBlinePercentage';
const { transformWorldToIndex } = utilities;
/**
* Creates and returns the commands module for ultrasound annotation
* @param params - Extension parameters including servicesManager and commandsManager
* @returns The commands module with actions and definitions
*/
function commandsModule({
servicesManager,
commandsManager,
}: OhifTypes.Extensions.ExtensionParams): OhifTypes.Extensions.CommandsModule {
const { viewportGridService, toolGroupService, cornerstoneViewportService } =
servicesManager.services as AppTypes.Services;
const actions = {
/**
* Switches the active ultrasound annotation type
* @param options - Object containing the annotationType to switch to
*/
switchUSPleuraBLineAnnotation: ({ annotationType }) => {
const activeViewportId = viewportGridService.getActiveViewportId();
const toolGroup = toolGroupService.getToolGroupForViewport(activeViewportId);
if (!toolGroup) {
return;
}
const usAnnotation = toolGroup.getToolInstance(UltrasoundPleuraBLineTool.toolName);
if (usAnnotation) {
usAnnotation.setActiveAnnotationType(annotationType);
}
},
/**
* Convenience method to switch to pleura line annotation type
*/
switchUSPleuraBLineAnnotationToPleuraLine: () => {
actions.switchUSPleuraBLineAnnotation({
annotationType: UltrasoundPleuraBLineTool.USPleuraBLineAnnotationType.PLEURA,
});
},
/**
* Convenience method to switch to B-line annotation type
*/
switchUSPleuraBLineAnnotationToBLine: () => {
actions.switchUSPleuraBLineAnnotation({
annotationType: UltrasoundPleuraBLineTool.USPleuraBLineAnnotationType.BLINE,
});
},
/**
* Deletes the last annotation of the specified type
* @param options - Object containing the annotationType to delete
*/
deleteLastUSPleuraBLineAnnotation: ({ annotationType }) => {
const activeViewportId = viewportGridService.getActiveViewportId();
const toolGroup = toolGroupService.getToolGroupForViewport(activeViewportId);
if (!toolGroup) {
return;
}
const usAnnotation = toolGroup.getToolInstance(UltrasoundPleuraBLineTool.toolName);
if (usAnnotation) {
const viewport = cornerstoneViewportService.getCornerstoneViewport(activeViewportId);
usAnnotation.deleteLastAnnotationType(viewport.element, annotationType);
viewport.render();
}
},
/**
* Convenience method to delete the last pleura line annotation
*/
deleteLastPleuraAnnotation: () => {
actions.deleteLastUSPleuraBLineAnnotation({
annotationType: UltrasoundPleuraBLineTool.USPleuraBLineAnnotationType.PLEURA,
});
},
/**
* Convenience method to delete the last B-line annotation
*/
deleteLastBLineAnnotation: () => {
actions.deleteLastUSPleuraBLineAnnotation({
annotationType: UltrasoundPleuraBLineTool.USPleuraBLineAnnotationType.BLINE,
});
},
/**
* Toggles a boolean attribute of the ultrasound annotation tool
* @param options - Object containing the attribute name to toggle
*/
toggleUSToolAttribute: ({ attribute }) => {
const activeViewportId = viewportGridService.getActiveViewportId();
const toolGroup = toolGroupService.getToolGroupForViewport(activeViewportId);
if (!toolGroup) {
return;
}
const configuration = toolGroup.getToolConfiguration(UltrasoundPleuraBLineTool.toolName);
if (!configuration) {
return;
}
toolGroup.setToolConfiguration(UltrasoundPleuraBLineTool.toolName, {
[attribute]: !configuration[attribute],
});
const viewport = cornerstoneViewportService.getCornerstoneViewport(activeViewportId);
viewport.render();
},
/**
* Sets a specific attribute of the ultrasound annotation tool to a given value
* @param options - Object containing the attribute name and value to set
*/
setUSToolAttribute: ({ attribute, value }) => {
const activeViewportId = viewportGridService.getActiveViewportId();
const toolGroup = toolGroupService.getToolGroupForViewport(activeViewportId);
if (!toolGroup) {
return;
}
const configuration = toolGroup.getToolConfiguration(UltrasoundPleuraBLineTool.toolName);
if (!configuration) {
return;
}
toolGroup.setToolConfiguration(UltrasoundPleuraBLineTool.toolName, {
[attribute]: value,
});
const viewport = cornerstoneViewportService.getCornerstoneViewport(activeViewportId);
viewport.render();
},
/**
* Toggles the display of fan annotations
*/
toggleDisplayFanAnnotation: () => {
actions.toggleUSToolAttribute({
attribute: 'showFanAnnotations',
});
},
/**
* Toggles the display of the depth guide
*/
toggleDepthGuide: () => {
actions.toggleUSToolAttribute({
attribute: 'drawDepthGuide',
});
},
/**
* Sets the depth guide display state
* @param options - Object containing the boolean value to set
*/
setDepthGuide: ({ value }) => {
actions.setUSToolAttribute({
attribute: 'drawDepthGuide',
value,
});
},
/**
* Sets the fan annotation display state
* @param options - Object containing the boolean value to set
*/
setDisplayFanAnnotation: ({ value }) => {
actions.setUSToolAttribute({
attribute: 'showFanAnnotations',
value,
});
},
/**
* Sets whether to show the pleura percentage in the viewport overlay
* @param options - Object containing the boolean value to set
*/
setShowPleuraPercentage: ({ value }) => {
setShowPercentage(value);
// Trigger ANNOTATION_MODIFIED event to update the overlay
triggerEvent(eventTarget, csToolsEnums.Events.ANNOTATION_MODIFIED, {
annotation: {
metadata: {
toolName: UltrasoundPleuraBLineTool.toolName,
},
},
});
},
/**
* Generates a JSON representation of the ultrasound annotations
* @param labels - Array of annotation labels
* @param imageIds - Array of image IDs to include in the JSON
* @returns A JSON object containing the annotations data or undefined if generation fails
*/
generateUSPleuraBLineAnnotationsJSON: (labels: string[] = [], imageIds: string[] = []) => {
const activeViewportId = viewportGridService.getActiveViewportId();
const viewport = cornerstoneViewportService.getCornerstoneViewport(activeViewportId);
if (!viewport) {
return;
}
const { imageData } = viewport.getImageData() || {};
if (!imageData) {
return;
}
const toolGroup = toolGroupService.getToolGroupForViewport(activeViewportId);
if (!toolGroup) {
return;
}
const usAnnotation = toolGroup.getToolInstance(UltrasoundPleuraBLineTool.toolName);
if (usAnnotation) {
const configuration = toolGroup.getToolConfiguration(UltrasoundPleuraBLineTool.toolName);
const imageId = viewport.getCurrentImageId();
const filterImageIds = (imageId: string) => {
if (imageIds.length === 0) {
return true;
} else {
return imageIds.includes(imageId);
}
};
const annotations = UltrasoundPleuraBLineTool.filterAnnotations(
viewport.element,
filterImageIds
);
const frame_annotations = {};
const viewportImageIds = viewport.getImageIds();
annotations.forEach(annotation => {
const imageId = annotation.metadata.referencedImageId;
const { annotationType } = annotation.data;
const [point1, point2] = annotation.data.handles.points;
const p1 = transformWorldToIndex(imageData, point1);
const p2 = transformWorldToIndex(imageData, point2);
const imageIdIndex = viewportImageIds.indexOf(imageId);
if (frame_annotations[imageIdIndex] === undefined) {
frame_annotations[imageIdIndex] = {
pleura_lines: [],
b_lines: [],
};
}
if (annotationType === UltrasoundPleuraBLineTool.USPleuraBLineAnnotationType.PLEURA) {
frame_annotations[imageIdIndex].pleura_lines.push([
[p1[0], p1[1], 0],
[p2[0], p2[1], 0],
]);
} else if (
annotationType === UltrasoundPleuraBLineTool.USPleuraBLineAnnotationType.BLINE
) {
frame_annotations[imageIdIndex].b_lines.push([
[p1[0], p1[1], 0],
[p2[0], p2[1], 0],
]);
}
});
const instance = getInstanceByImageId(servicesManager.services, imageId);
const json = {
SOPInstanceUID: instance.SOPInstanceUID,
GrayscaleConversion: false,
mask_type: 'fan',
angle1: configuration.startAngle,
angle2: configuration.endAngle,
center_rows_px: configuration.center[0],
center_cols_px: configuration.center[1],
radius1: configuration.innerRadius,
radius2: configuration.outerRadius,
image_size_rows: instance.rows,
image_size_cols: instance.columns,
AnnotationLabels: labels,
frame_annotations,
};
return json;
}
},
/**
* Downloads the ultrasound annotations as a JSON file
* @param options - Object containing labels and imageIds arrays
*/
downloadUSPleuraBLineAnnotationsJSON({ labels = [], imageIds = [] }) {
const json = actions.generateUSPleuraBLineAnnotationsJSON(labels, imageIds);
if (!json) {
return;
}
// Convert JSON object to a string
const jsonString = JSON.stringify(json, null, 2);
// Create a blob with the JSON data
const blob = new Blob([jsonString], { type: 'application/json' });
// Create a URL for the blob
const url = URL.createObjectURL(blob);
// Create an anchor element
const a = document.createElement('a');
a.href = url;
a.download = `ultrasound_annotations_${new Date().toISOString().slice(0, 10)}.json`;
// Append to the document, click to download, and remove
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
// Clean up by revoking the URL
URL.revokeObjectURL(url);
},
};
const definitions = {
switchUSAnnotation: {
commandFn: actions.switchUSPleuraBLineAnnotation,
},
deleteLastAnnotation: {
commandFn: actions.deleteLastUSPleuraBLineAnnotation,
},
toggleDepthGuide: {
commandFn: actions.toggleDepthGuide,
},
setDepthGuide: {
commandFn: actions.setDepthGuide,
},
setShowPleuraPercentage: {
commandFn: actions.setShowPleuraPercentage,
},
toggleUSToolAttribute: {
commandFn: actions.toggleUSToolAttribute,
},
setUSToolAttribute: {
commandFn: actions.setUSToolAttribute,
},
toggleDisplayFanAnnotation: {
commandFn: actions.toggleDisplayFanAnnotation,
},
setDisplayFanAnnotation: {
commandFn: actions.setDisplayFanAnnotation,
},
generateJSON: {
commandFn: actions.generateUSPleuraBLineAnnotationsJSON,
},
downloadJSON: {
commandFn: actions.downloadUSPleuraBLineAnnotationsJSON,
},
switchUSAnnotationToPleuraLine: {
commandFn: actions.switchUSPleuraBLineAnnotationToPleuraLine,
},
switchUSAnnotationToBLine: {
commandFn: actions.switchUSPleuraBLineAnnotationToBLine,
},
deleteLastPleuraAnnotation: {
commandFn: actions.deleteLastPleuraAnnotation,
},
deleteLastBLineAnnotation: {
commandFn: actions.deleteLastBLineAnnotation,
},
};
return {
actions,
definitions,
defaultContext: 'CORNERSTONE',
};
}
export default commandsModule;