diff --git a/common/reviews/api/core.api.md b/common/reviews/api/core.api.md index 33c7669141..9044a4ca9e 100644 --- a/common/reviews/api/core.api.md +++ b/common/reviews/api/core.api.md @@ -295,6 +295,8 @@ interface CPUFallbackTransform { // @public (undocumented) type CPUFallbackViewport = { scale?: number; + parallelScale?: number; + focalPoint?: number[]; translation?: { x: number; y: number; @@ -654,6 +656,8 @@ interface ICamera { // (undocumented) position?: Point3; // (undocumented) + scale?: number; + // (undocumented) viewAngle?: number; // (undocumented) viewPlaneNormal?: Point3; @@ -1696,7 +1700,7 @@ export class StackViewport extends Viewport implements IStackViewport { // (undocumented) setImageIdIndex(imageIdIndex: number): Promise; // (undocumented) - setProperties({ voiRange, invert, interpolationType, rotation, }?: StackViewportProperties): void; + setProperties({ voiRange, invert, interpolationType, rotation, }?: StackViewportProperties, suppressEvents?: boolean): void; // (undocumented) setStack(imageIds: Array, currentImageIdIndex?: number): Promise; // (undocumented) @@ -1724,6 +1728,7 @@ type StackViewportProperties = { invert?: boolean; interpolationType?: InterpolationType; rotation?: number; + suppressEvents?: boolean; }; // @public (undocumented) diff --git a/common/reviews/api/streaming-image-volume-loader.api.md b/common/reviews/api/streaming-image-volume-loader.api.md index 07f7f94f35..f4a57ed062 100644 --- a/common/reviews/api/streaming-image-volume-loader.api.md +++ b/common/reviews/api/streaming-image-volume-loader.api.md @@ -263,6 +263,8 @@ interface CPUFallbackTransform { // @public (undocumented) type CPUFallbackViewport = { scale?: number; + parallelScale?: number; + focalPoint?: number[]; translation?: { x: number; y: number; @@ -503,6 +505,7 @@ interface ICamera { parallelProjection?: boolean; parallelScale?: number; position?: Point3; + scale?: number; viewAngle?: number; viewPlaneNormal?: Point3; viewUp?: Point3; @@ -1109,6 +1112,7 @@ type StackViewportProperties = { invert?: boolean; interpolationType?: InterpolationType; rotation?: number; + suppressEvents?: boolean; }; // @public (undocumented) diff --git a/common/reviews/api/tools.api.md b/common/reviews/api/tools.api.md index fa1d89ae23..c15f9f0478 100644 --- a/common/reviews/api/tools.api.md +++ b/common/reviews/api/tools.api.md @@ -140,7 +140,7 @@ export class AngleTool extends AnnotationTool { // (undocumented) _mouseUpCallback: (evt: EventTypes_2.MouseUpEventType | EventTypes_2.MouseClickEventType) => void; // (undocumented) - renderAnnotation: (enabledElement: Types_2.IEnabledElement, svgDrawingHelper: any) => void; + renderAnnotation: (enabledElement: Types_2.IEnabledElement, svgDrawingHelper: SVGDrawingHelper) => boolean; // (undocumented) _throttledCalculateCachedStats: any; // (undocumented) @@ -329,7 +329,7 @@ export abstract class AnnotationTool extends BaseTool { // (undocumented) onImageSpacingCalibrated: (evt: Types_2.EventTypes.ImageSpacingCalibratedEvent) => void; // (undocumented) - abstract renderAnnotation(enabledElement: Types_2.IEnabledElement, svgDrawingHelper: any): any; + abstract renderAnnotation(enabledElement: Types_2.IEnabledElement, svgDrawingHelper: SVGDrawingHelper): any; // (undocumented) static toolName: string; // (undocumented) @@ -391,7 +391,7 @@ export class ArrowAnnotateTool extends AnnotationTool { // (undocumented) _mouseUpCallback: (evt: EventTypes_2.MouseUpEventType | EventTypes_2.MouseClickEventType) => void; // (undocumented) - renderAnnotation: (enabledElement: Types_2.IEnabledElement, svgDrawingHelper: any) => void; + renderAnnotation: (enabledElement: Types_2.IEnabledElement, svgDrawingHelper: SVGDrawingHelper) => boolean; // (undocumented) _throttledCalculateCachedStats: any; // (undocumented) @@ -535,7 +535,7 @@ export class BidirectionalTool extends AnnotationTool { // (undocumented) preventHandleOutsideImage: boolean; // (undocumented) - renderAnnotation: (enabledElement: Types_2.IEnabledElement, svgDrawingHelper: any) => void; + renderAnnotation: (enabledElement: Types_2.IEnabledElement, svgDrawingHelper: SVGDrawingHelper) => boolean; // (undocumented) _throttledCalculateCachedStats: any; // (undocumented) @@ -564,7 +564,7 @@ export class BrushTool extends BaseTool { // (undocumented) preMouseDownCallback: (evt: EventTypes_2.MouseDownActivateEventType) => boolean; // (undocumented) - renderAnnotation(enabledElement: Types_2.IEnabledElement, svgDrawingHelper: any): void; + renderAnnotation(enabledElement: Types_2.IEnabledElement, svgDrawingHelper: SVGDrawingHelper): void; // (undocumented) static toolName: string; } @@ -647,7 +647,7 @@ export class CircleScissorsTool extends BaseTool { // (undocumented) preMouseDownCallback: (evt: EventTypes_2.MouseDownActivateEventType) => boolean; // (undocumented) - renderAnnotation: (enabledElement: Types_2.IEnabledElement, svgDrawingHelper: any) => void; + renderAnnotation: (enabledElement: Types_2.IEnabledElement, svgDrawingHelper: SVGDrawingHelper) => boolean; // (undocumented) static toolName: string; } @@ -911,6 +911,8 @@ interface CPUFallbackTransform { // @public (undocumented) type CPUFallbackViewport = { scale?: number; + parallelScale?: number; + focalPoint?: number[]; translation?: { x: number; y: number; @@ -1085,7 +1087,7 @@ export class CrosshairsTool extends AnnotationTool { // (undocumented) _pointNearTool(element: any, annotation: any, canvasCoords: any, proximity: any): boolean; // (undocumented) - renderAnnotation: (enabledElement: Types_2.IEnabledElement, svgDrawingHelper: any) => void; + renderAnnotation: (enabledElement: Types_2.IEnabledElement, svgDrawingHelper: SVGDrawingHelper) => boolean; // (undocumented) setSlabThickness(viewport: any, slabThickness: any): void; // (undocumented) @@ -1195,7 +1197,7 @@ export class DragProbeTool extends ProbeTool { // (undocumented) postMouseDownCallback: (evt: EventTypes_2.MouseDownActivateEventType) => ProbeAnnotation; // (undocumented) - renderAnnotation: (enabledElement: Types_2.IEnabledElement, svgDrawingHelper: any) => void; + renderAnnotation: (enabledElement: Types_2.IEnabledElement, svgDrawingHelper: SVGDrawingHelper) => boolean; // (undocumented) static toolName: string; // (undocumented) @@ -1206,16 +1208,16 @@ export class DragProbeTool extends ProbeTool { function draw(element: HTMLDivElement, fn: (svgDrawingElement: any) => any): void; // @public (undocumented) -function drawArrow(svgDrawingHelper: any, annotationUID: string, arrowUID: string, start: Types_2.Point2, end: Types_2.Point2, options?: {}): void; +function drawArrow(svgDrawingHelper: SVGDrawingHelper, annotationUID: string, arrowUID: string, start: Types_2.Point2, end: Types_2.Point2, options?: {}): void; // @public (undocumented) -function drawCircle(svgDrawingHelper: any, annotationUID: string, circleUID: string, center: Types_2.Point2, radius: number, options?: {}): void; +function drawCircle(svgDrawingHelper: SVGDrawingHelper, annotationUID: string, circleUID: string, center: Types_2.Point2, radius: number, options?: {}): void; // @public (undocumented) -function drawEllipse(svgDrawingHelper: any, annotationUID: string, ellipseUID: string, corner1: Types_2.Point2, corner2: Types_2.Point2, options?: {}): void; +function drawEllipse(svgDrawingHelper: SVGDrawingHelper, annotationUID: string, ellipseUID: string, corner1: Types_2.Point2, corner2: Types_2.Point2, options?: {}): void; // @public (undocumented) -function drawHandles(svgDrawingHelper: any, annotationUID: string, handleGroupUID: string, handlePoints: Array, options?: {}): void; +function drawHandles(svgDrawingHelper: SVGDrawingHelper, annotationUID: string, handleGroupUID: string, handlePoints: Array, options?: {}): void; declare namespace drawing { export { @@ -1240,13 +1242,13 @@ declare namespace drawing_2 { } // @public (undocumented) -function drawLine(svgDrawingHelper: any, annotationUID: string, lineUID: string, start: Types_2.Point2, end: Types_2.Point2, options?: {}): void; +function drawLine(svgDrawingHelper: SVGDrawingHelper, annotationUID: string, lineUID: string, start: Types_2.Point2, end: Types_2.Point2, options?: {}): void; // @public (undocumented) -function drawLinkedTextBox(svgDrawingHelper: Record, annotationUID: string, textBoxUID: string, textLines: Array, textBoxPosition: Types_2.Point2, annotationAnchorPoints: Array, textBox: unknown, options?: {}): SVGRect; +function drawLinkedTextBox(svgDrawingHelper: SVGDrawingHelper, annotationUID: string, textBoxUID: string, textLines: Array, textBoxPosition: Types_2.Point2, annotationAnchorPoints: Array, textBox: unknown, options?: {}): SVGRect; // @public (undocumented) -function drawPolyline(svgDrawingHelper: any, annotationUID: string, polylineUID: string, points: Types_2.Point2[], options: { +function drawPolyline(svgDrawingHelper: SVGDrawingHelper, annotationUID: string, polylineUID: string, points: Types_2.Point2[], options: { color?: string; width?: number; lineWidth?: number; @@ -1255,10 +1257,10 @@ function drawPolyline(svgDrawingHelper: any, annotationUID: string, polylineUID: }): void; // @public (undocumented) -function drawRect(svgDrawingHelper: any, annotationUID: string, rectangleUID: string, start: Types_2.Point2, end: Types_2.Point2, options?: {}): void; +function drawRect(svgDrawingHelper: SVGDrawingHelper, annotationUID: string, rectangleUID: string, start: Types_2.Point2, end: Types_2.Point2, options?: {}): void; // @public (undocumented) -function drawTextBox(svgDrawingHelper: Record, annotationUID: string, textUID: string, textLines: Array, position: Types_2.Point2, options?: {}): SVGRect; +function drawTextBox(svgDrawingHelper: SVGDrawingHelper, annotationUID: string, textUID: string, textLines: Array, position: Types_2.Point2, options?: {}): SVGRect; declare namespace elementCursor { export { @@ -1374,7 +1376,7 @@ export class EllipticalROITool extends AnnotationTool { // (undocumented) _pointInEllipseCanvas(ellipse: any, location: Types_2.Point2): boolean; // (undocumented) - renderAnnotation: (enabledElement: Types_2.IEnabledElement, svgDrawingHelper: any) => void; + renderAnnotation: (enabledElement: Types_2.IEnabledElement, svgDrawingHelper: SVGDrawingHelper) => boolean; // (undocumented) _throttledCalculateCachedStats: any; // (undocumented) @@ -1817,6 +1819,7 @@ interface ICamera { parallelProjection?: boolean; parallelScale?: number; position?: Point3; + scale?: number; viewAngle?: number; viewPlaneNormal?: Point3; viewUp?: Point3; @@ -2554,7 +2557,7 @@ export class LengthTool extends AnnotationTool { // (undocumented) _mouseUpCallback: (evt: EventTypes_2.MouseUpEventType | EventTypes_2.MouseClickEventType) => void; // (undocumented) - renderAnnotation: (enabledElement: Types_2.IEnabledElement, svgDrawingHelper: any) => void; + renderAnnotation: (enabledElement: Types_2.IEnabledElement, svgDrawingHelper: SVGDrawingHelper) => boolean; // (undocumented) _throttledCalculateCachedStats: any; // (undocumented) @@ -2904,7 +2907,7 @@ export class PlanarFreehandROITool extends AnnotationTool { // (undocumented) mouseDragCallback: any; // (undocumented) - renderAnnotation: (enabledElement: Types_2.IEnabledElement, svgDrawingHelper: any) => void; + renderAnnotation: (enabledElement: Types_2.IEnabledElement, svgDrawingHelper: SVGDrawingHelper) => boolean; // (undocumented) _throttledCalculateCachedStats: any; // (undocumented) @@ -3044,7 +3047,7 @@ export class ProbeTool extends AnnotationTool { // (undocumented) _mouseUpCallback: (evt: EventTypes_2.MouseUpEventType | EventTypes_2.MouseClickEventType) => void; // (undocumented) - renderAnnotation: (enabledElement: Types_2.IEnabledElement, svgDrawingHelper: any) => void; + renderAnnotation: (enabledElement: Types_2.IEnabledElement, svgDrawingHelper: SVGDrawingHelper) => boolean; // (undocumented) static toolName: string; // (undocumented) @@ -3192,7 +3195,7 @@ export class RectangleROIStartEndThresholdTool extends RectangleROITool { // (undocumented) isHandleOutsideImage: boolean; // (undocumented) - renderAnnotation: (enabledElement: Types_2.IEnabledElement, svgDrawingHelper: any) => void; + renderAnnotation: (enabledElement: Types_2.IEnabledElement, svgDrawingHelper: SVGDrawingHelper) => boolean; // (undocumented) _throttledCalculateCachedStats: any; // (undocumented) @@ -3267,7 +3270,7 @@ export class RectangleROIThresholdTool extends RectangleROITool { // (undocumented) isHandleOutsideImage: boolean; // (undocumented) - renderAnnotation: (enabledElement: Types_2.IEnabledElement, svgDrawingHelper: any) => void; + renderAnnotation: (enabledElement: Types_2.IEnabledElement, svgDrawingHelper: SVGDrawingHelper) => boolean; // (undocumented) _throttledCalculateCachedStats: any; // (undocumented) @@ -3327,7 +3330,7 @@ export class RectangleROITool extends AnnotationTool { // (undocumented) _mouseUpCallback: (evt: EventTypes_2.MouseUpEventType | EventTypes_2.MouseClickEventType) => void; // (undocumented) - renderAnnotation: (enabledElement: Types_2.IEnabledElement, svgDrawingHelper: any) => void; + renderAnnotation: (enabledElement: Types_2.IEnabledElement, svgDrawingHelper: SVGDrawingHelper) => boolean; // (undocumented) _throttledCalculateCachedStats: any; // (undocumented) @@ -3374,7 +3377,7 @@ export class RectangleScissorsTool extends BaseTool { // (undocumented) preMouseDownCallback: (evt: EventTypes_2.MouseDownActivateEventType) => boolean; // (undocumented) - renderAnnotation: (enabledElement: Types_2.IEnabledElement, svgDrawingHelper: any) => void; + renderAnnotation: (enabledElement: Types_2.IEnabledElement, svgDrawingHelper: SVGDrawingHelper) => boolean; // (undocumented) _throttledCalculateCachedStats: any; // (undocumented) @@ -3696,7 +3699,7 @@ export class SphereScissorsTool extends BaseTool { // (undocumented) preMouseDownCallback: (evt: EventTypes_2.MouseDownActivateEventType) => true; // (undocumented) - renderAnnotation: (enabledElement: Types_2.IEnabledElement, svgDrawingHelper: any) => void; + renderAnnotation: (enabledElement: Types_2.IEnabledElement, svgDrawingHelper: SVGDrawingHelper) => boolean; // (undocumented) static toolName: string; } @@ -3776,6 +3779,7 @@ type StackViewportProperties = { invert?: boolean; interpolationType?: InterpolationType; rotation?: number; + suppressEvents?: boolean; }; // @public (undocumented) @@ -3855,6 +3859,16 @@ type SVGCursorDescriptor = { mousePointerGroupString: string; }; +// @public (undocumented) +type SVGDrawingHelper = { + svgLayerElement: HTMLDivElement; + svgNodeCacheForCanvas: Record; + getSvgNode: (cacheKey: string) => SVGGElement | undefined; + appendNode: (svgNode: SVGElement, cacheKey: string) => void; + setNodeTouched: (cacheKey: string) => void; + clearUntouched: () => void; +}; + // @public (undocumented) class SVGMouseCursor extends ImageMouseCursor { constructor(url: string, x?: number, y?: number, name?: string | undefined, fallback?: MouseCursor | undefined); @@ -4144,7 +4158,8 @@ declare namespace Types { SVGPoint_2 as SVGPoint, ScrollOptions_2 as ScrollOptions, CINETypes, - BoundsIJK + BoundsIJK, + SVGDrawingHelper } } export { Types } @@ -4366,21 +4381,12 @@ export class ZoomTool extends BaseTool { // (undocumented) _dragCallback(evt: EventTypes_2.MouseDragEventType): void; // (undocumented) - _dragParallelProjection: (evt: EventTypes_2.MouseDragEventType, camera: Types_2.ICamera) => void; - // (undocumented) - _dragPerspectiveProjection: (evt: any, camera: any) => void; + _dragParallelProjection: (evt: EventTypes_2.MouseDragEventType, viewport: Types_2.IStackViewport | Types_2.IVolumeViewport, camera: Types_2.ICamera) => void; // (undocumented) - _getCappedParallelScale: (viewport: any, parallelScale: any) => { - parallelScale: any; - thresholdExceeded: boolean; - }; + _dragPerspectiveProjection: (evt: any, viewport: any, camera: any) => void; // (undocumented) initialMousePosWorld: Types_2.Point3; // (undocumented) - maxZoomScale: number; - // (undocumented) - minZoomScale: number; - // (undocumented) mouseDragCallback: () => void; // (undocumented) preMouseDownCallback: (evt: EventTypes_2.MouseDownActivateEventType) => boolean; diff --git a/packages/core/src/RenderingEngine/StackViewport.ts b/packages/core/src/RenderingEngine/StackViewport.ts index bdf3d4657b..9d0c4f734a 100644 --- a/packages/core/src/RenderingEngine/StackViewport.ts +++ b/packages/core/src/RenderingEngine/StackViewport.ts @@ -62,6 +62,7 @@ import { } from '../types/EventTypes'; import getScalingParameters from '../utilities/getScalingParameters'; import cache from '../cache'; +import correctShift from './helpers/cpuFallback/rendering/correctShift'; const EPSILON = 1; // Slice Thickness @@ -524,16 +525,19 @@ class StackViewport extends Viewport implements IStackViewport { * @param interpolationType - Changes the interpolation type (1:linear, 0: nearest) * @param rotation - image rotation in degrees */ - public setProperties({ - voiRange, - invert, - interpolationType, - rotation, - }: StackViewportProperties = {}): void { + public setProperties( + { + voiRange, + invert, + interpolationType, + rotation, + }: StackViewportProperties = {}, + suppressEvents = false + ): void { // if voi is not applied for the first time, run the setVOI function // which will apply the default voi if (typeof voiRange !== 'undefined' || !this.voiApplied) { - this.setVOI(voiRange); + this.setVOI(voiRange, suppressEvents); } if (typeof invert !== 'undefined') { @@ -620,12 +624,16 @@ class StackViewport extends Viewport implements IStackViewport { } private _setPropertiesFromCache(): void { - this.setProperties({ - voiRange: this.voiRange, - rotation: this.rotation, - interpolationType: this.interpolationType, - invert: this.invert, - }); + const suppressEvents = true; + this.setProperties( + { + voiRange: this.voiRange, + rotation: this.rotation, + interpolationType: this.interpolationType, + invert: this.invert, + }, + suppressEvents + ); } private getCameraCPU(): Partial { @@ -652,72 +660,120 @@ class StackViewport extends Viewport implements IStackViewport { ) as Float32Array; } + const canvasCenter: Point2 = [ + this.element.clientWidth / 2, + this.element.clientHeight / 2, + ]; + + // Focal point is the center of the canvas in world coordinate by construction + const canvasCenterWorld = this.canvasToWorld(canvasCenter); + + // parallel scale is half of the viewport height in the world units (mm) + + const topLeftWorld = this.canvasToWorld([0, 0]); + const bottomLeftWorld = this.canvasToWorld([0, this.element.clientHeight]); + + const parallelScale = vec3.distance(topLeftWorld, bottomLeftWorld) / 2; + return { parallelProjection: true, - focalPoint: [0, 0, 0], + focalPoint: canvasCenterWorld, position: [0, 0, 0], - parallelScale: viewport.scale, + parallelScale, + scale: viewport.scale, viewPlaneNormal: [ viewPlaneNormal[0], viewPlaneNormal[1], viewPlaneNormal[2], ], viewUp: [viewUp[0], viewUp[1], viewUp[2]], + flipHorizontal: this.flipHorizontal, + flipVertical: this.flipVertical, }; } private setCameraCPU(cameraInterface: ICamera): void { - const { viewport } = this._cpuFallbackEnabledElement; + const { viewport, image } = this._cpuFallbackEnabledElement; const previousCamera = this.getCameraCPU(); - const { focalPoint, viewUp, parallelScale, flipHorizontal, flipVertical } = - cameraInterface; + const { + focalPoint, + viewUp, + parallelScale, + scale, + flipHorizontal, + flipVertical, + } = cameraInterface; + + const { clientHeight } = this.element; if (focalPoint) { - const focalPointCanvas = this.worldToCanvasCPU( - cameraInterface.focalPoint + const focalPointCanvas = this.worldToCanvasCPU(focalPoint); + const focalPointPixel = canvasToPixel( + this._cpuFallbackEnabledElement, + focalPointCanvas ); - const previousFocalPointCanvas = this.worldToCanvasCPU( + + const prevFocalPointCanvas = this.worldToCanvasCPU( previousCamera.focalPoint ); + const prevFocalPointPixel = canvasToPixel( + this._cpuFallbackEnabledElement, + prevFocalPointCanvas + ); - const deltaCanvas = vec2.create(); - + const deltaPixel = vec2.create(); vec2.subtract( - deltaCanvas, - vec2.fromValues( - previousFocalPointCanvas[0], - previousFocalPointCanvas[1] - ), - vec2.fromValues(focalPointCanvas[0], focalPointCanvas[1]) + deltaPixel, + vec2.fromValues(focalPointPixel[0], focalPointPixel[1]), + vec2.fromValues(prevFocalPointPixel[0], prevFocalPointPixel[1]) ); - viewport.translation.x += deltaCanvas[0] / previousCamera.parallelScale; - viewport.translation.y += deltaCanvas[1] / previousCamera.parallelScale; + const shift = correctShift( + { x: deltaPixel[0], y: deltaPixel[1] }, + viewport + ); + + viewport.translation.x -= shift.x; + viewport.translation.y -= shift.y; } - // If manipulating scale - if (parallelScale && previousCamera.parallelScale !== parallelScale) { - // Note: as parallel scale is defined differently to the GPU version, - // We instead need to find the difference and move the camera in - // the other direction in this adapter. + if (parallelScale) { + // We need to convert he parallelScale which has a physical meaning to + // camera scale factor (since CPU works with scale). Since parallelScale represents + // half of the height of the viewport in the world unit (mm), we can use that + // to compute the scale factor which is the ratio of the viewport height in pixels + // to the current rendered image height. + const { rowPixelSpacing } = image; + const scale = (clientHeight * rowPixelSpacing * 0.5) / parallelScale; - const diff = previousCamera.parallelScale - parallelScale; + viewport.scale = scale; + viewport.parallelScale = parallelScale; + } - viewport.scale += diff; // parallelScale; //viewport.scale < 0.1 ? 0.1 : viewport.scale; + if (scale) { + const { rowPixelSpacing } = image; + viewport.scale = scale; + viewport.parallelScale = (clientHeight * rowPixelSpacing * 0.5) / scale; } - if (flipHorizontal || flipVertical) { + if (flipHorizontal !== undefined || flipVertical !== undefined) { this.setFlipCPU({ flipHorizontal, flipVertical }); } + // re-calculate the transforms + this._cpuFallbackEnabledElement.transform = calculateTransform( + this._cpuFallbackEnabledElement + ); + const updatedCamera = { ...previousCamera, focalPoint, viewUp, - parallelScale, flipHorizontal, flipVertical, + parallelScale: viewport.parallelScale, + scale: viewport.scale, }; const eventDetail: EventTypes.CameraModifiedEventDetail = { @@ -742,13 +798,13 @@ class StackViewport extends Viewport implements IStackViewport { this.flipVertical = viewport.vflip; } - private setVOI(voiRange: VOIRange): void { + private setVOI(voiRange: VOIRange, suppressEvents?: boolean): void { if (this.useCPURendering) { - this.setVOICPU(voiRange); + this.setVOICPU(voiRange, suppressEvents); return; } - this.setVOIGPU(voiRange); + this.setVOIGPU(voiRange, suppressEvents); } private setRotation(rotationCache: number, rotation: number): void { @@ -873,7 +929,7 @@ class StackViewport extends Viewport implements IStackViewport { this.invert = invert; } - private setVOICPU(voiRange: VOIRange): void { + private setVOICPU(voiRange: VOIRange, suppressEvents?: boolean): void { const { viewport, image } = this._cpuFallbackEnabledElement; if (!viewport || !image) { @@ -917,10 +973,12 @@ class StackViewport extends Viewport implements IStackViewport { range: voiRange, }; - triggerEvent(this.element, Events.VOI_MODIFIED, eventDetail); + if (!suppressEvents) { + triggerEvent(this.element, Events.VOI_MODIFIED, eventDetail); + } } - private setVOIGPU(voiRange: VOIRange): void { + private setVOIGPU(voiRange: VOIRange, suppressEvents?: boolean): void { const defaultActor = this.getDefaultActor(); if (!defaultActor) { return; @@ -951,7 +1009,9 @@ class StackViewport extends Viewport implements IStackViewport { range: voiRange, }; - triggerEvent(this.element, Events.VOI_MODIFIED, eventDetail); + if (!suppressEvents) { + triggerEvent(this.element, Events.VOI_MODIFIED, eventDetail); + } } /** @@ -1773,6 +1833,19 @@ class StackViewport extends Viewport implements IStackViewport { } resetCamera(this._cpuFallbackEnabledElement, resetPan, resetZoom); + + const { scale } = this._cpuFallbackEnabledElement.viewport; + + // canvas center is the focal point + const { clientWidth, clientHeight } = this.element; + const center: Point2 = [clientWidth / 2, clientHeight / 2]; + + const centerWorld = this.canvasToWorldCPU(center); + + this.setCameraCPU({ + focalPoint: centerWorld, + scale, + }); } private resetCameraGPU(resetPan, resetZoom): boolean { diff --git a/packages/core/src/RenderingEngine/helpers/cpuFallback/drawImageSync.ts b/packages/core/src/RenderingEngine/helpers/cpuFallback/drawImageSync.ts index 5c8bc9c5c6..b9def4e363 100644 --- a/packages/core/src/RenderingEngine/helpers/cpuFallback/drawImageSync.ts +++ b/packages/core/src/RenderingEngine/helpers/cpuFallback/drawImageSync.ts @@ -15,7 +15,6 @@ export default function ( invalidated: boolean ): void { const image = enabledElement.image; - const canvas = enabledElement.canvas; // Check if enabledElement can be redrawn if (!enabledElement.canvas || !enabledElement.image) { diff --git a/packages/core/src/RenderingEngine/helpers/cpuFallback/rendering/correctShift.ts b/packages/core/src/RenderingEngine/helpers/cpuFallback/rendering/correctShift.ts new file mode 100644 index 0000000000..d8f2af56ee --- /dev/null +++ b/packages/core/src/RenderingEngine/helpers/cpuFallback/rendering/correctShift.ts @@ -0,0 +1,38 @@ +import { CPUFallbackViewport, Point2 } from '../../../../types'; + +type Shift = { + x: number; + y: number; +}; +/** + * Corrects the shift by accounting for viewport rotation and flips. + * + * @param shift - The shift to correct. + * @param viewportOrientation - Object containing information on the viewport orientation. + */ +export default function ( + shift: Shift, + viewportOrientation: CPUFallbackViewport +): Shift { + const { hflip, vflip, rotation } = viewportOrientation; + + // Apply Flips + shift.x *= hflip ? -1 : 1; + shift.y *= vflip ? -1 : 1; + + // Apply rotations + if (rotation !== 0) { + const angle = (rotation * Math.PI) / 180; + + const cosA = Math.cos(angle); + const sinA = Math.sin(angle); + + const newX = shift.x * cosA - shift.y * sinA; + const newY = shift.x * sinA + shift.y * cosA; + + shift.x = newX; + shift.y = newY; + } + + return shift; +} diff --git a/packages/core/src/RenderingEngine/helpers/createVolumeActor.ts b/packages/core/src/RenderingEngine/helpers/createVolumeActor.ts index 4ce2a5f7a2..a3c7d37001 100644 --- a/packages/core/src/RenderingEngine/helpers/createVolumeActor.ts +++ b/packages/core/src/RenderingEngine/helpers/createVolumeActor.ts @@ -11,7 +11,7 @@ import setDefaultVolumeVOI from './setDefaultVolumeVOI'; interface createVolumeActorInterface { volumeId: string; - callback?: ({ volumeActor: any, volumeId: string }) => void; + callback?: ({ volumeActor: VolumeActor, volumeId: string }) => void; blendMode?: BlendModes; } diff --git a/packages/core/src/types/CPUFallbackViewport.ts b/packages/core/src/types/CPUFallbackViewport.ts index b8070ce9f4..6619e11699 100644 --- a/packages/core/src/types/CPUFallbackViewport.ts +++ b/packages/core/src/types/CPUFallbackViewport.ts @@ -4,6 +4,8 @@ import CPUFallbackLUT from './CPUFallbackLUT'; type CPUFallbackViewport = { scale?: number; + parallelScale?: number; + focalPoint?: number[]; translation?: { x: number; y: number; diff --git a/packages/core/src/types/ICamera.ts b/packages/core/src/types/ICamera.ts index beb6b2200e..981c0760a8 100644 --- a/packages/core/src/types/ICamera.ts +++ b/packages/core/src/types/ICamera.ts @@ -1,4 +1,3 @@ -import Point2 from './Point2'; import Point3 from './Point3'; /** @@ -12,6 +11,11 @@ interface ICamera { parallelProjection?: boolean; /** Camera parallel scale - used for parallel projection zoom, smaller values zoom in */ parallelScale?: number; + /** + * Scale factor for the camera, it is the ratio of how much an image pixel takes + * up one screen pixel + */ + scale?: number; /** Camera position */ position?: Point3; /** Camera view angle - 90 degrees is orthographic */ diff --git a/packages/core/src/types/StackViewportProperties.ts b/packages/core/src/types/StackViewportProperties.ts index b6e8dcd240..3a32e51bf9 100644 --- a/packages/core/src/types/StackViewportProperties.ts +++ b/packages/core/src/types/StackViewportProperties.ts @@ -13,6 +13,8 @@ type StackViewportProperties = { interpolationType?: InterpolationType; /** image rotation */ rotation?: number; + /** suppress events (optional) */ + suppressEvents?: boolean; }; export default StackViewportProperties; diff --git a/packages/streaming-image-volume-loader/src/helpers/autoLoad.ts b/packages/streaming-image-volume-loader/src/helpers/autoLoad.ts index 7a2686e7ac..dd3d74d92a 100644 --- a/packages/streaming-image-volume-loader/src/helpers/autoLoad.ts +++ b/packages/streaming-image-volume-loader/src/helpers/autoLoad.ts @@ -1,9 +1,10 @@ import { getRenderingEngines, utilities } from '@cornerstonejs/core'; +import type { Types } from '@cornerstonejs/core'; //import type { Types } from '@cornerstonejs/core' type RenderingEngineAndViewportIds = { - renderingEngine: any | undefined; //Types.IRenderingEngine | undefined + renderingEngine: Types.IRenderingEngine | undefined; //Types.IRenderingEngine | undefined viewportIds: Array; }; diff --git a/packages/tools/examples/stackAnnotationTools/index.ts b/packages/tools/examples/stackAnnotationTools/index.ts index 57abbedb09..92a48a66b5 100644 --- a/packages/tools/examples/stackAnnotationTools/index.ts +++ b/packages/tools/examples/stackAnnotationTools/index.ts @@ -20,6 +20,7 @@ const { BidirectionalTool, AngleTool, ToolGroupManager, + ArrowAnnotateTool, Enums: csToolsEnums, } = cornerstoneTools; @@ -59,6 +60,7 @@ const toolsNames = [ EllipticalROITool.toolName, BidirectionalTool.toolName, AngleTool.toolName, + ArrowAnnotateTool.toolName, ]; let selectedToolName = toolsNames[0]; @@ -98,6 +100,7 @@ async function run() { cornerstoneTools.addTool(EllipticalROITool); cornerstoneTools.addTool(BidirectionalTool); cornerstoneTools.addTool(AngleTool); + cornerstoneTools.addTool(ArrowAnnotateTool); // Define a tool group, which defines how mouse events map to tool commands for // Any viewport using the group @@ -110,6 +113,7 @@ async function run() { toolGroup.addTool(EllipticalROITool.toolName); toolGroup.addTool(BidirectionalTool.toolName); toolGroup.addTool(AngleTool.toolName); + toolGroup.addTool(ArrowAnnotateTool.toolName); // Set the initial state of the tools, here we set one tool active on left click. // This means left click will draw that tool. @@ -127,6 +131,7 @@ async function run() { toolGroup.setToolPassive(EllipticalROITool.toolName); toolGroup.setToolPassive(BidirectionalTool.toolName); toolGroup.setToolPassive(AngleTool.toolName); + toolGroup.setToolPassive(ArrowAnnotateTool.toolName); // Get Cornerstone imageIds and fetch metadata into RAM const imageIds = await createImageIdsAndCacheMetaData({ diff --git a/packages/tools/src/drawingSvg/clearByToolType.ts b/packages/tools/src/drawingSvg/clearByToolType.ts index 407585e9ae..bee0952afa 100644 --- a/packages/tools/src/drawingSvg/clearByToolType.ts +++ b/packages/tools/src/drawingSvg/clearByToolType.ts @@ -8,7 +8,7 @@ import getSvgDrawingHelper from './getSvgDrawingHelper'; */ function clearByToolType(element: HTMLDivElement, toolType: string): void { const svgDrawingHelper = getSvgDrawingHelper(element); - const nodes = svgDrawingHelper._svgLayerElement.querySelectorAll( + const nodes = svgDrawingHelper.svgLayerElement.querySelectorAll( 'svg > *' ) as NodeListOf; @@ -18,7 +18,7 @@ function clearByToolType(element: HTMLDivElement, toolType: string): void { const toolUID = node.dataset.toolUid; if (toolUID === toolType) { - svgDrawingHelper._svgLayerElement.removeChild(node); + svgDrawingHelper.svgLayerElement.removeChild(node); } } } diff --git a/packages/tools/src/drawingSvg/draw.ts b/packages/tools/src/drawingSvg/draw.ts index f0cc9257b6..80f7ddc2ff 100644 --- a/packages/tools/src/drawingSvg/draw.ts +++ b/packages/tools/src/drawingSvg/draw.ts @@ -10,7 +10,7 @@ function draw( fn(svgDrawingHelper); // Restore... - svgDrawingHelper._clearUntouched(); + svgDrawingHelper.clearUntouched(); } export default draw; diff --git a/packages/tools/src/drawingSvg/drawArrow.ts b/packages/tools/src/drawingSvg/drawArrow.ts index 0f4a7d63d0..03c4057e63 100644 --- a/packages/tools/src/drawingSvg/drawArrow.ts +++ b/packages/tools/src/drawingSvg/drawArrow.ts @@ -1,8 +1,9 @@ import type { Types } from '@cornerstonejs/core'; +import { SVGDrawingHelper } from '../types'; import drawLine from './drawLine'; export default function drawArrow( - svgDrawingHelper: any, + svgDrawingHelper: SVGDrawingHelper, annotationUID: string, arrowUID: string, start: Types.Point2, diff --git a/packages/tools/src/drawingSvg/drawCircle.ts b/packages/tools/src/drawingSvg/drawCircle.ts index 8b637942d6..93c1e7481f 100644 --- a/packages/tools/src/drawingSvg/drawCircle.ts +++ b/packages/tools/src/drawingSvg/drawCircle.ts @@ -1,4 +1,5 @@ import type { Types } from '@cornerstonejs/core'; +import { SVGDrawingHelper } from '../types'; import _getHash from './_getHash'; @@ -6,7 +7,7 @@ import _setAttributesIfNecessary from './_setAttributesIfNecessary'; import _setNewAttributesIfValid from './_setNewAttributesIfValid'; function drawCircle( - svgDrawingHelper: any, + svgDrawingHelper: SVGDrawingHelper, annotationUID: string, circleUID: string, center: Types.Point2, @@ -29,7 +30,7 @@ function drawCircle( // variable for the namespace const svgns = 'http://www.w3.org/2000/svg'; const svgNodeHash = _getHash(annotationUID, 'circle', circleUID); - const existingCircleElement = svgDrawingHelper._getSvgNode(svgNodeHash); + const existingCircleElement = svgDrawingHelper.getSvgNode(svgNodeHash); const attributes = { cx: `${center[0]}`, @@ -43,13 +44,13 @@ function drawCircle( if (existingCircleElement) { _setAttributesIfNecessary(attributes, existingCircleElement); - svgDrawingHelper._setNodeTouched(svgNodeHash); + svgDrawingHelper.setNodeTouched(svgNodeHash); } else { const newCircleElement = document.createElementNS(svgns, 'circle'); _setNewAttributesIfValid(attributes, newCircleElement); - svgDrawingHelper._appendNode(newCircleElement, svgNodeHash); + svgDrawingHelper.appendNode(newCircleElement, svgNodeHash); } } diff --git a/packages/tools/src/drawingSvg/drawEllipse.ts b/packages/tools/src/drawingSvg/drawEllipse.ts index 36235abc56..c9c0d5eeeb 100644 --- a/packages/tools/src/drawingSvg/drawEllipse.ts +++ b/packages/tools/src/drawingSvg/drawEllipse.ts @@ -1,11 +1,12 @@ import type { Types } from '@cornerstonejs/core'; +import { SVGDrawingHelper } from '../types'; import _getHash from './_getHash'; import _setAttributesIfNecessary from './_setAttributesIfNecessary'; import _setNewAttributesIfValid from './_setNewAttributesIfValid'; function drawEllipse( - svgDrawingHelper: any, + svgDrawingHelper: SVGDrawingHelper, annotationUID: string, ellipseUID: string, corner1: Types.Point2, @@ -27,7 +28,7 @@ function drawEllipse( const svgns = 'http://www.w3.org/2000/svg'; const svgNodeHash = _getHash(annotationUID, 'ellipse', ellipseUID); - const existingEllipse = svgDrawingHelper._getSvgNode(svgNodeHash); + const existingEllipse = svgDrawingHelper.getSvgNode(svgNodeHash); const w = Math.abs(corner1[0] - corner2[0]); const h = Math.abs(corner1[1] - corner2[1]); @@ -52,13 +53,13 @@ function drawEllipse( if (existingEllipse) { _setAttributesIfNecessary(attributes, existingEllipse); - svgDrawingHelper._setNodeTouched(svgNodeHash); + svgDrawingHelper.setNodeTouched(svgNodeHash); } else { const svgEllipseElement = document.createElementNS(svgns, 'ellipse'); _setNewAttributesIfValid(attributes, svgEllipseElement); - svgDrawingHelper._appendNode(svgEllipseElement, svgNodeHash); + svgDrawingHelper.appendNode(svgEllipseElement, svgNodeHash); } } diff --git a/packages/tools/src/drawingSvg/drawHandles.ts b/packages/tools/src/drawingSvg/drawHandles.ts index e9e2683d6c..0ea6f9c155 100644 --- a/packages/tools/src/drawingSvg/drawHandles.ts +++ b/packages/tools/src/drawingSvg/drawHandles.ts @@ -3,9 +3,10 @@ import type { Types } from '@cornerstonejs/core'; import _getHash from './_getHash'; import _setNewAttributesIfValid from './_setNewAttributesIfValid'; import _setAttributesIfNecessary from './_setAttributesIfNecessary'; +import { SVGDrawingHelper } from '../types'; function drawHandles( - svgDrawingHelper: any, + svgDrawingHelper: SVGDrawingHelper, annotationUID: string, handleGroupUID: string, handlePoints: Array, @@ -67,18 +68,18 @@ function drawHandles( throw new Error(`Unsupported handle type: ${type}`); } - const existingHandleElement = svgDrawingHelper._getSvgNode(svgNodeHash); + const existingHandleElement = svgDrawingHelper.getSvgNode(svgNodeHash); if (existingHandleElement) { _setAttributesIfNecessary(attributes, existingHandleElement); - svgDrawingHelper._setNodeTouched(svgNodeHash); + svgDrawingHelper.setNodeTouched(svgNodeHash); } else { const newHandleElement = document.createElementNS(svgns, type); _setNewAttributesIfValid(attributes, newHandleElement); - svgDrawingHelper._appendNode(newHandleElement, svgNodeHash); + svgDrawingHelper.appendNode(newHandleElement, svgNodeHash); } } } diff --git a/packages/tools/src/drawingSvg/drawLine.ts b/packages/tools/src/drawingSvg/drawLine.ts index e1056d85dc..e7d5f2821c 100644 --- a/packages/tools/src/drawingSvg/drawLine.ts +++ b/packages/tools/src/drawingSvg/drawLine.ts @@ -3,9 +3,10 @@ import type { Types } from '@cornerstonejs/core'; import _getHash from './_getHash'; import _setNewAttributesIfValid from './_setNewAttributesIfValid'; import _setAttributesIfNecessary from './_setAttributesIfNecessary'; +import { SVGDrawingHelper } from '../types'; export default function drawLine( - svgDrawingHelper: any, + svgDrawingHelper: SVGDrawingHelper, annotationUID: string, lineUID: string, start: Types.Point2, @@ -32,7 +33,7 @@ export default function drawLine( const svgns = 'http://www.w3.org/2000/svg'; const svgNodeHash = _getHash(annotationUID, 'line', lineUID); - const existingLine = svgDrawingHelper._getSvgNode(svgNodeHash); + const existingLine = svgDrawingHelper.getSvgNode(svgNodeHash); const attributes = { x1: `${start[0]}`, @@ -48,12 +49,12 @@ export default function drawLine( // This is run to avoid re-rendering annotations that actually haven't changed _setAttributesIfNecessary(attributes, existingLine); - svgDrawingHelper._setNodeTouched(svgNodeHash); + svgDrawingHelper.setNodeTouched(svgNodeHash); } else { const newLine = document.createElementNS(svgns, 'line'); _setNewAttributesIfValid(attributes, newLine); - svgDrawingHelper._appendNode(newLine, svgNodeHash); + svgDrawingHelper.appendNode(newLine, svgNodeHash); } } diff --git a/packages/tools/src/drawingSvg/drawLink.ts b/packages/tools/src/drawingSvg/drawLink.ts index 68133a63f4..e9ac3db4fa 100644 --- a/packages/tools/src/drawingSvg/drawLink.ts +++ b/packages/tools/src/drawingSvg/drawLink.ts @@ -2,13 +2,13 @@ import type { Types } from '@cornerstonejs/core'; import drawLine from './drawLine'; import findClosestPoint from '../utilities/math/vec2/findClosestPoint'; -import { PlanarBoundingBox } from '../types'; +import { PlanarBoundingBox, SVGDrawingHelper } from '../types'; /** * Draw a link between an annotation to a box. */ function drawLink( - svgDrawingHelper: Record, + svgDrawingHelper: SVGDrawingHelper, annotationUID: string, linkUID: string, // Find closest point to approx. bounding box diff --git a/packages/tools/src/drawingSvg/drawLinkedTextBox.ts b/packages/tools/src/drawingSvg/drawLinkedTextBox.ts index 54e6eacd34..8ae8fa020b 100644 --- a/packages/tools/src/drawingSvg/drawLinkedTextBox.ts +++ b/packages/tools/src/drawingSvg/drawLinkedTextBox.ts @@ -2,9 +2,10 @@ import type { Types } from '@cornerstonejs/core'; import drawTextBox from './drawTextBox'; import drawLink from './drawLink'; +import { SVGDrawingHelper } from '../types'; function drawLinkedTextBox( - svgDrawingHelper: Record, + svgDrawingHelper: SVGDrawingHelper, annotationUID: string, textBoxUID: string, // diff --git a/packages/tools/src/drawingSvg/drawPolyline.ts b/packages/tools/src/drawingSvg/drawPolyline.ts index d8f832b9b0..7504e545cd 100644 --- a/packages/tools/src/drawingSvg/drawPolyline.ts +++ b/packages/tools/src/drawingSvg/drawPolyline.ts @@ -2,6 +2,7 @@ import type { Types } from '@cornerstonejs/core'; import _getHash from './_getHash'; import _setNewAttributesIfValid from './_setNewAttributesIfValid'; import _setAttributesIfNecessary from './_setAttributesIfNecessary'; +import { SVGDrawingHelper } from '../types'; /** * Draws an SVG polyline with the given points. @@ -10,7 +11,7 @@ import _setAttributesIfNecessary from './_setAttributesIfNecessary'; * last point connected to the first. */ export default function drawPolyline( - svgDrawingHelper: any, + svgDrawingHelper: SVGDrawingHelper, annotationUID: string, polylineUID: string, points: Types.Point2[], @@ -42,7 +43,7 @@ export default function drawPolyline( const svgns = 'http://www.w3.org/2000/svg'; const svgNodeHash = _getHash(annotationUID, 'polyline', polylineUID); - const existingPolyLine = svgDrawingHelper._getSvgNode(svgNodeHash); + const existingPolyLine = svgDrawingHelper.getSvgNode(svgNodeHash); let pointsAttribute = ''; @@ -68,12 +69,12 @@ export default function drawPolyline( // This is run to avoid re-rendering annotations that actually haven't changed _setAttributesIfNecessary(attributes, existingPolyLine); - svgDrawingHelper._setNodeTouched(svgNodeHash); + svgDrawingHelper.setNodeTouched(svgNodeHash); } else { const newPolyLine = document.createElementNS(svgns, 'polyline'); _setNewAttributesIfValid(attributes, newPolyLine); - svgDrawingHelper._appendNode(newPolyLine, svgNodeHash); + svgDrawingHelper.appendNode(newPolyLine, svgNodeHash); } } diff --git a/packages/tools/src/drawingSvg/drawRect.ts b/packages/tools/src/drawingSvg/drawRect.ts index ed21678635..9fa1e46534 100644 --- a/packages/tools/src/drawingSvg/drawRect.ts +++ b/packages/tools/src/drawingSvg/drawRect.ts @@ -3,10 +3,11 @@ import type { Types } from '@cornerstonejs/core'; import _getHash from './_getHash'; import _setAttributesIfNecessary from './_setAttributesIfNecessary'; import _setNewAttributesIfValid from './_setNewAttributesIfValid'; +import { SVGDrawingHelper } from '../types'; // export default function drawRect( - svgDrawingHelper: any, + svgDrawingHelper: SVGDrawingHelper, annotationUID: string, rectangleUID: string, start: Types.Point2, @@ -33,7 +34,7 @@ export default function drawRect( const svgns = 'http://www.w3.org/2000/svg'; const svgNodeHash = _getHash(annotationUID, 'rect', rectangleUID); - const existingRect = svgDrawingHelper._getSvgNode(svgNodeHash); + const existingRect = svgDrawingHelper.getSvgNode(svgNodeHash); const tlhc = [Math.min(start[0], end[0]), Math.min(start[1], end[1])]; const width = Math.abs(start[0] - end[0]); @@ -53,12 +54,12 @@ export default function drawRect( if (existingRect) { _setAttributesIfNecessary(attributes, existingRect); - svgDrawingHelper._setNodeTouched(svgNodeHash); + svgDrawingHelper.setNodeTouched(svgNodeHash); } else { const svgRectElement = document.createElementNS(svgns, 'rect'); _setNewAttributesIfValid(attributes, svgRectElement); - svgDrawingHelper._appendNode(svgRectElement, svgNodeHash); + svgDrawingHelper.appendNode(svgRectElement, svgNodeHash); } } diff --git a/packages/tools/src/drawingSvg/drawTextBox.ts b/packages/tools/src/drawingSvg/drawTextBox.ts index ca2912be04..25e622b0c9 100644 --- a/packages/tools/src/drawingSvg/drawTextBox.ts +++ b/packages/tools/src/drawingSvg/drawTextBox.ts @@ -1,4 +1,5 @@ import type { Types } from '@cornerstonejs/core'; +import { SVGDrawingHelper } from '../types'; import _getHash from './_getHash'; import _setAttributesIfNecessary from './_setAttributesIfNecessary'; @@ -12,7 +13,7 @@ import _setAttributesIfNecessary from './_setAttributesIfNecessary'; * @returns Bounding box; can be used for isPointNearTool */ function drawTextBox( - svgDrawingHelper: Record, + svgDrawingHelper: SVGDrawingHelper, annotationUID: string, textUID: string, textLines: Array, @@ -46,7 +47,7 @@ function drawTextBox( } function _drawTextGroup( - svgDrawingHelper: any, + svgDrawingHelper: SVGDrawingHelper, annotationUID: string, textUID: string, textLines: Array, @@ -59,7 +60,7 @@ function _drawTextGroup( const [x, y] = [position[0] + padding, position[1] + padding]; const svgns = 'http://www.w3.org/2000/svg'; const svgNodeHash = _getHash(annotationUID, 'text', textUID); - const existingTextGroup = svgDrawingHelper._getSvgNode(svgNodeHash); + const existingTextGroup = svgDrawingHelper.getSvgNode(svgNodeHash); // Todo: right now textBox gets a re-render even if the textBox has not changed // and evenIf the attributes are not set again since they are the same. @@ -85,7 +86,7 @@ function _drawTextGroup( } existingTextGroup.appendChild(textElement); - svgDrawingHelper._appendNode(existingTextGroup, svgNodeHash); + svgDrawingHelper.appendNode(existingTextGroup, svgNodeHash); } const textAttributes = { @@ -104,7 +105,7 @@ function _drawTextGroup( textGroupBoundingBox = _drawTextBackground(existingTextGroup, background); - svgDrawingHelper._setNodeTouched(svgNodeHash); + svgDrawingHelper.setNodeTouched(svgNodeHash); } else { const textGroup = document.createElementNS(svgns, 'g'); @@ -120,7 +121,7 @@ function _drawTextGroup( } textGroup.appendChild(textElement); - svgDrawingHelper._appendNode(textGroup, svgNodeHash); + svgDrawingHelper.appendNode(textGroup, svgNodeHash); textGroupBoundingBox = _drawTextBackground(textGroup, background); } diff --git a/packages/tools/src/drawingSvg/getSvgDrawingHelper.ts b/packages/tools/src/drawingSvg/getSvgDrawingHelper.ts index c5a7f8e7e6..358102de81 100644 --- a/packages/tools/src/drawingSvg/getSvgDrawingHelper.ts +++ b/packages/tools/src/drawingSvg/getSvgDrawingHelper.ts @@ -1,12 +1,13 @@ import { state } from '../store'; import { getEnabledElement } from '@cornerstonejs/core'; +import { SVGDrawingHelper } from '../types'; /** * Returns the SVG drawing helper for the given HTML element. * @param element - The HTML element to get the SVG drawing helper for. * @private */ -function getSvgDrawingHelper(element: HTMLDivElement) { +function getSvgDrawingHelper(element: HTMLDivElement): SVGDrawingHelper { const enabledElement = getEnabledElement(element); const { viewportId, renderingEngineId } = enabledElement; const canvasHash = `${viewportId}:${renderingEngineId}`; @@ -18,22 +19,18 @@ function getSvgDrawingHelper(element: HTMLDivElement) { }); return { - // Todo: not sure if we need enabledElement and _element anymore here - enabledElement: enabledElement, - _element: element, - _svgLayerElement: svgLayerElement, - _svgNodeCacheForCanvas: state.svgNodeCache, - _getSvgNode: getSvgNode.bind(this, canvasHash), - _appendNode: appendNode.bind(this, svgLayerElement, canvasHash), - _setNodeTouched: setNodeTouched.bind(this, canvasHash), - _clearUntouched: clearUntouched.bind(this, svgLayerElement, canvasHash), - // _drawnAnnotations: drawnAnnotations, + svgLayerElement: svgLayerElement, + svgNodeCacheForCanvas: state.svgNodeCache, + getSvgNode: getSvgNode.bind(this, canvasHash), + appendNode: appendNode.bind(this, svgLayerElement, canvasHash), + setNodeTouched: setNodeTouched.bind(this, canvasHash), + clearUntouched: clearUntouched.bind(this, svgLayerElement, canvasHash), }; } /** * - * @param canvasElement + * @param element * @private */ function _getSvgLayer(element) { diff --git a/packages/tools/src/tools/CrosshairsTool.ts b/packages/tools/src/tools/CrosshairsTool.ts index d4c827f752..cf289ae8e4 100644 --- a/packages/tools/src/tools/CrosshairsTool.ts +++ b/packages/tools/src/tools/CrosshairsTool.ts @@ -43,6 +43,7 @@ import { PublicToolProps, ToolProps, InteractionTypes, + SVGDrawingHelper, } from '../types'; import { isAnnotationLocked } from '../stateManagement/annotation/annotationLocking'; import triggerAnnotationRenderForViewportIds from '../utilities/triggerAnnotationRenderForViewportIds'; @@ -668,8 +669,9 @@ export default class CrosshairsTool extends AnnotationTool { */ renderAnnotation = ( enabledElement: Types.IEnabledElement, - svgDrawingHelper: any - ): void => { + svgDrawingHelper: SVGDrawingHelper + ): boolean => { + let renderStatus = false; const { viewport, renderingEngine } = enabledElement; const { element } = viewport; const annotations = getAnnotations(element, this.getToolName()); @@ -681,7 +683,7 @@ export default class CrosshairsTool extends AnnotationTool { const viewportAnnotation = filteredToolAnnotations[0]; if (!annotations || !viewportAnnotation || !viewportAnnotation.data) { // No annotations yet, and didn't just create it as we likely don't have a FrameOfReference/any data loaded yet. - return; + return renderStatus; } const annotationUID = viewportAnnotation.annotationUID; @@ -1288,32 +1290,33 @@ export default class CrosshairsTool extends AnnotationTool { } }); + renderStatus = true; + // Save new handles points in annotation data.handles.rotationPoints = newRtpoints; data.handles.slabThicknessPoints = newStpoints; - // render a circle to pin point the viewport color - // TODO: This should not be part of the tool, and definitely not part of the renderAnnotation loop - - if (!this.configuration.viewportIndicators) { - return; + if (this.configuration.viewportIndicators) { + // render a circle to pin point the viewport color + // TODO: This should not be part of the tool, and definitely not part of the renderAnnotation loop + const referenceColorCoordinates = [ + sWidth * 0.95, + sHeight * 0.05, + ] as Types.Point2; + const circleRadius = canvasDiagonalLength * 0.01; + + const circleUID = '0'; + drawCircleSvg( + svgDrawingHelper, + annotationUID, + circleUID, + referenceColorCoordinates, + circleRadius, + { color, fill: color } + ); } - const referenceColorCoordinates = [ - sWidth * 0.95, - sHeight * 0.05, - ] as Types.Point2; - const circleRadius = canvasDiagonalLength * 0.01; - - const circleUID = '0'; - drawCircleSvg( - svgDrawingHelper, - annotationUID, - circleUID, - referenceColorCoordinates, - circleRadius, - { color, fill: color } - ); + return renderStatus; }; _autoPanViewportIfNecessary( diff --git a/packages/tools/src/tools/ZoomTool.ts b/packages/tools/src/tools/ZoomTool.ts index ff85e0800f..cecbf99b5a 100644 --- a/packages/tools/src/tools/ZoomTool.ts +++ b/packages/tools/src/tools/ZoomTool.ts @@ -16,8 +16,6 @@ export default class ZoomTool extends BaseTool { mouseDragCallback: () => void; initialMousePosWorld: Types.Point3; dirVec: Types.Point3; - minZoomScale = 0.1; - maxZoomScale = 10; // Apparently TS says super _must_ be the first call? This seems a bit opinionated. constructor( @@ -27,6 +25,8 @@ export default class ZoomTool extends BaseTool { configuration: { // whether zoom to the center of the image OR zoom to the mouse position zoomToCenter: false, + minZoomScale: 0.1, + maxZoomScale: 30, }, } ) { @@ -83,9 +83,9 @@ export default class ZoomTool extends BaseTool { const camera = viewport.getCamera(); if (camera.parallelProjection) { - this._dragParallelProjection(evt, camera); + this._dragParallelProjection(evt, viewport, camera); } else { - this._dragPerspectiveProjection(evt, camera); + this._dragPerspectiveProjection(evt, viewport, camera); } viewport.render(); @@ -93,20 +93,19 @@ export default class ZoomTool extends BaseTool { _dragParallelProjection = ( evt: EventTypes.MouseDragEventType, + viewport: Types.IStackViewport | Types.IVolumeViewport, camera: Types.ICamera - ) => { + ): void => { const { element, deltaPoints } = evt.detail; - const enabledElement = getEnabledElement(element); - const { viewport } = enabledElement; - const size = [element.clientWidth, element.clientHeight]; + const size = [element.clientWidth, element.clientHeight]; const { parallelScale, focalPoint, position } = camera; const zoomScale = 1.5 / size[1]; const deltaY = deltaPoints.canvas[1]; const k = deltaY * zoomScale; - let newParallelScale = (1.0 - k) * parallelScale; + let parallelScaleToSet = (1.0 - k) * parallelScale; let focalPointToSet = focalPoint; let positionToSet = position; @@ -121,13 +120,14 @@ export default class ZoomTool extends BaseTool { focalPoint, this.initialMousePosWorld ); + // const initialYDistanceBetweenInitialAndFocalPoint; // we need to move in the direction of the vector between the focal point // and the initial mouse position by some amount until ultimately we // reach the mouse position at the focal point - const zoomScale = 10 / size[1]; + const zoomScale = 5 / size[1]; const k = deltaY * zoomScale; - newParallelScale = (1.0 - k) * parallelScale; + parallelScaleToSet = (1.0 - k) * parallelScale; positionToSet = vec3.scaleAndAdd( vec3.create(), @@ -144,8 +144,26 @@ export default class ZoomTool extends BaseTool { ) as Types.Point3; } - const { parallelScale: cappedParallelScale, thresholdExceeded } = - this._getCappedParallelScale(viewport, newParallelScale); + // If it is a regular GPU accelerated viewport, then parallel scale + // has a physical meaning and we can use that to determine the threshold + const imageData = viewport.getImageData(); + + const { spacing } = imageData; + const { minZoomScale, maxZoomScale } = this.configuration; + + const t = element.clientHeight * spacing[1] * 0.5; + const scale = t / parallelScaleToSet; + + let cappedParallelScale = parallelScaleToSet; + let thresholdExceeded = false; + + if (scale < minZoomScale) { + cappedParallelScale = t / minZoomScale; + thresholdExceeded = true; + } else if (scale >= maxZoomScale) { + cappedParallelScale = t / maxZoomScale; + thresholdExceeded = true; + } viewport.setCamera({ parallelScale: cappedParallelScale, @@ -154,10 +172,8 @@ export default class ZoomTool extends BaseTool { }); }; - _dragPerspectiveProjection = (evt, camera) => { + _dragPerspectiveProjection = (evt, viewport, camera) => { const { element, deltaPoints } = evt.detail; - const enabledElement = getEnabledElement(element); - const { viewport } = enabledElement; const size = [element.clientWidth, element.clientHeight]; const { position, focalPoint, viewPlaneNormal } = camera; @@ -188,33 +204,4 @@ export default class ZoomTool extends BaseTool { viewport.setCamera({ position, focalPoint }); }; - - _getCappedParallelScale = (viewport, parallelScale) => { - const imageData = viewport.getImageData(); - - if (!imageData) { - return; - } - - const { dimensions, spacing } = imageData; - - const t = dimensions[0] * spacing[0]; - const scale = t / parallelScale; - - let newParallelScale = parallelScale; - let thresholdExceeded = false; - - if (scale < this.minZoomScale) { - newParallelScale = t / this.minZoomScale; - thresholdExceeded = true; - } else if (scale > this.maxZoomScale) { - newParallelScale = t / this.maxZoomScale; - thresholdExceeded = true; - } - - return { - parallelScale: newParallelScale, - thresholdExceeded, - }; - }; } diff --git a/packages/tools/src/tools/annotation/AngleTool.ts b/packages/tools/src/tools/annotation/AngleTool.ts index dfa3a868f9..0732528175 100644 --- a/packages/tools/src/tools/annotation/AngleTool.ts +++ b/packages/tools/src/tools/annotation/AngleTool.ts @@ -43,6 +43,7 @@ import { PublicToolProps, ToolProps, InteractionTypes, + SVGDrawingHelper, } from '../../types'; import { AngleAnnotation } from '../../types/ToolSpecificAnnotationTypes'; import { StyleSpecifier } from '../../types/AnnotationStyle'; @@ -522,8 +523,10 @@ class AngleTool extends AnnotationTool { */ renderAnnotation = ( enabledElement: Types.IEnabledElement, - svgDrawingHelper: any - ): void => { + svgDrawingHelper: SVGDrawingHelper + ): boolean => { + let renderStatus = false; + const { viewport } = enabledElement; const { element } = viewport; @@ -531,7 +534,7 @@ class AngleTool extends AnnotationTool { // Todo: We don't need this anymore, filtering happens in triggerAnnotationRender if (!annotations?.length) { - return; + return renderStatus; } annotations = this.filterInteractableAnnotationsForElement( @@ -540,7 +543,7 @@ class AngleTool extends AnnotationTool { ); if (!annotations?.length) { - return; + return renderStatus; } const targetId = this.getTargetId(viewport); @@ -592,6 +595,12 @@ class AngleTool extends AnnotationTool { activeHandleCanvasCoords = [canvasCoordinates[activeHandleIndex]]; } + // If rendering engine has been destroyed while rendering + if (!viewport.getRenderingEngine()) { + console.warn('Rendering Engine has been destroyed'); + return renderStatus; + } + if (activeHandleCanvasCoords) { const handleGroupUID = '0'; @@ -622,9 +631,11 @@ class AngleTool extends AnnotationTool { } ); + renderStatus = true; + // Don't add textBox until annotation has 3 anchor points (actually 4 because of the center point) if (canvasCoordinates.length !== 3) { - return; + return renderStatus; } lineUID = '2'; @@ -642,12 +653,6 @@ class AngleTool extends AnnotationTool { } ); - // If rendering engine has been destroyed while rendering - if (!viewport.getRenderingEngine()) { - console.warn('Rendering Engine has been destroyed'); - return; - } - if (!data.cachedStats[targetId]?.angle) { continue; } @@ -686,6 +691,8 @@ class AngleTool extends AnnotationTool { bottomRight: viewport.canvasToWorld([left + width, top + height]), }; } + + return renderStatus; }; // text line for the current active length annotation diff --git a/packages/tools/src/tools/annotation/ArrowAnnotateTool.ts b/packages/tools/src/tools/annotation/ArrowAnnotateTool.ts index 3878a0a8ba..85d636127a 100644 --- a/packages/tools/src/tools/annotation/ArrowAnnotateTool.ts +++ b/packages/tools/src/tools/annotation/ArrowAnnotateTool.ts @@ -39,6 +39,7 @@ import { PublicToolProps, ToolProps, InteractionTypes, + SVGDrawingHelper, } from '../../types'; import { ArrowAnnotation } from '../../types/ToolSpecificAnnotationTypes'; import { StyleSpecifier } from '../../types/AnnotationStyle'; @@ -563,8 +564,9 @@ class ArrowAnnotateTool extends AnnotationTool { */ renderAnnotation = ( enabledElement: Types.IEnabledElement, - svgDrawingHelper: any - ): void => { + svgDrawingHelper: SVGDrawingHelper + ): boolean => { + let renderStatus = false; const { viewport } = enabledElement; const { element } = viewport; @@ -572,7 +574,7 @@ class ArrowAnnotateTool extends AnnotationTool { // Todo: We don't need this anymore, filtering happens in triggerAnnotationRender if (!annotations?.length) { - return; + return renderStatus; } annotations = this.filterInteractableAnnotationsForElement( @@ -581,7 +583,7 @@ class ArrowAnnotateTool extends AnnotationTool { ); if (!annotations?.length) { - return; + return renderStatus; } const styleSpecifier: StyleSpecifier = { @@ -660,10 +662,12 @@ class ArrowAnnotateTool extends AnnotationTool { ); } + renderStatus = true; + // If rendering engine has been destroyed while rendering if (!viewport.getRenderingEngine()) { console.warn('Rendering Engine has been destroyed'); - return; + return renderStatus; } if (!text) { @@ -703,6 +707,8 @@ class ArrowAnnotateTool extends AnnotationTool { bottomRight: viewport.canvasToWorld([left + width, top + height]), }; } + + return renderStatus; }; _isInsideVolume(index1, index2, dimensions) { diff --git a/packages/tools/src/tools/annotation/BidirectionalTool.ts b/packages/tools/src/tools/annotation/BidirectionalTool.ts index 065ff8de90..1f003d1a05 100644 --- a/packages/tools/src/tools/annotation/BidirectionalTool.ts +++ b/packages/tools/src/tools/annotation/BidirectionalTool.ts @@ -37,6 +37,7 @@ import { PublicToolProps, ToolProps, InteractionTypes, + SVGDrawingHelper, } from '../../types'; import { BidirectionalAnnotation } from '../../types/ToolSpecificAnnotationTypes'; @@ -951,14 +952,15 @@ export default class BidirectionalTool extends AnnotationTool { */ renderAnnotation = ( enabledElement: Types.IEnabledElement, - svgDrawingHelper: any - ): void => { + svgDrawingHelper: SVGDrawingHelper + ): boolean => { + let renderStatus = true; const { viewport } = enabledElement; const { element } = viewport; let annotations = getAnnotations(viewport.element, this.getToolName()); if (!annotations?.length) { - return; + return renderStatus; } annotations = this.filterInteractableAnnotationsForElement( @@ -967,7 +969,7 @@ export default class BidirectionalTool extends AnnotationTool { ); if (!annotations?.length) { - return; + return renderStatus; } const targetId = this.getTargetId(viewport); @@ -1010,7 +1012,7 @@ export default class BidirectionalTool extends AnnotationTool { // If rendering engine has been destroyed while rendering if (!viewport.getRenderingEngine()) { console.warn('Rendering Engine has been destroyed'); - return; + return renderStatus; } let activeHandleCanvasCoords; @@ -1070,6 +1072,8 @@ export default class BidirectionalTool extends AnnotationTool { } ); + renderStatus = true; + const textLines = this._getTextLines(data, targetId); if (!textLines || textLines.length === 0) { @@ -1109,6 +1113,8 @@ export default class BidirectionalTool extends AnnotationTool { bottomRight: viewport.canvasToWorld([left + width, top + height]), }; } + + return renderStatus; }; _movingLongAxisWouldPutItThroughShortAxis = ( diff --git a/packages/tools/src/tools/annotation/DragProbeTool.ts b/packages/tools/src/tools/annotation/DragProbeTool.ts index 05ffc3fa81..75f7aec40e 100644 --- a/packages/tools/src/tools/annotation/DragProbeTool.ts +++ b/packages/tools/src/tools/annotation/DragProbeTool.ts @@ -8,7 +8,12 @@ import { } from '../../drawingSvg'; import { getViewportIdsWithToolToRender } from '../../utilities/viewportFilters'; import { hideElementCursor } from '../../cursors/elementCursor'; -import { EventTypes, PublicToolProps, ToolProps } from '../../types'; +import { + EventTypes, + PublicToolProps, + SVGDrawingHelper, + ToolProps, +} from '../../types'; import triggerAnnotationRenderForViewportIds from '../../utilities/triggerAnnotationRenderForViewportIds'; import ProbeTool from './ProbeTool'; import { ProbeAnnotation } from '../../types/ToolSpecificAnnotationTypes'; @@ -105,12 +110,13 @@ export default class DragProbeTool extends ProbeTool { renderAnnotation = ( enabledElement: Types.IEnabledElement, - svgDrawingHelper: any - ): void => { + svgDrawingHelper: SVGDrawingHelper + ): boolean => { + let renderStatus = false; const { viewport } = enabledElement; if (!this.editData) { - return; + return renderStatus; } const targetId = this.getTargetId(viewport); @@ -147,7 +153,7 @@ export default class DragProbeTool extends ProbeTool { // If rendering engine has been destroyed while rendering if (!viewport.getRenderingEngine()) { console.warn('Rendering Engine has been destroyed'); - return; + return renderStatus; } const handleGroupUID = '0'; @@ -160,6 +166,8 @@ export default class DragProbeTool extends ProbeTool { { color } ); + renderStatus = true; + const textLines = this._getTextLines(data, targetId); if (textLines) { const textCanvasCoordinates = [ @@ -177,5 +185,7 @@ export default class DragProbeTool extends ProbeTool { this.getLinkedTextBoxStyle(styleSpecifier, annotation) ); } + + return renderStatus; }; } diff --git a/packages/tools/src/tools/annotation/EllipticalROITool.ts b/packages/tools/src/tools/annotation/EllipticalROITool.ts index d67f30edad..91905bef83 100644 --- a/packages/tools/src/tools/annotation/EllipticalROITool.ts +++ b/packages/tools/src/tools/annotation/EllipticalROITool.ts @@ -42,6 +42,7 @@ import { PublicToolProps, ToolProps, InteractionTypes, + SVGDrawingHelper, } from '../../types'; import { EllipticalROIAnnotation } from '../../types/ToolSpecificAnnotationTypes'; @@ -704,15 +705,16 @@ export default class EllipticalROITool extends AnnotationTool { */ renderAnnotation = ( enabledElement: Types.IEnabledElement, - svgDrawingHelper: any - ): void => { + svgDrawingHelper: SVGDrawingHelper + ): boolean => { + let renderStatus = false; const { viewport } = enabledElement; const { element } = viewport; let annotations = getAnnotations(element, this.getToolName()); if (!annotations?.length) { - return; + return renderStatus; } annotations = this.filterInteractableAnnotationsForElement( @@ -721,7 +723,7 @@ export default class EllipticalROITool extends AnnotationTool { ); if (!annotations?.length) { - return; + return renderStatus; } const targetId = this.getTargetId(viewport); @@ -812,7 +814,7 @@ export default class EllipticalROITool extends AnnotationTool { // If rendering engine has been destroyed while rendering if (!viewport.getRenderingEngine()) { console.warn('Rendering Engine has been destroyed'); - return; + return renderStatus; } let activeHandleCanvasCoords; @@ -857,6 +859,8 @@ export default class EllipticalROITool extends AnnotationTool { } ); + renderStatus = true; + const textLines = this._getTextLines(data, targetId); if (!textLines || textLines.length === 0) { continue; @@ -897,6 +901,8 @@ export default class EllipticalROITool extends AnnotationTool { bottomRight: viewport.canvasToWorld([left + width, top + height]), }; } + + return renderStatus; }; _getTextLines = (data, targetId) => { diff --git a/packages/tools/src/tools/annotation/LengthTool.ts b/packages/tools/src/tools/annotation/LengthTool.ts index 256e5788fe..58128710a5 100644 --- a/packages/tools/src/tools/annotation/LengthTool.ts +++ b/packages/tools/src/tools/annotation/LengthTool.ts @@ -44,6 +44,7 @@ import { PublicToolProps, ToolProps, InteractionTypes, + SVGDrawingHelper, } from '../../types'; import { LengthAnnotation } from '../../types/ToolSpecificAnnotationTypes'; import { StyleSpecifier } from '../../types/AnnotationStyle'; @@ -527,8 +528,9 @@ class LengthTool extends AnnotationTool { */ renderAnnotation = ( enabledElement: Types.IEnabledElement, - svgDrawingHelper: any - ): void => { + svgDrawingHelper: SVGDrawingHelper + ): boolean => { + let renderStatus = false; const { viewport } = enabledElement; const { element } = viewport; @@ -536,7 +538,7 @@ class LengthTool extends AnnotationTool { // Todo: We don't need this anymore, filtering happens in triggerAnnotationRender if (!annotations?.length) { - return; + return renderStatus; } annotations = this.filterInteractableAnnotationsForElement( @@ -545,7 +547,7 @@ class LengthTool extends AnnotationTool { ); if (!annotations?.length) { - return; + return renderStatus; } const targetId = this.getTargetId(viewport); @@ -631,10 +633,12 @@ class LengthTool extends AnnotationTool { } ); + renderStatus = true; + // If rendering engine has been destroyed while rendering if (!viewport.getRenderingEngine()) { console.warn('Rendering Engine has been destroyed'); - return; + return renderStatus; } const textLines = this._getTextLines(data, targetId); @@ -672,6 +676,8 @@ class LengthTool extends AnnotationTool { bottomRight: viewport.canvasToWorld([left + width, top + height]), }; } + + return renderStatus; }; // text line for the current active length annotation diff --git a/packages/tools/src/tools/annotation/PlanarFreehandROITool.ts b/packages/tools/src/tools/annotation/PlanarFreehandROITool.ts index 03a7959a42..0d352d2472 100644 --- a/packages/tools/src/tools/annotation/PlanarFreehandROITool.ts +++ b/packages/tools/src/tools/annotation/PlanarFreehandROITool.ts @@ -36,6 +36,7 @@ import { PublicToolProps, ToolProps, InteractionTypes, + SVGDrawingHelper, } from '../../types'; import { PlanarFreehandROIAnnotation } from '../../types/ToolSpecificAnnotationTypes'; import { PlanarFreehandROICommonData } from '../../utilities/math/polyline/planarFreehandROIInternalTypes'; @@ -110,22 +111,22 @@ class PlanarFreehandROITool extends AnnotationTool { private renderContour: ( enabledElement: Types.IEnabledElement, - svgDrawingHelper: any, + svgDrawingHelper: SVGDrawingHelper, annotation: PlanarFreehandROIAnnotation ) => void; private renderContourBeingDrawn: ( enabledElement: Types.IEnabledElement, - svgDrawingHelper: any, + svgDrawingHelper: SVGDrawingHelper, annotation: PlanarFreehandROIAnnotation ) => void; private renderClosedContourBeingEdited: ( enabledElement: Types.IEnabledElement, - svgDrawingHelper: any, + svgDrawingHelper: SVGDrawingHelper, annotation: PlanarFreehandROIAnnotation ) => void; private renderOpenContourBeingEdited: ( enabledElement: Types.IEnabledElement, - svgDrawingHelper: any, + svgDrawingHelper: SVGDrawingHelper, annotation: PlanarFreehandROIAnnotation ) => void; @@ -503,8 +504,9 @@ class PlanarFreehandROITool extends AnnotationTool { */ renderAnnotation = ( enabledElement: Types.IEnabledElement, - svgDrawingHelper: any - ): void => { + svgDrawingHelper: SVGDrawingHelper + ): boolean => { + const renderStatus = false; const { viewport } = enabledElement; const { element } = viewport; @@ -514,7 +516,7 @@ class PlanarFreehandROITool extends AnnotationTool { // Todo: We don't need this anymore, filtering happens in triggerAnnotationRender if (!annotations?.length) { - return; + return renderStatus; } annotations = this.filterInteractableAnnotationsForElement( @@ -523,7 +525,7 @@ class PlanarFreehandROITool extends AnnotationTool { ) as PlanarFreehandROIAnnotation[]; if (!annotations?.length) { - return; + return renderStatus; } const isDrawing = this.isDrawing; @@ -537,7 +539,7 @@ class PlanarFreehandROITool extends AnnotationTool { this.renderContour(enabledElement, svgDrawingHelper, annotation) ); - return; + return renderStatus; } // One of the annotations will need special rendering treatment, render all @@ -574,6 +576,9 @@ class PlanarFreehandROITool extends AnnotationTool { this.renderContour(enabledElement, svgDrawingHelper, annotation); } }); + + // Todo: return boolean flag for each rendering route in the planar tool. + return true; }; } diff --git a/packages/tools/src/tools/annotation/ProbeTool.ts b/packages/tools/src/tools/annotation/ProbeTool.ts index 52c8b3728c..a84f4e8a9d 100644 --- a/packages/tools/src/tools/annotation/ProbeTool.ts +++ b/packages/tools/src/tools/annotation/ProbeTool.ts @@ -40,6 +40,7 @@ import { ToolHandle, PublicToolProps, ToolProps, + SVGDrawingHelper, } from '../../types'; import { ProbeAnnotation } from '../../types/ToolSpecificAnnotationTypes'; import { StyleSpecifier } from '../../types/AnnotationStyle'; @@ -395,15 +396,16 @@ export default class ProbeTool extends AnnotationTool { */ renderAnnotation = ( enabledElement: Types.IEnabledElement, - svgDrawingHelper: any - ): void => { + svgDrawingHelper: SVGDrawingHelper + ): boolean => { + let renderStatus = false; const { viewport } = enabledElement; const { element } = viewport; let annotations = getAnnotations(element, this.getToolName()); if (!annotations?.length) { - return; + return renderStatus; } annotations = this.filterInteractableAnnotationsForElement( @@ -412,7 +414,7 @@ export default class ProbeTool extends AnnotationTool { ); if (!annotations?.length) { - return; + return renderStatus; } const targetId = this.getTargetId(viewport); @@ -485,7 +487,7 @@ export default class ProbeTool extends AnnotationTool { // If rendering engine has been destroyed while rendering if (!viewport.getRenderingEngine()) { console.warn('Rendering Engine has been destroyed'); - return; + return renderStatus; } const handleGroupUID = '0'; @@ -498,6 +500,8 @@ export default class ProbeTool extends AnnotationTool { { color } ); + renderStatus = true; + const textLines = this._getTextLines(data, targetId); if (textLines) { const textCanvasCoordinates = [ @@ -516,6 +520,8 @@ export default class ProbeTool extends AnnotationTool { ); } } + + return renderStatus; }; _getTextLines(data, targetId) { diff --git a/packages/tools/src/tools/annotation/RectangleROITool.ts b/packages/tools/src/tools/annotation/RectangleROITool.ts index 4077d67b32..57268dac89 100644 --- a/packages/tools/src/tools/annotation/RectangleROITool.ts +++ b/packages/tools/src/tools/annotation/RectangleROITool.ts @@ -41,6 +41,7 @@ import { ToolProps, PublicToolProps, InteractionTypes, + SVGDrawingHelper, } from '../../types'; import { RectangleROIAnnotation } from '../../types/ToolSpecificAnnotationTypes'; import { @@ -603,15 +604,16 @@ export default class RectangleROITool extends AnnotationTool { */ renderAnnotation = ( enabledElement: Types.IEnabledElement, - svgDrawingHelper: any - ): void => { + svgDrawingHelper: SVGDrawingHelper + ): boolean => { + let renderStatus = false; const { viewport } = enabledElement; const { element } = viewport; let annotations = getAnnotations(element, this.getToolName()); if (!annotations?.length) { - return; + return renderStatus; } annotations = this.filterInteractableAnnotationsForElement( @@ -620,7 +622,7 @@ export default class RectangleROITool extends AnnotationTool { ); if (!annotations?.length) { - return; + return renderStatus; } const targetId = this.getTargetId(viewport); @@ -710,7 +712,7 @@ export default class RectangleROITool extends AnnotationTool { // If rendering engine has been destroyed while rendering if (!viewport.getRenderingEngine()) { console.warn('Rendering Engine has been destroyed'); - return; + return renderStatus; } let activeHandleCanvasCoords; @@ -756,6 +758,8 @@ export default class RectangleROITool extends AnnotationTool { } ); + renderStatus = true; + const textLines = this._getTextLines(data, targetId); if (!textLines || textLines.length === 0) { continue; @@ -793,6 +797,8 @@ export default class RectangleROITool extends AnnotationTool { bottomRight: viewport.canvasToWorld([left + width, top + height]), }; } + + return renderStatus; }; _getRectangleImageCoordinates = ( diff --git a/packages/tools/src/tools/annotation/planarFreehandROITool/renderMethods.ts b/packages/tools/src/tools/annotation/planarFreehandROITool/renderMethods.ts index 4725edbed9..095c42bbd7 100644 --- a/packages/tools/src/tools/annotation/planarFreehandROITool/renderMethods.ts +++ b/packages/tools/src/tools/annotation/planarFreehandROITool/renderMethods.ts @@ -7,6 +7,7 @@ import { polyline } from '../../../utilities/math'; import { findOpenUShapedContourVectorToPeakOnRender } from './findOpenUShapedContourVectorToPeak'; import { PlanarFreehandROIAnnotation } from '../../../types/ToolSpecificAnnotationTypes'; import { StyleSpecifier } from '../../../types/AnnotationStyle'; +import { SVGDrawingHelper } from '../../../types'; const { pointsAreWithinCloseContourProximity } = polyline; @@ -48,7 +49,7 @@ function _getRenderingOptions( */ function renderContour( enabledElement: Types.IEnabledElement, - svgDrawingHelper: any, + svgDrawingHelper: SVGDrawingHelper, annotation: PlanarFreehandROIAnnotation ): void { // Check if the contour is an open contour @@ -94,7 +95,7 @@ function calculateUShapeContourVectorToPeakIfNotPresent( */ function renderClosedContour( enabledElement: Types.IEnabledElement, - svgDrawingHelper: any, + svgDrawingHelper: SVGDrawingHelper, annotation: PlanarFreehandROIAnnotation ): void { const { viewport } = enabledElement; @@ -125,7 +126,7 @@ function renderClosedContour( */ function renderOpenContour( enabledElement: Types.IEnabledElement, - svgDrawingHelper: any, + svgDrawingHelper: SVGDrawingHelper, annotation: PlanarFreehandROIAnnotation ): void { const { viewport } = enabledElement; @@ -203,7 +204,7 @@ function renderOpenContour( function renderOpenUShapedContour( enabledElement: Types.IEnabledElement, - svgDrawingHelper: any, + svgDrawingHelper: SVGDrawingHelper, annotation: PlanarFreehandROIAnnotation ): void { const { viewport } = enabledElement; @@ -259,7 +260,7 @@ function renderOpenUShapedContour( */ function renderContourBeingDrawn( enabledElement: Types.IEnabledElement, - svgDrawingHelper: any, + svgDrawingHelper: SVGDrawingHelper, annotation: PlanarFreehandROIAnnotation ): void { const options = this._getRenderingOptions(enabledElement, annotation); @@ -349,7 +350,7 @@ function renderClosedContourBeingEdited( */ function renderOpenContourBeingEdited( enabledElement: Types.IEnabledElement, - svgDrawingHelper: any, + svgDrawingHelper: SVGDrawingHelper, annotation: PlanarFreehandROIAnnotation ): void { const { fusedCanvasPoints } = this.editData; diff --git a/packages/tools/src/tools/base/AnnotationTool.ts b/packages/tools/src/tools/base/AnnotationTool.ts index 0d5ab45786..810fe30c19 100644 --- a/packages/tools/src/tools/base/AnnotationTool.ts +++ b/packages/tools/src/tools/base/AnnotationTool.ts @@ -19,6 +19,7 @@ import { EventTypes, ToolHandle, InteractionTypes, + SVGDrawingHelper, } from '../../types'; import triggerAnnotationRender from '../../utilities/triggerAnnotationRender'; import filterAnnotationsForDisplay from '../../utilities/planar/filterAnnotationsForDisplay'; @@ -62,7 +63,7 @@ abstract class AnnotationTool extends BaseTool { */ abstract renderAnnotation( enabledElement: Types.IEnabledElement, - svgDrawingHelper: any + svgDrawingHelper: SVGDrawingHelper ); /** diff --git a/packages/tools/src/tools/segmentation/BrushTool.ts b/packages/tools/src/tools/segmentation/BrushTool.ts index 65062155a6..08a057ab60 100644 --- a/packages/tools/src/tools/segmentation/BrushTool.ts +++ b/packages/tools/src/tools/segmentation/BrushTool.ts @@ -1,7 +1,12 @@ import { cache, getEnabledElement, StackViewport } from '@cornerstonejs/core'; import type { Types } from '@cornerstonejs/core'; -import type { PublicToolProps, ToolProps, EventTypes } from '../../types'; +import type { + PublicToolProps, + ToolProps, + EventTypes, + SVGDrawingHelper, +} from '../../types'; import { BaseTool } from '../base'; import { fillInsideCircle } from './strategies/fillCircle'; @@ -416,7 +421,7 @@ export default class BrushTool extends BaseTool { renderAnnotation( enabledElement: Types.IEnabledElement, - svgDrawingHelper: any + svgDrawingHelper: SVGDrawingHelper ): void { if (!this._hoverData) { return; diff --git a/packages/tools/src/tools/segmentation/CircleScissorsTool.ts b/packages/tools/src/tools/segmentation/CircleScissorsTool.ts index 9cba5bf0be..b54754e391 100644 --- a/packages/tools/src/tools/segmentation/CircleScissorsTool.ts +++ b/packages/tools/src/tools/segmentation/CircleScissorsTool.ts @@ -2,7 +2,12 @@ import { cache, getEnabledElement, StackViewport } from '@cornerstonejs/core'; import type { Types } from '@cornerstonejs/core'; import { BaseTool } from '../base'; -import { PublicToolProps, ToolProps, EventTypes } from '../../types'; +import { + PublicToolProps, + ToolProps, + EventTypes, + SVGDrawingHelper, +} from '../../types'; import { fillInsideCircle } from './strategies/fillCircle'; import { Events } from '../../enums'; @@ -298,17 +303,18 @@ export default class CircleScissorsTool extends BaseTool { */ renderAnnotation = ( enabledElement: Types.IEnabledElement, - svgDrawingHelper: any - ): void => { + svgDrawingHelper: SVGDrawingHelper + ): boolean => { + let renderStatus = false; if (!this.editData) { - return; + return renderStatus; } const { viewport } = enabledElement; const { viewportIdsToRender } = this.editData; if (!viewportIdsToRender.includes(viewport.id)) { - return; + return renderStatus; } const { annotation } = this.editData; @@ -336,7 +342,7 @@ export default class CircleScissorsTool extends BaseTool { // If rendering engine has been destroyed while rendering if (!viewport.getRenderingEngine()) { console.warn('Rendering Engine has been destroyed'); - return; + return renderStatus; } const circleUID = '0'; @@ -350,5 +356,8 @@ export default class CircleScissorsTool extends BaseTool { color, } ); + + renderStatus = true; + return renderStatus; }; } diff --git a/packages/tools/src/tools/segmentation/RectangleROIStartEndThresholdTool.ts b/packages/tools/src/tools/segmentation/RectangleROIStartEndThresholdTool.ts index c51d8efe9d..de71aadf1a 100644 --- a/packages/tools/src/tools/segmentation/RectangleROIStartEndThresholdTool.ts +++ b/packages/tools/src/tools/segmentation/RectangleROIStartEndThresholdTool.ts @@ -24,7 +24,12 @@ import { isAnnotationVisible } from '../../stateManagement/annotation/annotation import { hideElementCursor } from '../../cursors/elementCursor'; import triggerAnnotationRenderForViewportIds from '../../utilities/triggerAnnotationRenderForViewportIds'; -import { PublicToolProps, ToolProps, EventTypes } from '../../types'; +import { + PublicToolProps, + ToolProps, + EventTypes, + SVGDrawingHelper, +} from '../../types'; import { RectangleROIStartEndThresholdAnnotation } from '../../types/ToolSpecificAnnotationTypes'; import RectangleROITool from '../annotation/RectangleROITool'; import { StyleSpecifier } from '../../types/AnnotationStyle'; @@ -296,15 +301,16 @@ export default class RectangleROIStartEndThresholdTool extends RectangleROITool */ renderAnnotation = ( enabledElement: Types.IEnabledElement, - svgDrawingHelper: any - ): void => { + svgDrawingHelper: SVGDrawingHelper + ): boolean => { + let renderStatus = false; const annotations = getAnnotations( enabledElement.viewport.element, this.getToolName() ); if (!annotations?.length) { - return; + return renderStatus; } const { viewport } = enabledElement; @@ -358,7 +364,7 @@ export default class RectangleROIStartEndThresholdTool extends RectangleROITool // If rendering engine has been destroyed while rendering if (!viewport.getRenderingEngine()) { console.warn('Rendering Engine has been destroyed'); - return; + return renderStatus; } let activeHandleCanvasCoords; @@ -410,7 +416,11 @@ export default class RectangleROIStartEndThresholdTool extends RectangleROITool lineWidth, } ); + + renderStatus = true; } + + return renderStatus; }; _getEndSliceIndex( diff --git a/packages/tools/src/tools/segmentation/RectangleROIThresholdTool.ts b/packages/tools/src/tools/segmentation/RectangleROIThresholdTool.ts index 792915f93e..f0e66d1f80 100644 --- a/packages/tools/src/tools/segmentation/RectangleROIThresholdTool.ts +++ b/packages/tools/src/tools/segmentation/RectangleROIThresholdTool.ts @@ -20,7 +20,12 @@ import { getViewportIdsWithToolToRender } from '../../utilities/viewportFilters' import { hideElementCursor } from '../../cursors/elementCursor'; import triggerAnnotationRenderForViewportIds from '../../utilities/triggerAnnotationRenderForViewportIds'; import { isAnnotationVisible } from '../../stateManagement/annotation/annotationVisibility'; -import { PublicToolProps, ToolProps, EventTypes } from '../../types'; +import { + PublicToolProps, + ToolProps, + EventTypes, + SVGDrawingHelper, +} from '../../types'; import { RectangleROIThresholdAnnotation } from '../../types/ToolSpecificAnnotationTypes'; import { AnnotationModifiedEventDetail } from '../../types/EventTypes'; import RectangleROITool from '../annotation/RectangleROITool'; @@ -164,14 +169,15 @@ export default class RectangleROIThresholdTool extends RectangleROITool { */ renderAnnotation = ( enabledElement: Types.IEnabledElement, - svgDrawingHelper: any - ): void => { + svgDrawingHelper: SVGDrawingHelper + ): boolean => { + let renderStatus = false; const { viewport, renderingEngineId } = enabledElement; const { element } = viewport; let annotations = getAnnotations(element, this.getToolName()); if (!annotations?.length) { - return; + return renderStatus; } annotations = this.filterInteractableAnnotationsForElement( @@ -180,7 +186,7 @@ export default class RectangleROIThresholdTool extends RectangleROITool { ); if (!annotations?.length) { - return; + return renderStatus; } const styleSpecifier: StyleSpecifier = { @@ -204,7 +210,7 @@ export default class RectangleROIThresholdTool extends RectangleROITool { // If rendering engine has been destroyed while rendering if (!viewport.getRenderingEngine()) { console.warn('Rendering Engine has been destroyed'); - return; + return renderStatus; } // Todo: This is not correct way to add the event trigger, @@ -262,6 +268,10 @@ export default class RectangleROIThresholdTool extends RectangleROITool { lineWidth, } ); + + renderStatus = true; } + + return renderStatus; }; } diff --git a/packages/tools/src/tools/segmentation/RectangleScissorsTool.ts b/packages/tools/src/tools/segmentation/RectangleScissorsTool.ts index e04fbfda17..f2183fb1ac 100644 --- a/packages/tools/src/tools/segmentation/RectangleScissorsTool.ts +++ b/packages/tools/src/tools/segmentation/RectangleScissorsTool.ts @@ -2,7 +2,12 @@ import { cache, getEnabledElement, StackViewport } from '@cornerstonejs/core'; import type { Types } from '@cornerstonejs/core'; import { BaseTool } from '../base'; -import { PublicToolProps, ToolProps, EventTypes } from '../../types'; +import { + PublicToolProps, + ToolProps, + EventTypes, + SVGDrawingHelper, +} from '../../types'; import { fillInsideRectangle } from './strategies/fillRectangle'; import { eraseInsideRectangle } from './strategies/eraseRectangle'; import { getViewportIdsWithToolToRender } from '../../utilities/viewportFilters'; @@ -333,10 +338,11 @@ export default class RectangleScissorsTool extends BaseTool { */ renderAnnotation = ( enabledElement: Types.IEnabledElement, - svgDrawingHelper: any - ): void => { + svgDrawingHelper: SVGDrawingHelper + ): boolean => { + let renderStatus = false; if (!this.editData) { - return; + return renderStatus; } const { viewport } = enabledElement; @@ -354,7 +360,7 @@ export default class RectangleScissorsTool extends BaseTool { // If rendering engine has been destroyed while rendering if (!viewport.getRenderingEngine()) { console.warn('Rendering Engine has been destroyed'); - return; + return renderStatus; } const rectangleUID = '0'; @@ -368,5 +374,9 @@ export default class RectangleScissorsTool extends BaseTool { color, } ); + + renderStatus = true; + + return renderStatus; }; } diff --git a/packages/tools/src/tools/segmentation/SphereScissorsTool.ts b/packages/tools/src/tools/segmentation/SphereScissorsTool.ts index 4ee1b5f3ee..6dd9f58e91 100644 --- a/packages/tools/src/tools/segmentation/SphereScissorsTool.ts +++ b/packages/tools/src/tools/segmentation/SphereScissorsTool.ts @@ -2,7 +2,12 @@ import { cache, getEnabledElement, StackViewport } from '@cornerstonejs/core'; import type { Types } from '@cornerstonejs/core'; import { BaseTool } from '../base'; -import { PublicToolProps, ToolProps, EventTypes } from '../../types'; +import { + PublicToolProps, + ToolProps, + EventTypes, + SVGDrawingHelper, +} from '../../types'; import { fillInsideSphere } from './strategies/fillSphere'; import { Events } from '../../enums'; @@ -299,17 +304,18 @@ export default class SphereScissorsTool extends BaseTool { */ renderAnnotation = ( enabledElement: Types.IEnabledElement, - svgDrawingHelper: any - ): void => { + svgDrawingHelper: SVGDrawingHelper + ): boolean => { + let renderStatus = false; if (!this.editData) { - return; + return renderStatus; } const { viewport } = enabledElement; const { viewportIdsToRender } = this.editData; if (!viewportIdsToRender.includes(viewport.id)) { - return; + return renderStatus; } const { annotation } = this.editData; @@ -337,7 +343,7 @@ export default class SphereScissorsTool extends BaseTool { // If rendering engine has been destroyed while rendering if (!viewport.getRenderingEngine()) { console.warn('Rendering Engine has been destroyed'); - return; + return renderStatus; } const circleUID = '0'; @@ -351,5 +357,9 @@ export default class SphereScissorsTool extends BaseTool { color, } ); + + renderStatus = true; + + return renderStatus; }; } diff --git a/packages/tools/src/types/SVGDrawingHelper.ts b/packages/tools/src/types/SVGDrawingHelper.ts new file mode 100644 index 0000000000..37de5bacf7 --- /dev/null +++ b/packages/tools/src/types/SVGDrawingHelper.ts @@ -0,0 +1,10 @@ +type SVGDrawingHelper = { + svgLayerElement: HTMLDivElement; + svgNodeCacheForCanvas: Record; + getSvgNode: (cacheKey: string) => SVGGElement | undefined; + appendNode: (svgNode: SVGElement, cacheKey: string) => void; + setNodeTouched: (cacheKey: string) => void; + clearUntouched: () => void; +}; + +export default SVGDrawingHelper; diff --git a/packages/tools/src/types/index.ts b/packages/tools/src/types/index.ts index 756a24734d..257caeecaf 100644 --- a/packages/tools/src/types/index.ts +++ b/packages/tools/src/types/index.ts @@ -24,6 +24,7 @@ import type { SVGCursorDescriptor, SVGPoint } from './CursorTypes'; import type JumpToSliceOptions from './JumpToSliceOptions'; import type ScrollOptions from './ScrollOptions'; import type BoundsIJK from './BoundsIJK'; +import type SVGDrawingHelper from './SVGDrawingHelper'; import type * as CINETypes from './CINETypes'; import type { Color, @@ -86,4 +87,5 @@ export type { // CINE CINETypes, BoundsIJK, + SVGDrawingHelper, }; diff --git a/packages/tools/src/utilities/triggerAnnotationRender.ts b/packages/tools/src/utilities/triggerAnnotationRender.ts index 2651008c6d..d85d4a6751 100644 --- a/packages/tools/src/utilities/triggerAnnotationRender.ts +++ b/packages/tools/src/utilities/triggerAnnotationRender.ts @@ -6,9 +6,7 @@ import { import { Events, ToolModes } from '../enums'; import { draw as drawSvg } from '../drawingSvg'; import getToolsWithModesForElement from './getToolsWithModesForElement'; -import SegmentationDisplayTool from '../tools/displayTools/SegmentationDisplayTool'; import { AnnotationRenderedEventDetail } from '../types/EventTypes'; - const { Active, Passive, Enabled } = ToolModes; /** @@ -160,26 +158,33 @@ class AnnotationRenderingEngine { }; // const enabledToolsWithAnnotations = enabledTools.filter((tool) => { - // const annotations = getAnnotations(element, (tool.constructor as typeof BaseTool).toolName) - // return annotations && annotations.length - // }) + // const annotations = getAnnotations(element, tool.getToolName()); + // return annotations && annotations.length; + // }); drawSvg(element, (svgDrawingHelper) => { + let anyRendered = false; const handleDrawSvg = (tool) => { - // Todo: we should not have the need to check tool if it is instance - // of SegmentationDisplayTool, but right now SegmentationScissors - // are instance of BaseTool and we cannot simply check if tool is - // instance of AnnotationTool - if ( - !(tool instanceof SegmentationDisplayTool) && - tool.renderAnnotation - ) { - tool.renderAnnotation(enabledElement, svgDrawingHelper); - triggerEvent(element, Events.ANNOTATION_RENDERED, { ...eventDetail }); + if (tool.renderAnnotation) { + const rendered = tool.renderAnnotation( + enabledElement, + svgDrawingHelper + ); + anyRendered = anyRendered || rendered; } }; + /** + * We should be able to filter tools that don't have annotations, but + * currently some of tools have renderAnnotation method BUT + * don't keep annotation in the state, so if we do so, the tool will not be + * rendered. + */ enabledTools.forEach(handleDrawSvg); + + if (anyRendered) { + triggerEvent(element, Events.ANNOTATION_RENDERED, { ...eventDetail }); + } }); }