diff --git a/extensions/tmtv/src/commandsModule.ts b/extensions/tmtv/src/commandsModule.ts index 465b4b7a2..8ea6ebd1e 100644 --- a/extensions/tmtv/src/commandsModule.ts +++ b/extensions/tmtv/src/commandsModule.ts @@ -12,9 +12,10 @@ import dicomRTAnnotationExport from './utils/dicomRTAnnotationExport/RTStructure import { getWebWorkerManager } from '@cornerstonejs/core'; const metadataProvider = classes.MetadataProvider; -const RECTANGLE_ROI_THRESHOLD_MANUAL_TOOL_IDS = [ +const ROI_THRESHOLD_MANUAL_TOOL_IDS = [ 'RectangleROIStartEndThreshold', 'RectangleROIThreshold', + 'CircleROIStartEndThreshold' ]; const LABELMAP = csTools.Enums.SegmentationRepresentations.Labelmap; @@ -204,7 +205,7 @@ const commandsModule = ({ servicesManager, commandsManager, extensionManager }: const { referencedVolumeId } = cs.cache.getVolume(segVolumeId); const annotationUIDs = _getAnnotationsSelectedByToolNames( - RECTANGLE_ROI_THRESHOLD_MANUAL_TOOL_IDS + ROI_THRESHOLD_MANUAL_TOOL_IDS ); if (annotationUIDs.length === 0) { @@ -291,7 +292,7 @@ const commandsModule = ({ servicesManager, commandsManager, extensionManager }: const referencedVolume = cs.cache.getVolume(referencedVolumeId); const annotationUIDs = _getAnnotationsSelectedByToolNames( - RECTANGLE_ROI_THRESHOLD_MANUAL_TOOL_IDS + ROI_THRESHOLD_MANUAL_TOOL_IDS ); const annotations = annotationUIDs.map(annotationUID => @@ -469,38 +470,18 @@ const commandsModule = ({ servicesManager, commandsManager, extensionManager }: }, setStartSliceForROIThresholdTool: () => { const { viewport } = _getActiveViewportsEnabledElement(); - const { focalPoint, viewPlaneNormal } = viewport.getCamera(); + const { focalPoint } = viewport.getCamera(); const selectedAnnotationUIDs = _getAnnotationsSelectedByToolNames( - RECTANGLE_ROI_THRESHOLD_MANUAL_TOOL_IDS + ROI_THRESHOLD_MANUAL_TOOL_IDS ); const annotationUID = selectedAnnotationUIDs[0]; const annotation = csTools.annotation.state.getAnnotation(annotationUID); - const { handles } = annotation.data; - const { points } = handles; - - // get the current slice Index - const sliceIndex = viewport.getCurrentImageIdIndex(); - annotation.data.startSlice = sliceIndex; - - // distance between camera focal point and each point on the rectangle - const newPoints = points.map(point => { - const distance = vec3.create(); - vec3.subtract(distance, focalPoint, point); - // distance in the direction of the viewPlaneNormal - const distanceInViewPlane = vec3.dot(distance, viewPlaneNormal); - // new point is current point minus distanceInViewPlane - const newPoint = vec3.create(); - vec3.scaleAndAdd(newPoint, point, viewPlaneNormal, distanceInViewPlane); - - return newPoint; - // - }); - - handles.points = newPoints; + // set the current focalpoint + annotation.data.startCoordinate = focalPoint; // IMPORTANT: invalidate the toolData for the cached stat to get updated // and re-calculate the projection points annotation.invalidated = true; @@ -510,16 +491,16 @@ const commandsModule = ({ servicesManager, commandsManager, extensionManager }: const { viewport } = _getActiveViewportsEnabledElement(); const selectedAnnotationUIDs = _getAnnotationsSelectedByToolNames( - RECTANGLE_ROI_THRESHOLD_MANUAL_TOOL_IDS + ROI_THRESHOLD_MANUAL_TOOL_IDS ); const annotationUID = selectedAnnotationUIDs[0]; const annotation = csTools.annotation.state.getAnnotation(annotationUID); - // get the current slice Index - const sliceIndex = viewport.getCurrentImageIdIndex(); - annotation.data.endSlice = sliceIndex; + // get the current focalpoint + const focalPointToEnd = viewport.getCamera().focalPoint; + annotation.data.endCoordinate = focalPointToEnd; // IMPORTANT: invalidate the toolData for the cached stat to get updated // and re-calculate the projection points @@ -535,7 +516,7 @@ const commandsModule = ({ servicesManager, commandsManager, extensionManager }: Object.keys(stateManager.annotations).forEach(frameOfReferenceUID => { const forAnnotations = stateManager.annotations[frameOfReferenceUID]; - const ROIAnnotations = RECTANGLE_ROI_THRESHOLD_MANUAL_TOOL_IDS.reduce( + const ROIAnnotations = ROI_THRESHOLD_MANUAL_TOOL_IDS.reduce( (annotations, toolName) => [...annotations, ...(forAnnotations[toolName] ?? [])], [] ); diff --git a/extensions/tmtv/src/init.js b/extensions/tmtv/src/init.js index eaa461533..57ca26cd5 100644 --- a/extensions/tmtv/src/init.js +++ b/extensions/tmtv/src/init.js @@ -1,4 +1,4 @@ -import { addTool, RectangleROIStartEndThresholdTool } from '@cornerstonejs/tools'; +import { addTool, RectangleROIStartEndThresholdTool, CircleROIStartEndThresholdTool } from '@cornerstonejs/tools'; import measurementServiceMappingsFactory from './utils/measurementServiceMappings/measurementServiceMappingsFactory'; @@ -15,8 +15,9 @@ export default function init({ servicesManager }) { servicesManager.services; addTool(RectangleROIStartEndThresholdTool); + addTool(CircleROIStartEndThresholdTool); - const { RectangleROIStartEndThreshold } = measurementServiceMappingsFactory( + const { RectangleROIStartEndThreshold, CircleROIStartEndThreshold } = measurementServiceMappingsFactory( measurementService, displaySetService, cornerstoneViewportService @@ -34,4 +35,12 @@ export default function init({ servicesManager }) { RectangleROIStartEndThreshold.toAnnotation, RectangleROIStartEndThreshold.toMeasurement ); + + measurementService.addMapping( + csTools3DVer1MeasurementSource, + 'CircleROIStartEndThreshold', + CircleROIStartEndThreshold.matchingCriteria, + CircleROIStartEndThreshold.toAnnotation, + CircleROIStartEndThreshold.toMeasurement + ); } diff --git a/extensions/tmtv/src/utils/measurementServiceMappings/CircleROIStartEndThreshold.js b/extensions/tmtv/src/utils/measurementServiceMappings/CircleROIStartEndThreshold.js new file mode 100644 index 000000000..bcd948589 --- /dev/null +++ b/extensions/tmtv/src/utils/measurementServiceMappings/CircleROIStartEndThreshold.js @@ -0,0 +1,67 @@ +import SUPPORTED_TOOLS from './constants/supportedTools'; +import getSOPInstanceAttributes from './utils/getSOPInstanceAttributes'; + +const CircleROIStartEndThreshold = { + toAnnotation: (measurement, definition) => { }, + + /** + * Maps cornerstone annotation event data to measurement service format. + * + * @param {Object} cornerstone Cornerstone event data + * @return {Measurement} Measurement instance + */ + toMeasurement: (csToolsEventDetail, displaySetService, cornerstoneViewportService) => { + const { annotation, viewportId } = csToolsEventDetail; + const { metadata, data, annotationUID } = annotation; + + if (!metadata || !data) { + console.warn('Length tool: Missing metadata or data'); + return null; + } + + const { toolName, referencedImageId, FrameOfReferenceUID } = metadata; + const validToolType = SUPPORTED_TOOLS.includes(toolName); + + if (!validToolType) { + throw new Error('Tool not supported'); + } + + const { SOPInstanceUID, SeriesInstanceUID, StudyInstanceUID } = getSOPInstanceAttributes( + referencedImageId, + cornerstoneViewportService, + viewportId + ); + + let displaySet; + + if (SOPInstanceUID) { + displaySet = displaySetService.getDisplaySetForSOPInstanceUID( + SOPInstanceUID, + SeriesInstanceUID + ); + } else { + displaySet = displaySetService.getDisplaySetsForSeries(SeriesInstanceUID); + } + + const { cachedStats } = data; + + return { + uid: annotationUID, + SOPInstanceUID, + FrameOfReferenceUID, + // points, + metadata, + referenceSeriesUID: SeriesInstanceUID, + referenceStudyUID: StudyInstanceUID, + toolName: metadata.toolName, + displaySetInstanceUID: displaySet.displaySetInstanceUID, + label: metadata.label, + // displayText: displayText, + data: data.cachedStats, + type: 'CircleROIStartEndThreshold', + // getReport, + }; + }, +}; + +export default CircleROIStartEndThreshold; diff --git a/extensions/tmtv/src/utils/measurementServiceMappings/measurementServiceMappingsFactory.js b/extensions/tmtv/src/utils/measurementServiceMappings/measurementServiceMappingsFactory.js index f99b956a1..af19d43d9 100644 --- a/extensions/tmtv/src/utils/measurementServiceMappings/measurementServiceMappingsFactory.js +++ b/extensions/tmtv/src/utils/measurementServiceMappings/measurementServiceMappingsFactory.js @@ -1,4 +1,5 @@ import RectangleROIStartEndThreshold from './RectangleROIStartEndThreshold'; +import CircleROIStartEndThreshold from './CircleROIStartEndThreshold' const measurementServiceMappingsFactory = ( measurementService, @@ -20,6 +21,20 @@ const measurementServiceMappingsFactory = ( }, ], }, + CircleROIStartEndThreshold: { + toAnnotation: CircleROIStartEndThreshold.toAnnotation, + toMeasurement: csToolsAnnotation => + CircleROIStartEndThreshold.toMeasurement( + csToolsAnnotation, + displaySetService, + cornerstoneViewportService + ), + matchingCriteria: [ + { + valueType: measurementService.VALUE_TYPES.ROI_THRESHOLD_MANUAL, + }, + ], + }, }; }; diff --git a/yarn.lock b/yarn.lock index 2202de09d..db6102232 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1492,6 +1492,7 @@ resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9" integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ== + "@cornerstonejs/adapters@^1.81.6", "@cornerstonejs/adapters@^1.82.0": version "1.82.0" resolved "https://registry.yarnpkg.com/@cornerstonejs/adapters/-/adapters-1.82.0.tgz#4c7ae6341ad7bcf5cf0213e5252fbac009da23b6" @@ -1530,10 +1531,10 @@ resolved "https://registry.yarnpkg.com/@cornerstonejs/codec-openjph/-/codec-openjph-2.4.5.tgz#8690b61a86fa53ef38a70eee9d665a79229517c0" integrity sha512-MZCUy8VG0VG5Nl1l58+g+kH3LujAzLYTfJqkwpWI2gjSrGXnP6lgwyy4GmPRZWVoS40/B1LDNALK905cNWm+sg== -"@cornerstonejs/core@^1.78.1": - version "1.78.1" - resolved "https://registry.yarnpkg.com/@cornerstonejs/core/-/core-1.78.1.tgz#e7db1afd573dd631e0ad4157706deb59844ca1b7" - integrity sha512-BfClVMEPM6IwU4TlAEh305jZOwK+xQBgf5jQywBea1DKf5KPYUFhnZcrxaWlZ44ldlpvsGBHZhDuTpdwjYszBg== +"@cornerstonejs/core@^1.78.1", "@cornerstonejs/core@^1.81.3": + version "1.81.3" + resolved "https://registry.yarnpkg.com/@cornerstonejs/core/-/core-1.81.3.tgz#76deaee4a2cce24db5695cb87857accea5645263" + integrity sha512-Uievs/wBpw20Xj4B+8UEjb/qe+cmSfz2oWfQzBANcWoqI5pXD77evXE0s6hORCMr4yFUmTIMbGnWwXT2fF2bYw== dependencies: "@kitware/vtk.js" "30.4.1" comlink "^4.4.1" @@ -1553,15 +1554,15 @@ lodash.clonedeep "4.5.0" "@cornerstonejs/dicom-image-loader@^1.77.6": - version "1.78.1" - resolved "https://registry.yarnpkg.com/@cornerstonejs/dicom-image-loader/-/dicom-image-loader-1.78.1.tgz#d8eef8f65e7069bef26a0f569a2d89bf7c48c9de" - integrity sha512-S37NlGD3FPrwE9qQcLVQA7pyzRlAIO+XBS7y2AgHWgXlHysRlJP+s4BWdLuwz7+Xh6cVh5g6mj/g+z51zep2kQ== + version "1.81.3" + resolved "https://registry.yarnpkg.com/@cornerstonejs/dicom-image-loader/-/dicom-image-loader-1.81.3.tgz#8d9165b351954999bbc566d811a9eedd2363d55c" + integrity sha512-QFqr0Jq9DWvoBB1rTwI6gL4fc3pawpnG6ebDSZpz+uPmG/QcGYaeGNT+Szrcu2V/q1ByOU/Jxn8ZG5Nuo+kD6A== dependencies: "@cornerstonejs/codec-charls" "^1.2.3" "@cornerstonejs/codec-libjpeg-turbo-8bit" "^1.2.2" "@cornerstonejs/codec-openjpeg" "^1.2.2" "@cornerstonejs/codec-openjph" "^2.4.5" - "@cornerstonejs/core" "^1.78.1" + "@cornerstonejs/core" "^1.81.3" dicom-parser "^1.8.9" pako "^2.0.4" uuid "^9.0.0" @@ -5476,10 +5477,10 @@ dependencies: "@types/node" "*" -"@types/node@*", "@types/node@^20.12.12": - version "20.14.5" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.14.5.tgz#fe35e3022ebe58b8f201580eb24e1fcfc0f2487d" - integrity sha512-aoRR+fJkZT2l0aGOJhuA8frnCSoNX6W7U2mpNq63+BxBIj5BQFt8rHy627kijCmm63ijdSdwvGgpUsU6MBsZZA== +"@types/node@*": + version "20.14.9" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.14.9.tgz#12e8e765ab27f8c421a1820c99f5f313a933b420" + integrity sha512-06OCtnTXtWOZBJlRApleWndH4JsRVs1pDCc8dLSQp+7PpUpX3ePdHyeNSFTeSe7FtKyQkrlPvHwJOW3SLd8Oyg== dependencies: undici-types "~5.26.4" @@ -5500,6 +5501,13 @@ dependencies: undici-types "~5.26.4" +"@types/node@^20.12.12": + version "20.14.5" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.14.5.tgz#fe35e3022ebe58b8f201580eb24e1fcfc0f2487d" + integrity sha512-aoRR+fJkZT2l0aGOJhuA8frnCSoNX6W7U2mpNq63+BxBIj5BQFt8rHy627kijCmm63ijdSdwvGgpUsU6MBsZZA== + dependencies: + undici-types "~5.26.4" + "@types/normalize-package-data@^2.4.0": version "2.4.4" resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz#56e2cc26c397c038fab0e3a917a12d5c5909e901" @@ -5727,9 +5735,9 @@ source-map "^0.6.0" "@types/webxr@^0.5.5": - version "0.5.16" - resolved "https://registry.yarnpkg.com/@types/webxr/-/webxr-0.5.16.tgz#28955aa2d1197d1ef0b9826ae0f7e68f7eca0601" - integrity sha512-0E0Cl84FECtzrB4qG19TNTqpunw0F1YF0QZZnFMF6pDw1kNKJtrlTKlVB34stGIsHbZsYQ7H0tNjPfZftkHHoA== + version "0.5.19" + resolved "https://registry.yarnpkg.com/@types/webxr/-/webxr-0.5.19.tgz#463a27bc06ff1c0a0c997e86b48bf24c5f50a4af" + integrity sha512-4hxA+NwohSgImdTSlPXEqDqqFktNgmTXQ05ff1uWam05tNGroCMp4G+4XVl6qWm1p7GQ/9oD41kAYsSssF6Mzw== "@types/ws@^8.2.2", "@types/ws@^8.5.5": version "8.5.10" @@ -12536,13 +12544,20 @@ is-ci@^2.0.0: dependencies: ci-info "^2.0.0" -is-core-module@^2.1.0, is-core-module@^2.13.0, is-core-module@^2.13.1, is-core-module@^2.5.0, is-core-module@^2.8.1: +is-core-module@^2.1.0, is-core-module@^2.13.1, is-core-module@^2.5.0, is-core-module@^2.8.1: version "2.13.1" resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.1.tgz#ad0d7532c6fea9da1ebdc82742d74525c6273384" integrity sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw== dependencies: hasown "^2.0.0" +is-core-module@^2.13.0: + version "2.14.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.14.0.tgz#43b8ef9f46a6a08888db67b1ffd4ec9e3dfd59d1" + integrity sha512-a5dFJih5ZLYlRtDc0dZWP7RiKr6xIKzmn/oAYCDvdLThadVgyJwlaoQPmRtMSpz+rk0OGAgIu+TcM9HUF0fk1A== + dependencies: + hasown "^2.0.2" + is-data-view@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-data-view/-/is-data-view-1.0.1.tgz#4b4d3a511b70f3dc26d42c03ca9ca515d847759f"