From f9a498ac18be171e6e2f89822c88e59f06ce43f2 Mon Sep 17 00:00:00 2001 From: m00n620 <50647994+m00n620@users.noreply.github.com> Date: Tue, 26 Sep 2023 23:14:30 -0400 Subject: [PATCH] feat(referenceLines): showFullDimension option to ReferenceLines tool (#784) * showFullDimension option to ReferenceLines tool * refactor * update api --- common/reviews/api/tools.api.md | 4 + .../tools/examples/referenceLines/index.ts | 22 ++- .../tools/src/tools/ReferenceLinesTool.ts | 134 +++++++++++++++++- .../src/tools/base/AnnotationDisplayTool.ts | 4 + 4 files changed, 154 insertions(+), 10 deletions(-) diff --git a/common/reviews/api/tools.api.md b/common/reviews/api/tools.api.md index c4ca45c2ab..29ab61328b 100644 --- a/common/reviews/api/tools.api.md +++ b/common/reviews/api/tools.api.md @@ -4440,10 +4440,14 @@ class ReferenceLines extends AnnotationDisplayTool { // (undocumented) _init: () => void; // (undocumented) + intersectInfiniteLines(line1Start: Types_2.Point2, line1End: Types_2.Point2, line2Start: Types_2.Point2, line2End: Types_2.Point2): number[]; + // (undocumented) isDrawing: boolean; // (undocumented) isHandleOutsideImage: boolean; // (undocumented) + isInBound(point: number[], dimensions: Types_2.Point3): boolean; + // (undocumented) isParallel(vec1: Types_2.Point3, vec2: Types_2.Point3): boolean; // (undocumented) isPerpendicular: (vec1: Types_2.Point3, vec2: Types_2.Point3) => boolean; diff --git a/packages/tools/examples/referenceLines/index.ts b/packages/tools/examples/referenceLines/index.ts index 178e1f351e..c91f6d8820 100644 --- a/packages/tools/examples/referenceLines/index.ts +++ b/packages/tools/examples/referenceLines/index.ts @@ -10,6 +10,7 @@ import { createImageIdsAndCacheMetaData, setTitleAndDescription, addDropdownToToolbar, + addCheckboxToToolbar, } from '../../../../utils/demo/helpers'; import * as cornerstoneTools from '@cornerstonejs/tools'; @@ -138,17 +139,24 @@ addDropdownToToolbar({ const element = elements[index]; element.style.border = '5px solid yellow'; - toolGroup.setToolConfiguration( - ReferenceLinesTool.toolName, - { - sourceViewportId: selectedViewportId, - }, - true // overwrite - ); + toolGroup.setToolConfiguration(ReferenceLinesTool.toolName, { + sourceViewportId: selectedViewportId, + }); toolGroup.setToolEnabled(ReferenceLinesTool.toolName); }, }); + +addCheckboxToToolbar({ + id: 'show-full-dimension-checkbox', + title: 'Show Full Dimension', + checked: false, + onChange: (showFullDimension) => { + toolGroup.setToolConfiguration(ReferenceLinesTool.toolName, { + showFullDimension, + }); + }, +}); /** * Runs the demo */ diff --git a/packages/tools/src/tools/ReferenceLinesTool.ts b/packages/tools/src/tools/ReferenceLinesTool.ts index f31ccc384b..baf2ab0498 100644 --- a/packages/tools/src/tools/ReferenceLinesTool.ts +++ b/packages/tools/src/tools/ReferenceLinesTool.ts @@ -11,7 +11,6 @@ import { addAnnotation } from '../stateManagement/annotation/annotationState'; import { drawLine as drawLineSvg } from '../drawingSvg'; import { filterViewportsWithToolEnabled } from '../utilities/viewportFilters'; import triggerAnnotationRenderForViewportIds from '../utilities/triggerAnnotationRenderForViewportIds'; - import { PublicToolProps, ToolProps, SVGDrawingHelper } from '../types'; import { ReferenceLineAnnotation } from '../types/ToolSpecificAnnotationTypes'; import { StyleSpecifier } from '../types/AnnotationStyle'; @@ -43,6 +42,7 @@ class ReferenceLines extends AnnotationDisplayTool { supportedInteractionTypes: ['Mouse', 'Touch'], configuration: { sourceViewportId: '', + showFullDimension: false, }, } ) { @@ -177,7 +177,7 @@ class ReferenceLines extends AnnotationDisplayTool { const bottomLeft = annotation.data.handles.points[2]; const bottomRight = annotation.data.handles.points[3]; - const { focalPoint, viewPlaneNormal } = targetViewport.getCamera(); + const { focalPoint, viewPlaneNormal, viewUp } = targetViewport.getCamera(); const { viewPlaneNormal: sourceViewPlaneNormal } = sourceViewport.getCamera(); @@ -239,10 +239,21 @@ class ReferenceLines extends AnnotationDisplayTool { const color = this.getStyle('color', styleSpecifier, annotation); const shadow = this.getStyle('shadow', styleSpecifier, annotation); - const canvasCoordinates = [lineStartWorld, lineEndWorld].map((world) => + let canvasCoordinates = [lineStartWorld, lineEndWorld].map((world) => targetViewport.worldToCanvas(world) ); + if (this.configuration.showFullDimension) { + canvasCoordinates = this.handleFullDimension( + targetViewport, + lineStartWorld, + viewPlaneNormal, + viewUp, + lineEndWorld, + canvasCoordinates + ); + } + const dataId = `${annotationUID}-line`; const lineUID = '1'; drawLineSvg( @@ -270,9 +281,126 @@ class ReferenceLines extends AnnotationDisplayTool { return Math.abs(dot) < EPSILON; }; + private handleFullDimension( + targetViewport: Types.IStackViewport | Types.IVolumeViewport, + lineStartWorld: Types.Point3, + viewPlaneNormal: Types.Point3, + viewUp: Types.Point3, + lineEndWorld: Types.Point3, + canvasCoordinates: Types.Point2[] + ) { + const renderingEngine = targetViewport.getRenderingEngine(); + const targetId = this.getTargetId(targetViewport); + const targetImage = this.getTargetIdImage(targetId, renderingEngine); + + const referencedImageId = this.getReferencedImageId( + targetViewport, + lineStartWorld, + viewPlaneNormal, + viewUp + ); + + if (referencedImageId && targetImage) { + try { + const { imageData, dimensions } = targetImage; + + // Calculate bound image coordinates + const [ + topLeftImageCoord, + topRightImageCoord, + bottomRightImageCoord, + bottomLeftImageCoord, + ] = [ + imageData.indexToWorld([0, 0, 0]) as Types.Point3, + imageData.indexToWorld([dimensions[0] - 1, 0, 0]) as Types.Point3, + imageData.indexToWorld([ + dimensions[0] - 1, + dimensions[1] - 1, + 0, + ]) as Types.Point3, + imageData.indexToWorld([0, dimensions[1] - 1, 0]) as Types.Point3, + ].map((world) => csUtils.worldToImageCoords(referencedImageId, world)); + + // Calculate line start and end image coordinates + const [lineStartImageCoord, lineEndImageCoord] = [ + lineStartWorld, + lineEndWorld, + ].map((world) => csUtils.worldToImageCoords(referencedImageId, world)); + + // Calculate intersection points between line and image bounds + canvasCoordinates = [ + [topLeftImageCoord, topRightImageCoord], + [topRightImageCoord, bottomRightImageCoord], + [bottomLeftImageCoord, bottomRightImageCoord], + [topLeftImageCoord, bottomLeftImageCoord], + ] + .map(([start, end]) => + this.intersectInfiniteLines( + start, + end, + lineStartImageCoord, + lineEndImageCoord + ) + ) + .filter((point) => point && this.isInBound(point, dimensions)) + .map((point) => { + const world = csUtils.imageToWorldCoords( + referencedImageId, + point as Types.Point2 + ); + return targetViewport.worldToCanvas(world); + }); + } catch (err) { + console.log(err); + } + } + return canvasCoordinates; + } + + // get the intersection point between two infinite lines, not line segments + intersectInfiniteLines( + line1Start: Types.Point2, + line1End: Types.Point2, + line2Start: Types.Point2, + line2End: Types.Point2 + ) { + const [x1, y1] = line1Start; + const [x2, y2] = line1End; + const [x3, y3] = line2Start; + const [x4, y4] = line2End; + + // Compute a1, b1, c1, where line joining points 1 and 2 is "a1 x + b1 y + c1 = 0" + const a1 = y2 - y1; + const b1 = x1 - x2; + const c1 = x2 * y1 - x1 * y2; + + // Compute a2, b2, c2 + const a2 = y4 - y3; + const b2 = x3 - x4; + const c2 = x4 * y3 - x3 * y4; + + if (Math.abs(a1 * b2 - a2 * b1) < EPSILON) { + return; + } + + const x = (b1 * c2 - b2 * c1) / (a1 * b2 - a2 * b1); + const y = (a2 * c1 - a1 * c2) / (a1 * b2 - a2 * b1); + + return [x, y]; + } + isParallel(vec1: Types.Point3, vec2: Types.Point3): boolean { return Math.abs(vec3.dot(vec1, vec2)) > 1 - EPSILON; } + + isInBound(point: number[], dimensions: Types.Point3): boolean { + return ( + point[0] >= 0 && + point[0] <= dimensions[0] && + point[1] >= 0 && + point[1] <= dimensions[1] + ); + } } ReferenceLines.toolName = 'ReferenceLines'; diff --git a/packages/tools/src/tools/base/AnnotationDisplayTool.ts b/packages/tools/src/tools/base/AnnotationDisplayTool.ts index 73f4aa0c7c..6799d7ca50 100644 --- a/packages/tools/src/tools/base/AnnotationDisplayTool.ts +++ b/packages/tools/src/tools/base/AnnotationDisplayTool.ts @@ -102,6 +102,10 @@ abstract class AnnotationDisplayTool extends BaseTool { // for this specific tool toolSpecificAnnotations.forEach((annotation) => { + if (!annotation.metadata?.referencedImageId) { + return; + } + // if the annotation is drawn on the same imageId const referencedImageURI = utilities.imageIdToURI( annotation.metadata.referencedImageId