Skip to content

Commit

Permalink
fix(view-2d): build image in image space
Browse files Browse the repository at this point in the history
  • Loading branch information
PaulHax committed Jun 4, 2024
1 parent d59023e commit a4a2152
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 46 deletions.
104 changes: 63 additions & 41 deletions packages/viewer/src/view-2d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,45 +16,52 @@ import { ValueOf } from '@itk-viewer/io/types.js';
import { CreateChild } from './children.js';
import { Camera, reset2d } from './camera.js';
import { ViewportActor } from './viewport.js';
import { quat, vec3 } from 'gl-matrix';
import { mat4, quat, vec3 } from 'gl-matrix';
import { XYZ, ensuredDims } from '@itk-viewer/io/dimensionUtils.js';
import { getCorners } from '@itk-viewer/utils/bounding-box.js';
import { Bounds, getCorners } from '@itk-viewer/utils/bounding-box.js';

export const AXIS = {
export const Axis = {
I: 'I',
J: 'J',
K: 'K',
} as const;

export type Axis = ValueOf<typeof AXIS>;
export type AxisType = ValueOf<typeof Axis>;

const axisToIndex = {
I: 0,
J: 1,
K: 2,
} as const;

// To MultiScaleImage dimension
const axisToDim = {
I: 'x',
J: 'y',
K: 'z',
} as const;

const viewContext = {
slice: 0.5,
axis: AXIS.K as Axis,
axis: Axis.K as AxisType,
scale: 0,
image: undefined as MultiscaleSpatialImage | undefined,
spawned: {} as Record<string, AnyActorRef>,
viewport: undefined as ViewportActor | undefined,
camera: undefined as Camera | undefined,
};

const toRotation = (direction: Float64Array, axis: Axis) => {
const toRotation = (direction: Float64Array, axis: AxisType) => {
const dir3d = ensure3dDirection(direction);

const x = vec3.fromValues(dir3d[0], dir3d[1], dir3d[2]);
const y = vec3.fromValues(dir3d[3], dir3d[4], dir3d[5]);
const z = vec3.fromValues(dir3d[6], dir3d[7], dir3d[8]);

const rotation = quat.create();
if (axis == AXIS.I) {
if (axis == Axis.I) {
quat.setAxes(rotation, x, z, y);
} else if (axis == AXIS.J) {
} else if (axis == Axis.J) {
quat.setAxes(rotation, y, x, z); // negate z?
} else {
vec3.negate(z, z);
Expand All @@ -69,12 +76,12 @@ const computeMinSizeAxis = (spacing: Array<number>, size: Array<number>) => {
const jSize = imageSpaceSize[1];
const kSize = imageSpaceSize[2];
if (iSize < jSize && iSize < kSize) {
return AXIS.I;
return Axis.I;
}
if (jSize < iSize && jSize < kSize) {
return AXIS.J;
return Axis.J;
}
return AXIS.K;
return Axis.K;
};

export const view2d = setup({
Expand All @@ -97,51 +104,66 @@ export const view2d = setup({
input: {
image: MultiscaleSpatialImage;
scale: number;
slice: number;
axis: Axis;
slice: number; // 0 to 1 for depth on slice axis
axis: AxisType;
};
}) => {
const worldBounds = await image.getWorldBounds(scale);
let sliceWorldPos = 0;
if (axis === AXIS.I) {
const xWidth = worldBounds[1] - worldBounds[0];
sliceWorldPos = worldBounds[0] + xWidth * slice; // world X pos
worldBounds[0] = sliceWorldPos;
worldBounds[1] = sliceWorldPos;
} else if (axis === AXIS.J) {
const yWidth = worldBounds[3] - worldBounds[2];
sliceWorldPos = worldBounds[2] + yWidth * slice;
worldBounds[2] = sliceWorldPos;
worldBounds[3] = sliceWorldPos;
} else if (axis === AXIS.K) {
const zWidth = worldBounds[5] - worldBounds[4];
sliceWorldPos = worldBounds[4] + zWidth * slice;
worldBounds[4] = sliceWorldPos;
worldBounds[5] = sliceWorldPos;
const normalizedImageBounds = [0, 1, 0, 1, 0, 1] as Bounds;
if (axis === Axis.I) {
normalizedImageBounds[0] = slice;
normalizedImageBounds[1] = slice;
} else if (axis === Axis.J) {
normalizedImageBounds[2] = slice;
normalizedImageBounds[3] = slice;
} else if (axis === Axis.K) {
normalizedImageBounds[4] = slice;
normalizedImageBounds[5] = slice;
}
const builtImage = (await image.getImage(

const builtImage = (await image.getImageInImageSpace(
scale,
worldBounds,
normalizedImageBounds,
)) as BuiltImage;

if (builtImage.imageType.dimension === 2) {
return { builtImage, sliceIndex: 0 };
}

// buildImage could be larger than slice if cached so
// find index of slice in builtImage
const axisIndex = axisToIndex[axis];
const builtWidthWorld =
builtImage.spacing[axisIndex] * builtImage.size[axisIndex];
const sliceInBuildImageWorld =
sliceWorldPos - builtImage.origin[axisIndex];
const sliceIndexFloat = Math.round(
builtImage.size[axisIndex] *
(sliceInBuildImageWorld / builtWidthWorld),
const indexToWorld = await image.scaleIndexToWorld(scale);
const worldToIndex = mat4.invert(mat4.create(), indexToWorld);
const wholeImageOrigin = [...(await image.scaleOrigin(scale))] as vec3;
if (wholeImageOrigin.length == 2) {
wholeImageOrigin[2] = 0;
}
vec3.transformMat4(wholeImageOrigin, wholeImageOrigin, worldToIndex);
const buildImageOrigin = [...builtImage.origin] as vec3;
if (buildImageOrigin.length == 2) {
buildImageOrigin[2] = 0;
}
vec3.transformMat4(buildImageOrigin, buildImageOrigin, worldToIndex);

// vector from whole image origin to build image origin
const wholeImageToBuildImageOrigin = vec3.subtract(
buildImageOrigin,
buildImageOrigin,
wholeImageOrigin,
);
const builtOriginIndex =
wholeImageToBuildImageOrigin[axisToIndex[axis]];

const axisIndexSize =
image.scaleInfos[scale].arrayShape.get(axisToDim[axis]) ?? 1;
const fullImageSliceIndex = slice * axisIndexSize;

const sliceIndexInBuildImageFloat =
fullImageSliceIndex - builtOriginIndex;
const sliceIndexFloat = Math.round(sliceIndexInBuildImageFloat);
// Math.round goes up with .5, so clamp to max index
const sliceIndex = Math.max(
0,
Math.min(sliceIndexFloat, builtImage.size[axisIndex] - 1),
Math.min(sliceIndexFloat, builtImage.size[axisToIndex[axis]] - 1),
);
return { builtImage, sliceIndex };
},
Expand Down
10 changes: 5 additions & 5 deletions packages/vtkjs/src/view-2d-vtkjs.machine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ import GenericRenderWindow, {
} from '@kitware/vtk.js/Rendering/Misc/GenericRenderWindow.js';
import { BuiltImage } from '@itk-viewer/io/MultiscaleSpatialImage.js';
import { Camera, Pose } from '@itk-viewer/viewer/camera.js';
import { AXIS, Axis } from '@itk-viewer/viewer/view-2d.js';
import { Axis, AxisType } from '@itk-viewer/viewer/view-2d.js';

export type Context = {
rendererContainer: vtkGenericRenderWindow;
camera: Camera | undefined;
parent: AnyActorRef;
axis: Axis;
axis: AxisType;
};

export type SetContainerEvent = {
Expand All @@ -26,7 +26,7 @@ export const view2dLogic = setup({
| SetContainerEvent
| { type: 'setResolution'; resolution: [number, number] }
| { type: 'imageBuilt'; image: BuiltImage }
| { type: 'axis'; axis: Axis }
| { type: 'axis'; axis: AxisType }
| { type: 'setCameraPose'; pose: Pose; parallelScaleRatio: number }
| { type: 'setCamera'; camera: Camera };
},
Expand All @@ -42,7 +42,7 @@ export const view2dLogic = setup({
throw new Error('Function not implemented.');
},
// eslint-disable-next-line @typescript-eslint/no-unused-vars
axis: (_, __: { axis: Axis }) => {
axis: (_, __: { axis: AxisType }) => {
throw new Error('Function not implemented.');
},
forwardToParent: sendTo(
Expand All @@ -59,7 +59,7 @@ export const view2dLogic = setup({
listenWindowResize: false,
}),
camera: undefined,
axis: AXIS.K,
axis: Axis.K,
parent,
};
},
Expand Down

0 comments on commit a4a2152

Please sign in to comment.