diff --git a/packages/core/src/RenderingEngine/WSIViewport.ts b/packages/core/src/RenderingEngine/WSIViewport.ts index 9ed1dba7e..1396e7216 100644 --- a/packages/core/src/RenderingEngine/WSIViewport.ts +++ b/packages/core/src/RenderingEngine/WSIViewport.ts @@ -1,4 +1,4 @@ -import { vec3 } from 'gl-matrix'; +import { vec3, mat4 } from 'gl-matrix'; import { Events as EVENTS, MetadataModules } from '../enums'; import type { WSIViewportProperties, @@ -9,7 +9,6 @@ import type { CPUIImageData, ViewportInput, BoundsIJK, - CPUImageData, } from '../types'; import uuidv4 from '../utilities/uuidv4'; import * as metaData from '../metaData'; @@ -23,7 +22,9 @@ import { pointInShapeCallback } from '../utilities/pointInShapeCallback'; import microscopyViewportCss from '../constants/microscopyViewportCss'; import type { DataSetOptions } from '../types/IViewport'; +let WSIUtilFunctions = null; const _map = Symbol.for('map'); +const affineSymbol = Symbol.for('affine'); const EVENT_POSTRENDER = 'postrender'; /** * A viewport which shows a microscopy view using the dicom-microscopy-viewer @@ -232,6 +233,30 @@ class WSIViewport extends Viewport { return null; } + public computeTransforms() { + const indexToWorld = mat4.create(); + const worldToIndex = mat4.create(); + + mat4.fromTranslation(indexToWorld, this.metadata.origin); + + indexToWorld[0] = this.metadata.direction[0]; + indexToWorld[1] = this.metadata.direction[1]; + indexToWorld[2] = this.metadata.direction[2]; + + indexToWorld[4] = this.metadata.direction[3]; + indexToWorld[5] = this.metadata.direction[4]; + indexToWorld[6] = this.metadata.direction[5]; + + indexToWorld[8] = this.metadata.direction[6]; + indexToWorld[9] = this.metadata.direction[7]; + indexToWorld[10] = this.metadata.direction[8]; + + mat4.scale(indexToWorld, indexToWorld, this.metadata.spacing); + + mat4.invert(worldToIndex, indexToWorld); + return { indexToWorld, worldToIndex }; + } + public getImageData(): CPUIImageData { const { metadata } = this; if (!metadata) { @@ -247,13 +272,10 @@ class WSIViewport extends Viewport { getScalarData: () => this.getScalarData(), getSpacing: () => metadata.spacing, worldToIndex: (point: Point3) => { - const canvasPoint = this.worldToCanvas(point); - const pixelCoord = this.canvasToIndex(canvasPoint); - return [pixelCoord[0], pixelCoord[1], 0] as Point3; + return this.worldToIndex(point); }, indexToWorld: (point: Point3) => { - const canvasPoint = this.indexToCanvas([point[0], point[1]]); - return this.canvasToWorld(canvasPoint); + return this.indexToWorld(point); }, }; const imageDataReturn = { @@ -372,7 +394,11 @@ class WSIViewport extends Viewport { this.refreshRenderValues(); const { resolution, xSpacing, centerIndex } = this.internalCamera; const canvasToWorldRatio = resolution * xSpacing; - const canvasCenter = this.indexToCanvas(centerIndex.slice(0, 2) as Point2); + const canvasCenter = this.indexToCanvas([ + centerIndex[0], + centerIndex[1], + 0, + ]); const focalPoint = this.canvasToWorld(canvasCenter); return { @@ -428,6 +454,54 @@ class WSIViewport extends Viewport { this.refreshRenderValues(); }; + /** + * Converts a slide coordinate to a image coordinate using WSI utils functions + * @param point + * @returns + */ + public worldToIndexWSI(point: Point3): Point2 { + if (!WSIUtilFunctions) { + return; + } + const affine = this.viewer[affineSymbol]; + const pixelCoords = WSIUtilFunctions.applyInverseTransform({ + coordinate: [point[0], point[1]], + affine, + }); + return [pixelCoords[0], pixelCoords[1]] as Point2; + } + + /** + * Converts a image coordinate to a slide coordinate using WSI utils functions + * @param point + * @returns + */ + public indexToWorldWSI(point: Point2): Point3 { + if (!WSIUtilFunctions) { + return; + } + const sliceCoords = WSIUtilFunctions.applyTransform({ + coordinate: [point[0], point[1]], + affine: this.viewer[affineSymbol], + }); + return [sliceCoords[0], sliceCoords[1], 0] as Point3; + } + + public worldToIndex(point: Point3): Point3 { + const { worldToIndex: worldToIndexMatrix } = this.computeTransforms(); + const imageCoord = vec3.create(); + vec3.transformMat4(imageCoord, point, worldToIndexMatrix); + return imageCoord as Point3; + } + + public indexToWorld(point: Point3): Point3 { + const { indexToWorld: indexToWorldMatrix } = this.computeTransforms(); + const worldPos = vec3.create(); + const point3D = vec3.fromValues(...point); + vec3.transformMat4(worldPos, point3D, indexToWorldMatrix); + return [worldPos[0], worldPos[1], worldPos[2]] as Point3; + } + /** * Converts a VideoViewport canvas coordinate to a video coordinate. * @@ -439,21 +513,9 @@ class WSIViewport extends Viewport { return; } // compute the pixel coordinate in the image - const [px, py] = this.canvasToIndex(canvasPos); - // convert pixel coordinate to world coordinate - const { origin, spacing, direction } = this.getImageData(); - - const worldPos = vec3.fromValues(0, 0, 0); - - // Calculate size of spacing vector in normal direction - const iVector = direction.slice(0, 3) as Point3; - const jVector = direction.slice(3, 6) as Point3; - - // Calculate the world coordinate of the pixel - vec3.scaleAndAdd(worldPos, origin, iVector, px * spacing[0]); - vec3.scaleAndAdd(worldPos, worldPos, jVector, py * spacing[1]); - - return [worldPos[0], worldPos[1], worldPos[2]] as Point3; + const indexPoint = this.canvasToIndex(canvasPos); + indexPoint[1] = -indexPoint[1]; // flip y axis to match canvas coordinates + return this.indexToWorld(indexPoint); }; /** @@ -466,20 +528,11 @@ class WSIViewport extends Viewport { if (!this.metadata) { return; } - const { spacing, direction, origin } = this.metadata; - - const iVector = direction.slice(0, 3) as Point3; - const jVector = direction.slice(3, 6) as Point3; - - const diff = vec3.subtract([0, 0, 0], worldPos, origin); - - const indexPoint: Point2 = [ - vec3.dot(diff, iVector) / spacing[0], - vec3.dot(diff, jVector) / spacing[1], - ]; + const indexPoint = this.worldToIndex(worldPos); + indexPoint[1] = -indexPoint[1]; // flip y axis to match canvas coordinates // pixel to canvas - const canvasPoint = this.indexToCanvas(indexPoint); + const canvasPoint = this.indexToCanvas([indexPoint[0], indexPoint[1], 0]); return canvasPoint; }; @@ -514,6 +567,7 @@ class WSIViewport extends Viewport { this.microscopyElement.innerText = 'Loading'; this.imageIds = imageIds; const DicomMicroscopyViewer = await WSIViewport.getDicomMicroscopyViewer(); + WSIUtilFunctions ||= DicomMicroscopyViewer.utils; this.frameOfReferenceUID = null; const metadataDicomweb = this.imageIds.map((imageId) => { @@ -608,18 +662,19 @@ class WSIViewport extends Viewport { public getRotation = () => 0; - protected canvasToIndex = (canvasPos: Point2): Point2 => { + protected canvasToIndex = (canvasPos: Point2): Point3 => { const transform = this.getTransform(); transform.invert(); - return transform.transformPoint( + const indexPoint = transform.transformPoint( canvasPos.map((it) => it * devicePixelRatio) as Point2 ); + return [indexPoint[0], indexPoint[1], 0] as Point3; }; - protected indexToCanvas = (indexPos: Point2): Point2 => { + protected indexToCanvas = (indexPos: Point3): Point2 => { const transform = this.getTransform(); return transform - .transformPoint(indexPos) + .transformPoint([indexPos[0], indexPos[1]]) .map((it) => it / devicePixelRatio) as Point2; };