From e192e6d4651901f43c4c2d28892b9e877d283662 Mon Sep 17 00:00:00 2001 From: rodrigobasilio2022 Date: Wed, 7 May 2025 08:09:56 -0300 Subject: [PATCH 1/5] fix(coordinates): fix wsi coordinate mapping --- .../core/src/RenderingEngine/WSIViewport.ts | 111 ++++++++++++------ 1 file changed, 78 insertions(+), 33 deletions(-) diff --git a/packages/core/src/RenderingEngine/WSIViewport.ts b/packages/core/src/RenderingEngine/WSIViewport.ts index 441a97719a..9ba8f6a9ec 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, @@ -23,6 +23,7 @@ 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 EVENT_POSTRENDER = 'postrender'; /** @@ -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 = { @@ -432,6 +454,48 @@ 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 { + const affine = this.viewer[Symbol.for('affine')]; + 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 { + const sliceCoords = WSIUtilFunctions?.applyTransform({ + coordinate: [point[0], point[1]], + affine: this.viewer[Symbol.for('affine')], + }); + return [sliceCoords[0], sliceCoords[1], 0] as Point3; + } + + public worldToIndex(point: Point3): Point2 { + const { worldToIndex: worldToIndexMatrix } = this.computeTransforms(); + const imageCoord = vec3.create(); + vec3.transformMat4(imageCoord, point, worldToIndexMatrix); + return [imageCoord[0], imageCoord[1]] as Point2; + } + + public indexToWorld(point: Point2): Point3 { + const { indexToWorld: indexToWorldMatrix } = this.computeTransforms(); + const worldPos = vec3.create(); + const point3D = vec3.fromValues(point[0], point[1], 0); + vec3.transformMat4(worldPos, point3D, indexToWorldMatrix); + return [worldPos[0], worldPos[1], worldPos[2]] as Point3; + } + /** * Converts a VideoViewport canvas coordinate to a video coordinate. * @@ -444,20 +508,7 @@ class WSIViewport extends Viewport { } // 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; + return this.indexToWorld([px, py, 0]); }; /** @@ -470,20 +521,10 @@ 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); // pixel to canvas - const canvasPoint = this.indexToCanvas(indexPoint); + const canvasPoint = this.indexToCanvas([indexPoint[0], indexPoint[1]]); return canvasPoint; }; @@ -518,6 +559,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) => { @@ -615,12 +657,15 @@ class WSIViewport extends Viewport { protected canvasToIndex = (canvasPos: Point2): Point2 => { const transform = this.getTransform(); transform.invert(); - return transform.transformPoint( + const indexPoint = transform.transformPoint( canvasPos.map((it) => it * devicePixelRatio) as Point2 ); + indexPoint[1] = indexPoint[1] * -1; // Reverse y axis to match CS3D + return indexPoint as Point2; }; protected indexToCanvas = (indexPos: Point2): Point2 => { + indexPos[1] = indexPos[1] * -1; // Reverse y axis to match CS3D const transform = this.getTransform(); return transform .transformPoint(indexPos) From 3fdac7b20987163c69d6868b02a0ee589cfbefce Mon Sep 17 00:00:00 2001 From: rodrigobasilio2022 Date: Wed, 7 May 2025 10:42:14 -0300 Subject: [PATCH 2/5] fix function signature --- packages/core/src/RenderingEngine/WSIViewport.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/core/src/RenderingEngine/WSIViewport.ts b/packages/core/src/RenderingEngine/WSIViewport.ts index 2ed452c532..e43999d6d9 100644 --- a/packages/core/src/RenderingEngine/WSIViewport.ts +++ b/packages/core/src/RenderingEngine/WSIViewport.ts @@ -477,17 +477,17 @@ class WSIViewport extends Viewport { return [sliceCoords[0], sliceCoords[1], 0] as Point3; } - public worldToIndex(point: Point3): Point2 { + public worldToIndex(point: Point3): Point3 { const { worldToIndex: worldToIndexMatrix } = this.computeTransforms(); const imageCoord = vec3.create(); vec3.transformMat4(imageCoord, point, worldToIndexMatrix); - return [imageCoord[0], imageCoord[1]] as Point2; + return imageCoord as Point3; } - public indexToWorld(point: Point2): Point3 { + public indexToWorld(point: Point3): Point3 { const { indexToWorld: indexToWorldMatrix } = this.computeTransforms(); const worldPos = vec3.create(); - const point3D = vec3.fromValues(point[0], point[1], 0); + const point3D = vec3.fromValues(...point); vec3.transformMat4(worldPos, point3D, indexToWorldMatrix); return [worldPos[0], worldPos[1], worldPos[2]] as Point3; } From c1623699fafc1a68b71dc1c1f9114902c7790a13 Mon Sep 17 00:00:00 2001 From: rodrigobasilio2022 Date: Wed, 7 May 2025 10:52:17 -0300 Subject: [PATCH 3/5] REmoving -1 multiplication i Y axis --- packages/core/src/RenderingEngine/WSIViewport.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/core/src/RenderingEngine/WSIViewport.ts b/packages/core/src/RenderingEngine/WSIViewport.ts index e43999d6d9..a24687a03c 100644 --- a/packages/core/src/RenderingEngine/WSIViewport.ts +++ b/packages/core/src/RenderingEngine/WSIViewport.ts @@ -656,12 +656,10 @@ class WSIViewport extends Viewport { const indexPoint = transform.transformPoint( canvasPos.map((it) => it * devicePixelRatio) as Point2 ); - indexPoint[1] = indexPoint[1] * -1; // Reverse y axis to match CS3D return indexPoint as Point2; }; protected indexToCanvas = (indexPos: Point2): Point2 => { - indexPos[1] = indexPos[1] * -1; // Reverse y axis to match CS3D const transform = this.getTransform(); return transform .transformPoint(indexPos) From 919be4d4b55994f16b568ec65c77240b8db78669 Mon Sep 17 00:00:00 2001 From: rodrigobasilio2022 Date: Wed, 7 May 2025 12:18:57 -0300 Subject: [PATCH 4/5] Add flipping in index conversions --- .../core/src/RenderingEngine/WSIViewport.ts | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/packages/core/src/RenderingEngine/WSIViewport.ts b/packages/core/src/RenderingEngine/WSIViewport.ts index a24687a03c..125f6125d0 100644 --- a/packages/core/src/RenderingEngine/WSIViewport.ts +++ b/packages/core/src/RenderingEngine/WSIViewport.ts @@ -9,7 +9,6 @@ import type { CPUIImageData, ViewportInput, BoundsIJK, - CPUImageData, } from '../types'; import uuidv4 from '../utilities/uuidv4'; import * as metaData from '../metaData'; @@ -394,7 +393,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 { @@ -503,8 +506,9 @@ class WSIViewport extends Viewport { return; } // compute the pixel coordinate in the image - const [px, py] = this.canvasToIndex(canvasPos); - return this.indexToWorld([px, py, 0]); + const indexPoint = this.canvasToIndex(canvasPos); + indexPoint[1] = -indexPoint[1]; // flip y axis to match canvas coordinates + return this.indexToWorld(indexPoint); }; /** @@ -518,9 +522,10 @@ class WSIViewport extends Viewport { return; } const indexPoint = this.worldToIndex(worldPos); + indexPoint[1] = -indexPoint[1]; // flip y axis to match canvas coordinates // pixel to canvas - const canvasPoint = this.indexToCanvas([indexPoint[0], indexPoint[1]]); + const canvasPoint = this.indexToCanvas([indexPoint[0], indexPoint[1], 0]); return canvasPoint; }; @@ -650,19 +655,19 @@ class WSIViewport extends Viewport { public getRotation = () => 0; - protected canvasToIndex = (canvasPos: Point2): Point2 => { + protected canvasToIndex = (canvasPos: Point2): Point3 => { const transform = this.getTransform(); transform.invert(); const indexPoint = transform.transformPoint( canvasPos.map((it) => it * devicePixelRatio) as Point2 ); - return indexPoint 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; }; From 24332e721e61548faa1f4cb46f15bc46adeb6b11 Mon Sep 17 00:00:00 2001 From: rodrigobasilio2022 Date: Thu, 8 May 2025 05:59:45 -0300 Subject: [PATCH 5/5] apply review suggestions --- .../core/src/RenderingEngine/WSIViewport.ts | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/packages/core/src/RenderingEngine/WSIViewport.ts b/packages/core/src/RenderingEngine/WSIViewport.ts index 125f6125d0..1396e7216d 100644 --- a/packages/core/src/RenderingEngine/WSIViewport.ts +++ b/packages/core/src/RenderingEngine/WSIViewport.ts @@ -24,6 +24,7 @@ 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 @@ -459,8 +460,11 @@ class WSIViewport extends Viewport { * @returns */ public worldToIndexWSI(point: Point3): Point2 { - const affine = this.viewer[Symbol.for('affine')]; - const pixelCoords = WSIUtilFunctions?.applyInverseTransform({ + if (!WSIUtilFunctions) { + return; + } + const affine = this.viewer[affineSymbol]; + const pixelCoords = WSIUtilFunctions.applyInverseTransform({ coordinate: [point[0], point[1]], affine, }); @@ -473,9 +477,12 @@ class WSIViewport extends Viewport { * @returns */ public indexToWorldWSI(point: Point2): Point3 { - const sliceCoords = WSIUtilFunctions?.applyTransform({ + if (!WSIUtilFunctions) { + return; + } + const sliceCoords = WSIUtilFunctions.applyTransform({ coordinate: [point[0], point[1]], - affine: this.viewer[Symbol.for('affine')], + affine: this.viewer[affineSymbol], }); return [sliceCoords[0], sliceCoords[1], 0] as Point3; } @@ -560,7 +567,7 @@ class WSIViewport extends Viewport { this.microscopyElement.innerText = 'Loading'; this.imageIds = imageIds; const DicomMicroscopyViewer = await WSIViewport.getDicomMicroscopyViewer(); - WSIUtilFunctions = DicomMicroscopyViewer.utils; + WSIUtilFunctions ||= DicomMicroscopyViewer.utils; this.frameOfReferenceUID = null; const metadataDicomweb = this.imageIds.map((imageId) => {