import { UltrasoundPleuraBLineTool, Enums as csToolsEnums } from '@cornerstonejs/tools'; import { Types as OhifTypes, utils } from '@ohif/core'; import { eventTarget, triggerEvent, utilities } from '@cornerstonejs/core'; import getInstanceByImageId from './getInstanceByImageId'; import { setShowPercentage } from './PleuraBlinePercentage'; const { downloadBlob } = utils; 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, }: 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' }); downloadBlob(blob, { filename: `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;