Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@itk-viewer/viewer

Package Overview
Dependencies
Maintainers
0
Versions
20
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@itk-viewer/viewer - npm Package Compare versions

Comparing version 0.3.0 to 0.4.0

12

CHANGELOG.md
# @itk-viewer/viewer
## 0.4.0
### Minor Changes
- 99d78de: View-2d slices in IJK image space instead of world XYZ.
- ff46215: Add slice axis GUI controls for View 2D
### Patch Changes
- Updated dependencies [99d78de]
- @itk-viewer/io@0.2.0
## 0.3.0

@@ -4,0 +16,0 @@

2

dist/camera.d.ts

@@ -125,3 +125,3 @@ /// <reference types="gl-matrix/index.js" />

};
export declare const reset2d: (pose: Pose, verticalFieldOfView: number, bounds: Bounds, aspect: number) => {
export declare const reset2d: (pose: Pose, verticalFieldOfView: number, pointsToFit: Array<ReadonlyVec3>, aspect: number) => {
center: vec3;

@@ -128,0 +128,0 @@ rotation: quat;

@@ -1,2 +0,2 @@

import { addPoint, createBounds, getCorners, getLength, } from '@itk-viewer/utils/bounding-box.js';
import { addPoint, createBounds, getLength, } from '@itk-viewer/utils/bounding-box.js';
import { mat4, vec3, quat } from 'gl-matrix';

@@ -135,13 +135,17 @@ import { assign, createActor, setup } from 'xstate';

};
export const reset2d = (pose, verticalFieldOfView, bounds, aspect) => {
const center = vec3.fromValues((bounds[0] + bounds[1]) / 2.0, (bounds[2] + bounds[3]) / 2.0, (bounds[4] + bounds[5]) / 2.0);
export const reset2d = (pose, verticalFieldOfView, pointsToFit, aspect) => {
const center = vec3.create();
for (let i = 0; i < pointsToFit.length; ++i) {
vec3.add(center, center, pointsToFit[i]);
}
vec3.scale(center, center, 1.0 / pointsToFit.length);
// Get the bounds in view coordinates
const viewBounds = createBounds();
const visiblePoints = getCorners(bounds);
const viewMat = mat4.create();
toMat4(viewMat, pose);
for (let i = 0; i < visiblePoints.length; ++i) {
const point = visiblePoints[i];
vec3.transformMat4(point, point, viewMat);
addPoint(viewBounds, ...point);
const viewSpacePoint = vec3.create();
for (let i = 0; i < pointsToFit.length; ++i) {
const point = pointsToFit[i];
vec3.transformMat4(viewSpacePoint, point, viewMat);
addPoint(viewBounds, viewSpacePoint[0], viewSpacePoint[1], viewSpacePoint[2]);
}

@@ -148,0 +152,0 @@ const xLength = getLength(viewBounds, 0);

@@ -6,11 +6,11 @@ import { Actor, AnyActorRef } from 'xstate';

import { ViewportActor } from './viewport.js';
export declare const AXIS: {
readonly X: "x";
readonly Y: "y";
readonly Z: "z";
export declare const Axis: {
readonly I: "I";
readonly J: "J";
readonly K: "K";
};
export type Axis = ValueOf<typeof AXIS>;
export type AxisType = ValueOf<typeof Axis>;
export declare const view2d: import("xstate").StateMachine<{
slice: number;
axis: Axis;
axis: AxisType;
scale: number;

@@ -62,2 +62,5 @@ image: MultiscaleSpatialImage | undefined;

} | {
type: 'setAxis';
axis: AxisType;
} | {
type: 'setScale';

@@ -116,8 +119,9 @@ scale: number;

slice: number;
axis: Axis;
axis: AxisType;
}>, {
[k: string]: unknown;
type: string;
}> | import("xstate").ActorRef<import("xstate").PromiseSnapshot<"x" | "y" | "z", {
}> | import("xstate").ActorRef<import("xstate").PromiseSnapshot<"I" | "J" | "K", {
image: MultiscaleSpatialImage;
scale: number;
}>, {

@@ -136,3 +140,3 @@ [k: string]: unknown;

slice: number;
axis: Axis;
axis: AxisType;
}>;

@@ -142,4 +146,5 @@ id: string | undefined;

src: "findDefaultAxis";
logic: import("xstate").PromiseActorLogic<"x" | "y" | "z", {
logic: import("xstate").PromiseActorLogic<"I" | "J" | "K", {
image: MultiscaleSpatialImage;
scale: number;
}>;

@@ -165,2 +170,5 @@ id: string | undefined;

} | {
type: 'setAxis';
axis: AxisType;
} | {
type: 'setScale';

@@ -220,3 +228,3 @@ scale: number;

slice: number;
axis: Axis;
axis: AxisType;
}>;

@@ -226,4 +234,5 @@ id: string | undefined;

src: "findDefaultAxis";
logic: import("xstate").PromiseActorLogic<"x" | "y" | "z", {
logic: import("xstate").PromiseActorLogic<"I" | "J" | "K", {
image: MultiscaleSpatialImage;
scale: number;
}>;

@@ -230,0 +239,0 @@ id: string | undefined;

@@ -1,17 +0,26 @@

import { assign, enqueueActions, fromPromise, setup, } from 'xstate';
import { assign, enqueueActions, fromPromise, setup, stateIn, } from 'xstate';
import { ensure3dDirection, } from '@itk-viewer/io/MultiscaleSpatialImage.js';
import { reset2d } from './camera.js';
import { quat, vec3 } from 'gl-matrix';
export const AXIS = {
X: 'x',
Y: 'y',
Z: 'z',
import { mat3, mat4, quat, vec3 } from 'gl-matrix';
import { XYZ, ensuredDims } from '@itk-viewer/io/dimensionUtils.js';
import { getCorners } from '@itk-viewer/utils/bounding-box.js';
export const Axis = {
I: 'I',
J: 'J',
K: 'K',
};
const axisToIndex = {
x: 0,
y: 1,
z: 2,
I: 0,
J: 1,
K: 2,
};
// To MultiScaleImage dimension
const axisToDim = {
I: 'x',
J: 'y',
K: 'z',
};
const viewContext = {
slice: 0.5,
axis: AXIS.Z,
axis: Axis.K,
scale: 0,

@@ -23,28 +32,36 @@ image: undefined,

};
const toRotation = (axis) => {
// Default to z axis where +Z goes into screen and +Y is down on screen
let vec = vec3.fromValues(1, 0, 0);
let angle = Math.PI;
if (axis == 'x') {
vec = vec3.fromValues(0, 1, 0);
angle = Math.PI / 2;
const toRotation = (direction, axis) => {
const direction3d = ensure3dDirection(direction);
// ITK (and VTKMath) uses row-major index axis, but gl-matrix uses column-major. Transpose.
mat3.transpose(direction3d, direction3d);
const rotation = quat.create();
if (axis == Axis.I) {
quat.fromEuler(rotation, 0, 90, 0);
const roll = quat.fromEuler(quat.create(), 0, 0, 90);
quat.multiply(rotation, rotation, roll);
}
else if (axis == 'y') {
angle = Math.PI / 2;
else if (axis == Axis.J) {
quat.fromEuler(rotation, 90, 0, 0);
}
const rotation = quat.create();
quat.setAxisAngle(rotation, vec, angle);
else {
quat.fromEuler(rotation, 0, 0, 180);
}
const sliceAxisRotation = mat3.fromQuat(mat3.create(), rotation);
mat3.multiply(direction3d, direction3d, sliceAxisRotation);
quat.fromMat3(rotation, direction3d);
quat.normalize(rotation, rotation);
return rotation;
};
const computeMinSizeAxis = (bounds) => {
const xSize = Math.abs(bounds[1] - bounds[0]);
const ySize = Math.abs(bounds[3] - bounds[2]);
const zSize = Math.abs(bounds[5] - bounds[4]);
if (xSize < ySize && xSize < zSize) {
return AXIS.X;
const computeMinSizeAxis = (spacing, size) => {
const imageSpaceSize = size.map((s, i) => s * spacing[i]);
const iSize = imageSpaceSize[0];
const jSize = imageSpaceSize[1];
const kSize = imageSpaceSize[2];
if (iSize < jSize && iSize < kSize) {
return Axis.I;
}
if (ySize < xSize && ySize < zSize) {
return AXIS.Y;
if (jSize < iSize && jSize < kSize) {
return Axis.J;
}
return AXIS.Z;
return Axis.K;
};

@@ -55,23 +72,16 @@ export const view2d = setup({

imageBuilder: fromPromise(async ({ input: { image, scale, slice, axis }, }) => {
const worldBounds = await image.getWorldBounds(scale);
let sliceWorldPos = 0;
if (axis === 'x') {
const xWidth = worldBounds[1] - worldBounds[0];
sliceWorldPos = worldBounds[0] + xWidth * slice; // world X pos
worldBounds[0] = sliceWorldPos;
worldBounds[1] = sliceWorldPos;
const normalizedImageBounds = [0, 1, 0, 1, 0, 1];
if (axis === Axis.I) {
normalizedImageBounds[0] = slice;
normalizedImageBounds[1] = slice;
}
else if (axis === 'y') {
const yWidth = worldBounds[3] - worldBounds[2];
sliceWorldPos = worldBounds[2] + yWidth * slice;
worldBounds[2] = sliceWorldPos;
worldBounds[3] = sliceWorldPos;
else if (axis === Axis.J) {
normalizedImageBounds[2] = slice;
normalizedImageBounds[3] = slice;
}
else if (axis === 'z') {
const zWidth = worldBounds[5] - worldBounds[4];
sliceWorldPos = worldBounds[4] + zWidth * slice;
worldBounds[4] = sliceWorldPos;
worldBounds[5] = sliceWorldPos;
else if (axis === Axis.K) {
normalizedImageBounds[4] = slice;
normalizedImageBounds[5] = slice;
}
const builtImage = (await image.getImage(scale, worldBounds));
const builtImage = (await image.getImageInImageSpace(scale, normalizedImageBounds));
if (builtImage.imageType.dimension === 2) {

@@ -82,14 +92,34 @@ return { builtImage, sliceIndex: 0 };

// 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));
// Math.round goes up with .5, so we need to clamp to max index
const sliceIndex = Math.max(0, Math.min(sliceIndexFloat, builtImage.size[axisIndex] - 1));
const indexToWorld = await image.scaleIndexToWorld(scale);
const worldToIndex = mat4.invert(mat4.create(), indexToWorld);
const wholeImageOrigin = [...(await image.scaleOrigin(scale))];
if (wholeImageOrigin.length == 2) {
wholeImageOrigin[2] = 0;
}
vec3.transformMat4(wholeImageOrigin, wholeImageOrigin, worldToIndex);
const buildImageOrigin = [...builtImage.origin];
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[axisToIndex[axis]] - 1));
return { builtImage, sliceIndex };
}),
findDefaultAxis: fromPromise(async ({ input: { image }, }) => {
const worldBounds = await image.getWorldBounds(image.coarsestScale);
return computeMinSizeAxis(worldBounds);
findDefaultAxis: fromPromise(async ({ input: { image, scale }, }) => {
const ijkSpacing = await image.scaleSpacing(scale);
if (ijkSpacing.length > 3) {
ijkSpacing.push(0);
}
const shape = image.scaleInfos[scale].arrayShape;
const shape3d = ensuredDims(0, ['x', 'y', 'z'], shape);
const shapeArray = XYZ.map((axis) => shape3d.get(axis));
return computeMinSizeAxis(ijkSpacing, shapeArray);
}),

@@ -103,3 +133,3 @@ },

},
resetCameraPose: async ({ context: { image, camera, viewport, axis } }) => {
resetCameraPose: async ({ context: { image, camera, viewport, axis, scale }, }) => {
if (!image || !camera)

@@ -113,8 +143,18 @@ return;

})();
const bounds = await image.getWorldBounds(image.coarsestScale);
const { pose: currentPose, verticalFieldOfView } = camera.getSnapshot().context;
const withAxis = { ...currentPose };
withAxis.rotation = toRotation(axis);
const pose = reset2d(withAxis, verticalFieldOfView, bounds, aspect);
withAxis.rotation = toRotation(image.direction, axis);
const indexToWorld = await image.scaleIndexToWorld(scale);
const indexBounds = image.getIndexExtent(scale);
const corners = getCorners(indexBounds);
// to world space
const pointsToFit = corners.map((corner) => {
return vec3.transformMat4(corner, corner, indexToWorld);
});
const pose = reset2d(withAxis, verticalFieldOfView, pointsToFit, aspect);
camera.send({
type: 'setEnableRotation',
enable: true,
});
camera.send({
type: 'setPose',

@@ -175,10 +215,34 @@ pose,

},
setSlice: {
actions: [assign({ slice: ({ event }) => event.slice })],
target: '.buildingImage',
},
setScale: {
actions: [assign({ scale: ({ event }) => event.scale })],
target: '.buildingImage',
},
setSlice: [
// if buildingImage, rebuild image
{
guard: stateIn('view2d.buildingImage'),
target: '.buildingImage',
actions: [assign({ slice: ({ event }) => event.slice })],
},
// else eventually going to buildingImage
{
actions: [assign({ slice: ({ event }) => event.slice })],
},
],
setAxis: [
// if buildingImage, rebuild image
{
guard: stateIn('view2d.buildingImage'),
target: '.buildingImage',
actions: [
assign({ axis: ({ event }) => event.axis }),
'forwardToSpawned',
'resetCameraPose',
],
},
// else eventually going to buildingImage
{
actions: [
assign({ axis: ({ event }) => event.axis }),
'forwardToSpawned',
'resetCameraPose',
],
},
],
setViewport: {

@@ -212,7 +276,19 @@ actions: [

states: {
idle: {},
idle: {
on: {
setScale: {
actions: [
assign({
scale: ({ event }) => {
return event.scale;
},
}),
],
},
},
},
findingNewImageDefaults: {
invoke: {
input: ({ context }) => {
const { image } = context;
const { image, scale } = context;
if (!image)

@@ -222,2 +298,3 @@ throw new Error('No image available');

image,
scale,
};

@@ -234,3 +311,3 @@ },

enqueue.sendTo(actor, {
type: 'axis',
type: 'setAxis',
axis: context.axis,

@@ -245,2 +322,15 @@ });

},
on: {
setScale: {
actions: [
assign({
scale: ({ event }) => {
return event.scale;
},
}),
],
target: '.',
reenter: true,
},
},
},

@@ -275,2 +365,15 @@ buildingImage: {

},
on: {
setScale: {
actions: [
assign({
scale: ({ event }) => {
return event.scale;
},
}),
],
target: '.',
reenter: true,
},
},
},

@@ -277,0 +380,0 @@ },

@@ -42,5 +42,2 @@ /// <reference types="gl-matrix/index.js" />

params: unknown;
} | {
type: "resetCameraPose";
params: unknown;
}, {

@@ -62,5 +59,2 @@ type: string;

params: unknown;
} | {
type: "resetCameraPose";
params: unknown;
}, {

@@ -67,0 +61,0 @@ type: string;

import { assign, sendParent, setup } from 'xstate';
import { cameraMachine, reset3d } from './camera.js';
import { cameraMachine } from './camera.js';
export const viewportMachine = setup({

@@ -14,17 +14,2 @@ types: {},

},
resetCameraPose: async ({ context: { image, resolution: dims }, self }) => {
const { camera } = self.getSnapshot().children;
if (!image || !camera)
return;
const aspect = (() => {
return dims[1] && dims[0] ? dims[0] / dims[1] : 1;
})();
const bounds = await image.getWorldBounds(image.coarsestScale);
const { pose: currentPose, verticalFieldOfView } = camera.getSnapshot().context;
const pose = reset3d(currentPose, verticalFieldOfView, bounds, aspect);
camera.send({
type: 'setPose',
pose,
});
},
},

@@ -65,3 +50,2 @@ }).createMachine({

}),
'resetCameraPose',
'forwardToSpawned',

@@ -75,3 +59,2 @@ ],

}),
'resetCameraPose',
'forwardToSpawned',

@@ -78,0 +61,0 @@ ],

{
"name": "@itk-viewer/viewer",
"version": "0.3.0",
"version": "0.4.0",
"description": "Multi-dimensional web-based image, mesh, and point set viewer",

@@ -46,3 +46,3 @@ "type": "module",

"xstate": "5.5.2",
"@itk-viewer/io": "^0.1.8",
"@itk-viewer/io": "^0.2.0",
"@itk-viewer/utils": "^0.1.3"

@@ -49,0 +49,0 @@ },

@@ -5,3 +5,2 @@ import {

createBounds,
getCorners,
getLength,

@@ -213,20 +212,25 @@ } from '@itk-viewer/utils/bounding-box.js';

verticalFieldOfView: number,
bounds: Bounds,
pointsToFit: Array<ReadonlyVec3>,
aspect: number,
) => {
const center = vec3.fromValues(
(bounds[0] + bounds[1]) / 2.0,
(bounds[2] + bounds[3]) / 2.0,
(bounds[4] + bounds[5]) / 2.0,
);
const center = vec3.create();
for (let i = 0; i < pointsToFit.length; ++i) {
vec3.add(center, center, pointsToFit[i]);
}
vec3.scale(center, center, 1.0 / pointsToFit.length);
// Get the bounds in view coordinates
const viewBounds = createBounds();
const visiblePoints = getCorners(bounds);
const viewMat = mat4.create();
toMat4(viewMat, pose);
for (let i = 0; i < visiblePoints.length; ++i) {
const point = visiblePoints[i];
vec3.transformMat4(point, point, viewMat);
addPoint(viewBounds, ...point);
const viewSpacePoint = vec3.create();
for (let i = 0; i < pointsToFit.length; ++i) {
const point = pointsToFit[i];
vec3.transformMat4(viewSpacePoint, point, viewMat);
addPoint(
viewBounds,
viewSpacePoint[0],
viewSpacePoint[1],
viewSpacePoint[2],
);
}

@@ -233,0 +237,0 @@

@@ -8,2 +8,3 @@ import {

setup,
stateIn,
} from 'xstate';

@@ -13,27 +14,36 @@ import {

BuiltImage,
ensure3dDirection,
} from '@itk-viewer/io/MultiscaleSpatialImage.js';
import { ValueOf } from '@itk-viewer/io/types.js';
import { ReadonlyBounds } from '@itk-viewer/utils/bounding-box.js';
import { CreateChild } from './children.js';
import { Camera, reset2d } from './camera.js';
import { ViewportActor } from './viewport.js';
import { quat, vec3 } from 'gl-matrix';
import { mat3, mat4, quat, vec3 } from 'gl-matrix';
import { XYZ, ensuredDims } from '@itk-viewer/io/dimensionUtils.js';
import { Bounds, getCorners } from '@itk-viewer/utils/bounding-box.js';
export const AXIS = {
X: 'x',
Y: 'y',
Z: 'z',
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 = {
x: 0,
y: 1,
z: 2,
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.Z as Axis,
axis: Axis.K as AxisType,
scale: 0,

@@ -46,28 +56,38 @@ image: undefined as MultiscaleSpatialImage | undefined,

const toRotation = (axis: Axis) => {
// Default to z axis where +Z goes into screen and +Y is down on screen
let vec = vec3.fromValues(1, 0, 0);
let angle = Math.PI;
if (axis == 'x') {
vec = vec3.fromValues(0, 1, 0);
angle = Math.PI / 2;
} else if (axis == 'y') {
angle = Math.PI / 2;
const toRotation = (direction: Float64Array, axis: AxisType) => {
const direction3d = ensure3dDirection(direction);
// ITK (and VTKMath) uses row-major index axis, but gl-matrix uses column-major. Transpose.
mat3.transpose(direction3d, direction3d);
const rotation = quat.create();
if (axis == Axis.I) {
quat.fromEuler(rotation, 0, 90, 0);
const roll = quat.fromEuler(quat.create(), 0, 0, 90);
quat.multiply(rotation, rotation, roll);
} else if (axis == Axis.J) {
quat.fromEuler(rotation, 90, 0, 0);
} else {
quat.fromEuler(rotation, 0, 0, 180);
}
const rotation = quat.create();
quat.setAxisAngle(rotation, vec, angle);
const sliceAxisRotation = mat3.fromQuat(mat3.create(), rotation);
mat3.multiply(direction3d, direction3d, sliceAxisRotation);
quat.fromMat3(rotation, direction3d);
quat.normalize(rotation, rotation);
return rotation;
};
const computeMinSizeAxis = (bounds: ReadonlyBounds) => {
const xSize = Math.abs(bounds[1] - bounds[0]);
const ySize = Math.abs(bounds[3] - bounds[2]);
const zSize = Math.abs(bounds[5] - bounds[4]);
if (xSize < ySize && xSize < zSize) {
return AXIS.X;
const computeMinSizeAxis = (spacing: Array<number>, size: Array<number>) => {
const imageSpaceSize = size.map((s, i) => s * spacing[i]);
const iSize = imageSpaceSize[0];
const jSize = imageSpaceSize[1];
const kSize = imageSpaceSize[2];
if (iSize < jSize && iSize < kSize) {
return Axis.I;
}
if (ySize < xSize && ySize < zSize) {
return AXIS.Y;
if (jSize < iSize && jSize < kSize) {
return Axis.J;
}
return AXIS.Z;
return Axis.K;
};

@@ -81,2 +101,3 @@

| { type: 'setSlice'; slice: number }
| { type: 'setAxis'; axis: AxisType }
| { type: 'setScale'; scale: number }

@@ -96,27 +117,20 @@ | { type: 'setViewport'; viewport: ViewportActor }

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 === 'x') {
const xWidth = worldBounds[1] - worldBounds[0];
sliceWorldPos = worldBounds[0] + xWidth * slice; // world X pos
worldBounds[0] = sliceWorldPos;
worldBounds[1] = sliceWorldPos;
} else if (axis === 'y') {
const yWidth = worldBounds[3] - worldBounds[2];
sliceWorldPos = worldBounds[2] + yWidth * slice;
worldBounds[2] = sliceWorldPos;
worldBounds[3] = sliceWorldPos;
} else if (axis === 'z') {
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;

@@ -127,17 +141,38 @@

}
// 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,
);
// Math.round goes up with .5, so we need to clamp to max index
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),
);

@@ -149,10 +184,17 @@ return { builtImage, sliceIndex };

async ({
input: { image },
input: { image, scale },
}: {
input: {
image: MultiscaleSpatialImage;
scale: number;
};
}) => {
const worldBounds = await image.getWorldBounds(image.coarsestScale);
return computeMinSizeAxis(worldBounds);
const ijkSpacing = await image.scaleSpacing(scale);
if (ijkSpacing.length > 3) {
ijkSpacing.push(0);
}
const shape = image.scaleInfos[scale].arrayShape;
const shape3d = ensuredDims(0, ['x', 'y', 'z'], shape);
const shapeArray = XYZ.map((axis) => shape3d.get(axis)!);
return computeMinSizeAxis(ijkSpacing, shapeArray);
},

@@ -167,3 +209,5 @@ ),

},
resetCameraPose: async ({ context: { image, camera, viewport, axis } }) => {
resetCameraPose: async ({
context: { image, camera, viewport, axis, scale },
}) => {
if (!image || !camera) return;

@@ -176,12 +220,23 @@ const aspect = (() => {

const bounds = await image.getWorldBounds(image.coarsestScale);
const { pose: currentPose, verticalFieldOfView } =
camera.getSnapshot().context;
const withAxis = { ...currentPose };
withAxis.rotation = toRotation(image.direction, axis);
withAxis.rotation = toRotation(axis);
const indexToWorld = await image.scaleIndexToWorld(scale);
const indexBounds = image.getIndexExtent(scale);
const corners = getCorners(indexBounds);
const pose = reset2d(withAxis, verticalFieldOfView, bounds, aspect);
// to world space
const pointsToFit = corners.map((corner) => {
return vec3.transformMat4(corner, corner, indexToWorld);
});
const pose = reset2d(withAxis, verticalFieldOfView, pointsToFit, aspect);
camera.send({
type: 'setEnableRotation',
enable: true,
});
camera.send({
type: 'setPose',

@@ -246,10 +301,34 @@ pose,

},
setSlice: {
actions: [assign({ slice: ({ event }) => event.slice })],
target: '.buildingImage',
},
setScale: {
actions: [assign({ scale: ({ event }) => event.scale })],
target: '.buildingImage',
},
setSlice: [
// if buildingImage, rebuild image
{
guard: stateIn('view2d.buildingImage'),
target: '.buildingImage',
actions: [assign({ slice: ({ event }) => event.slice })],
},
// else eventually going to buildingImage
{
actions: [assign({ slice: ({ event }) => event.slice })],
},
],
setAxis: [
// if buildingImage, rebuild image
{
guard: stateIn('view2d.buildingImage'),
target: '.buildingImage',
actions: [
assign({ axis: ({ event }) => event.axis }),
'forwardToSpawned',
'resetCameraPose',
],
},
// else eventually going to buildingImage
{
actions: [
assign({ axis: ({ event }) => event.axis }),
'forwardToSpawned',
'resetCameraPose',
],
},
],
setViewport: {

@@ -282,10 +361,23 @@ actions: [

states: {
idle: {},
idle: {
on: {
setScale: {
actions: [
assign({
scale: ({ event }) => {
return event.scale;
},
}),
],
},
},
},
findingNewImageDefaults: {
invoke: {
input: ({ context }) => {
const { image } = context;
const { image, scale } = context;
if (!image) throw new Error('No image available');
return {
image,
scale,
};

@@ -302,3 +394,3 @@ },

enqueue.sendTo(actor, {
type: 'axis',
type: 'setAxis',
axis: context.axis,

@@ -313,2 +405,15 @@ });

},
on: {
setScale: {
actions: [
assign({
scale: ({ event }) => {
return event.scale;
},
}),
],
target: '.',
reenter: true,
},
},
},

@@ -342,2 +447,15 @@ buildingImage: {

},
on: {
setScale: {
actions: [
assign({
scale: ({ event }) => {
return event.scale;
},
}),
],
target: '.',
reenter: true,
},
},
},

@@ -344,0 +462,0 @@ },

@@ -5,3 +5,3 @@ import { Actor, AnyActorRef, assign, sendParent, setup } from 'xstate';

import { MultiscaleSpatialImage } from '@itk-viewer/io/MultiscaleSpatialImage.js';
import { cameraMachine, Camera, reset3d } from './camera.js';
import { cameraMachine, Camera } from './camera.js';
import { CreateChild } from './children.js';

@@ -52,18 +52,2 @@

},
resetCameraPose: async ({ context: { image, resolution: dims }, self }) => {
const { camera } = self.getSnapshot().children;
if (!image || !camera) return;
const aspect = (() => {
return dims[1] && dims[0] ? dims[0] / dims[1] : 1;
})();
const bounds = await image.getWorldBounds(image.coarsestScale);
const { pose: currentPose, verticalFieldOfView } =
camera.getSnapshot().context;
const pose = reset3d(currentPose, verticalFieldOfView, bounds, aspect);
camera.send({
type: 'setPose',
pose,
});
},
},

@@ -109,3 +93,2 @@ }).createMachine({

}),
'resetCameraPose',
'forwardToSpawned',

@@ -120,3 +103,2 @@ ],

}),
'resetCameraPose',
'forwardToSpawned',

@@ -123,0 +105,0 @@ ],

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc