Skip to content

Commit fe23258

Browse files
Feat(coordinate mapping) (#2054)
* fix(coordinates): fix wsi coordinate mapping * fix function signature * REmoving -1 multiplication i Y axis * Add flipping in index conversions * apply review suggestions
1 parent 1613662 commit fe23258

File tree

1 file changed

+94
-39
lines changed

1 file changed

+94
-39
lines changed

packages/core/src/RenderingEngine/WSIViewport.ts

Lines changed: 94 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { vec3 } from 'gl-matrix';
1+
import { vec3, mat4 } from 'gl-matrix';
22
import { Events as EVENTS, MetadataModules } from '../enums';
33
import type {
44
WSIViewportProperties,
@@ -9,7 +9,6 @@ import type {
99
CPUIImageData,
1010
ViewportInput,
1111
BoundsIJK,
12-
CPUImageData,
1312
} from '../types';
1413
import uuidv4 from '../utilities/uuidv4';
1514
import * as metaData from '../metaData';
@@ -23,7 +22,9 @@ import { pointInShapeCallback } from '../utilities/pointInShapeCallback';
2322
import microscopyViewportCss from '../constants/microscopyViewportCss';
2423
import type { DataSetOptions } from '../types/IViewport';
2524

25+
let WSIUtilFunctions = null;
2626
const _map = Symbol.for('map');
27+
const affineSymbol = Symbol.for('affine');
2728
const EVENT_POSTRENDER = 'postrender';
2829
/**
2930
* A viewport which shows a microscopy view using the dicom-microscopy-viewer
@@ -232,6 +233,30 @@ class WSIViewport extends Viewport {
232233
return null;
233234
}
234235

236+
public computeTransforms() {
237+
const indexToWorld = mat4.create();
238+
const worldToIndex = mat4.create();
239+
240+
mat4.fromTranslation(indexToWorld, this.metadata.origin);
241+
242+
indexToWorld[0] = this.metadata.direction[0];
243+
indexToWorld[1] = this.metadata.direction[1];
244+
indexToWorld[2] = this.metadata.direction[2];
245+
246+
indexToWorld[4] = this.metadata.direction[3];
247+
indexToWorld[5] = this.metadata.direction[4];
248+
indexToWorld[6] = this.metadata.direction[5];
249+
250+
indexToWorld[8] = this.metadata.direction[6];
251+
indexToWorld[9] = this.metadata.direction[7];
252+
indexToWorld[10] = this.metadata.direction[8];
253+
254+
mat4.scale(indexToWorld, indexToWorld, this.metadata.spacing);
255+
256+
mat4.invert(worldToIndex, indexToWorld);
257+
return { indexToWorld, worldToIndex };
258+
}
259+
235260
public getImageData(): CPUIImageData {
236261
const { metadata } = this;
237262
if (!metadata) {
@@ -247,13 +272,10 @@ class WSIViewport extends Viewport {
247272
getScalarData: () => this.getScalarData(),
248273
getSpacing: () => metadata.spacing,
249274
worldToIndex: (point: Point3) => {
250-
const canvasPoint = this.worldToCanvas(point);
251-
const pixelCoord = this.canvasToIndex(canvasPoint);
252-
return [pixelCoord[0], pixelCoord[1], 0] as Point3;
275+
return this.worldToIndex(point);
253276
},
254277
indexToWorld: (point: Point3) => {
255-
const canvasPoint = this.indexToCanvas([point[0], point[1]]);
256-
return this.canvasToWorld(canvasPoint);
278+
return this.indexToWorld(point);
257279
},
258280
};
259281
const imageDataReturn = {
@@ -372,7 +394,11 @@ class WSIViewport extends Viewport {
372394
this.refreshRenderValues();
373395
const { resolution, xSpacing, centerIndex } = this.internalCamera;
374396
const canvasToWorldRatio = resolution * xSpacing;
375-
const canvasCenter = this.indexToCanvas(centerIndex.slice(0, 2) as Point2);
397+
const canvasCenter = this.indexToCanvas([
398+
centerIndex[0],
399+
centerIndex[1],
400+
0,
401+
]);
376402
const focalPoint = this.canvasToWorld(canvasCenter);
377403

378404
return {
@@ -428,6 +454,54 @@ class WSIViewport extends Viewport {
428454
this.refreshRenderValues();
429455
};
430456

457+
/**
458+
* Converts a slide coordinate to a image coordinate using WSI utils functions
459+
* @param point
460+
* @returns
461+
*/
462+
public worldToIndexWSI(point: Point3): Point2 {
463+
if (!WSIUtilFunctions) {
464+
return;
465+
}
466+
const affine = this.viewer[affineSymbol];
467+
const pixelCoords = WSIUtilFunctions.applyInverseTransform({
468+
coordinate: [point[0], point[1]],
469+
affine,
470+
});
471+
return [pixelCoords[0], pixelCoords[1]] as Point2;
472+
}
473+
474+
/**
475+
* Converts a image coordinate to a slide coordinate using WSI utils functions
476+
* @param point
477+
* @returns
478+
*/
479+
public indexToWorldWSI(point: Point2): Point3 {
480+
if (!WSIUtilFunctions) {
481+
return;
482+
}
483+
const sliceCoords = WSIUtilFunctions.applyTransform({
484+
coordinate: [point[0], point[1]],
485+
affine: this.viewer[affineSymbol],
486+
});
487+
return [sliceCoords[0], sliceCoords[1], 0] as Point3;
488+
}
489+
490+
public worldToIndex(point: Point3): Point3 {
491+
const { worldToIndex: worldToIndexMatrix } = this.computeTransforms();
492+
const imageCoord = vec3.create();
493+
vec3.transformMat4(imageCoord, point, worldToIndexMatrix);
494+
return imageCoord as Point3;
495+
}
496+
497+
public indexToWorld(point: Point3): Point3 {
498+
const { indexToWorld: indexToWorldMatrix } = this.computeTransforms();
499+
const worldPos = vec3.create();
500+
const point3D = vec3.fromValues(...point);
501+
vec3.transformMat4(worldPos, point3D, indexToWorldMatrix);
502+
return [worldPos[0], worldPos[1], worldPos[2]] as Point3;
503+
}
504+
431505
/**
432506
* Converts a VideoViewport canvas coordinate to a video coordinate.
433507
*
@@ -439,21 +513,9 @@ class WSIViewport extends Viewport {
439513
return;
440514
}
441515
// compute the pixel coordinate in the image
442-
const [px, py] = this.canvasToIndex(canvasPos);
443-
// convert pixel coordinate to world coordinate
444-
const { origin, spacing, direction } = this.getImageData();
445-
446-
const worldPos = vec3.fromValues(0, 0, 0);
447-
448-
// Calculate size of spacing vector in normal direction
449-
const iVector = direction.slice(0, 3) as Point3;
450-
const jVector = direction.slice(3, 6) as Point3;
451-
452-
// Calculate the world coordinate of the pixel
453-
vec3.scaleAndAdd(worldPos, origin, iVector, px * spacing[0]);
454-
vec3.scaleAndAdd(worldPos, worldPos, jVector, py * spacing[1]);
455-
456-
return [worldPos[0], worldPos[1], worldPos[2]] as Point3;
516+
const indexPoint = this.canvasToIndex(canvasPos);
517+
indexPoint[1] = -indexPoint[1]; // flip y axis to match canvas coordinates
518+
return this.indexToWorld(indexPoint);
457519
};
458520

459521
/**
@@ -466,20 +528,11 @@ class WSIViewport extends Viewport {
466528
if (!this.metadata) {
467529
return;
468530
}
469-
const { spacing, direction, origin } = this.metadata;
470-
471-
const iVector = direction.slice(0, 3) as Point3;
472-
const jVector = direction.slice(3, 6) as Point3;
473-
474-
const diff = vec3.subtract([0, 0, 0], worldPos, origin);
475-
476-
const indexPoint: Point2 = [
477-
vec3.dot(diff, iVector) / spacing[0],
478-
vec3.dot(diff, jVector) / spacing[1],
479-
];
531+
const indexPoint = this.worldToIndex(worldPos);
532+
indexPoint[1] = -indexPoint[1]; // flip y axis to match canvas coordinates
480533

481534
// pixel to canvas
482-
const canvasPoint = this.indexToCanvas(indexPoint);
535+
const canvasPoint = this.indexToCanvas([indexPoint[0], indexPoint[1], 0]);
483536
return canvasPoint;
484537
};
485538

@@ -514,6 +567,7 @@ class WSIViewport extends Viewport {
514567
this.microscopyElement.innerText = 'Loading';
515568
this.imageIds = imageIds;
516569
const DicomMicroscopyViewer = await WSIViewport.getDicomMicroscopyViewer();
570+
WSIUtilFunctions ||= DicomMicroscopyViewer.utils;
517571
this.frameOfReferenceUID = null;
518572

519573
const metadataDicomweb = this.imageIds.map((imageId) => {
@@ -608,18 +662,19 @@ class WSIViewport extends Viewport {
608662

609663
public getRotation = () => 0;
610664

611-
protected canvasToIndex = (canvasPos: Point2): Point2 => {
665+
protected canvasToIndex = (canvasPos: Point2): Point3 => {
612666
const transform = this.getTransform();
613667
transform.invert();
614-
return transform.transformPoint(
668+
const indexPoint = transform.transformPoint(
615669
canvasPos.map((it) => it * devicePixelRatio) as Point2
616670
);
671+
return [indexPoint[0], indexPoint[1], 0] as Point3;
617672
};
618673

619-
protected indexToCanvas = (indexPos: Point2): Point2 => {
674+
protected indexToCanvas = (indexPos: Point3): Point2 => {
620675
const transform = this.getTransform();
621676
return transform
622-
.transformPoint(indexPos)
677+
.transformPoint([indexPos[0], indexPos[1]])
623678
.map((it) => it / devicePixelRatio) as Point2;
624679
};
625680

0 commit comments

Comments
 (0)