ImageMapper

Introduction

vtkImageMapper provides 2D image display support for vtk.
It can be associated with a vtkImageSlice prop and placed within a Renderer.

This class resolves coincident topology with the same methods as vtkMapper.

Methods

extend

Method use to decorate a given object (publicAPI+model) with vtkImageMapper characteristics.

Argument Type Required Description
publicAPI Yes object on which methods will be bounds (public)
model Yes object on which data structure will be bounds (protected)
initialValues IImageMapperInitialValues No (default: {})

getBounds

Get the bounds for this mapper as [xmin, xmax, ymin, ymax,zmin, zmax].

getBoundsForSlice

Get the bounds for a given slice as [xmin, xmax, ymin, ymax,zmin, zmax].

Argument Type Required Description
slice Number No The slice index. If undefined, the current slice is considered.
halfThickness Number No Half the slice thickness in index space (unit voxel spacing). If undefined, 0 is considered.

getClosestIJKAxis

Get the closest IJK axis

getCurrentImage

Return currently active image. By default, there can only be one image
for this mapper, if an input is set.

getIsOpaque

getRenderToRectangle

getResolveCoincidentTopology

getResolveCoincidentTopologyAsString

getResolveCoincidentTopologyLineOffsetParameters

getResolveCoincidentTopologyPointOffsetParameters

getResolveCoincidentTopologyPolygonOffsetFaces

getResolveCoincidentTopologyPolygonOffsetParameters

getSliceAtFocalPoint

Get the slice number at a focal point.

getSliceAtPosition

Returns the IJK slice value from a world position or XYZ slice value

Argument Type Required Description
pos Vector3 or number No World point or XYZ slice value

getSlicingModeNormal

intersectWithLineForCellPicking

Argument Type Required Description
p1 Array. Yes The coordinates of the first point.
p2 Array. Yes The coordinates of the second point.

intersectWithLineForPointPicking

Argument Type Required Description
p1 Array. Yes The coordinates of the first point.
p2 Array. Yes The coordinates of the second point.

newInstance

Method use to create a new instance of vtkImageMapper

Argument Type Required Description
initialValues IImageMapperInitialValues No for pre-setting some of its content

setClosestIJKAxis

Set the closest IJK axis

Argument Type Required Description
closestIJKAxis IClosestIJKAxis Yes The axis object.

setISlice

Set the slice for the I axis.

Argument Type Required Description
id Number Yes The slice index.

setJSlice

Set the slice for the J axis.

Argument Type Required Description
id Number Yes The slice index.

setKSlice

Set the slice for the K axis.

Argument Type Required Description
id Number Yes The slice index.

setRelativeCoincidentTopologyLineOffsetParameters

Argument Type Required Description
factor Number Yes
offset Number Yes

setRelativeCoincidentTopologyPointOffsetParameters

Argument Type Required Description
factor Number Yes
offset Number Yes

setRelativeCoincidentTopologyPolygonOffsetParameters

Argument Type Required Description
factor Number Yes
offset Number Yes

setRenderToRectangle

Argument Type Required Description
renderToRectangle Boolean Yes

setResolveCoincidentTopology

Argument Type Required Description
resolveCoincidentTopology Yes

setResolveCoincidentTopologyLineOffsetParameters

Argument Type Required Description
factor Number Yes
offset Number Yes

setResolveCoincidentTopologyPointOffsetParameters

Argument Type Required Description
factor Number Yes
offset Number Yes

setResolveCoincidentTopologyPolygonOffsetFaces

Argument Type Required Description
value Yes

setResolveCoincidentTopologyPolygonOffsetParameters

Argument Type Required Description
factor Number Yes
offset Number Yes

setResolveCoincidentTopologyToDefault

setResolveCoincidentTopologyToOff

setResolveCoincidentTopologyToPolygonOffset

setSlice

Argument Type Required Description
slice Number Yes The slice index.

setSliceAtFocalPoint

Set the slice from a given focal point.

Argument Type Required Description
sliceAtFocalPoint Boolean Yes

setSliceFromCamera

Set the slice from a given camera.

Argument Type Required Description
cam vtkCamera Yes The camera object.

setSlicingMode

Set the slicing mode.

Argument Type Required Description
mode Number Yes The slicing mode.

setXSlice

Set the slice for the X axis.

Argument Type Required Description
id Number Yes The slice index.

setYSlice

Set the slice for the Y axis.

Argument Type Required Description
id Number Yes The slice index.

setZSlice

Set the slice for the Z axis.

Argument Type Required Description
id Number Yes The slice index.

Source

Constants.d.ts
export declare enum SlicingMode {
NONE = -1,
I = 0,
J = 1,
K = 2,
X = 3,
Y = 4,
Z = 5,
}

declare const _default: {
SlicingMode: typeof SlicingMode;
};
export default _default;
Constants.js
export const SlicingMode = {
NONE: -1,
I: 0,
J: 1,
K: 2,
X: 3,
Y: 4,
Z: 5,
};

export default {
SlicingMode,
};
index.d.ts
import vtkCamera from "../Camera";
import vtkAbstractImageMapper, { IAbstractImageMapperInitialValues } from "../AbstractImageMapper";
import { Bounds, Nullable, Vector3 } from "../../../types";
import { SlicingMode } from "./Constants";
import vtkImageData from "../../../Common/DataModel/ImageData";


interface IClosestIJKAxis {
ijkMode: SlicingMode,
flip: boolean
}

interface ICoincidentTopology {
factor: number;
offset: number;
}

export interface IImageMapperInitialValues extends IAbstractImageMapperInitialValues {
closestIJKAxis?: IClosestIJKAxis;
renderToRectangle?: boolean;
sliceAtFocalPoint?: boolean;
}

export interface vtkImageMapper extends vtkAbstractImageMapper {

/**
* Returns the IJK slice value from a world position or XYZ slice value
* @param {Vector3 | number} [pos] World point or XYZ slice value
*/
getSliceAtPosition(pos: Vector3 | number): number;

/**
* Get the closest IJK axis
* @return {IClosestIJKAxis} The axis object.
*/
getClosestIJKAxis(): IClosestIJKAxis;

/**
* Get the bounds for this mapper as [xmin, xmax, ymin, ymax,zmin, zmax].
* @return {Bounds} The bounds for the mapper.
*/
getBounds(): Bounds;

/**
* Get the bounds for a given slice as [xmin, xmax, ymin, ymax,zmin, zmax].
* @param {Number} [slice] The slice index. If undefined, the current slice is considered.
* @param {Number} [halfThickness] Half the slice thickness in index space (unit voxel
* spacing). If undefined, 0 is considered.
* @return {Number[]} The bounds for a given slice.
*/
getBoundsForSlice(slice?: number, halfThickness?: number): number[];

/**
*
*/
getIsOpaque(): boolean;


/**
*
*/
getRenderToRectangle(): boolean;

/**
*
*/
getResolveCoincidentTopology(): ICoincidentTopology

/**
*
*/
getResolveCoincidentTopologyAsString(): ICoincidentTopology

/**
*
*/
getResolveCoincidentTopologyLineOffsetParameters(): ICoincidentTopology

/**
*
*/
getResolveCoincidentTopologyPointOffsetParameters(): ICoincidentTopology

/**
*
*/
getResolveCoincidentTopologyPolygonOffsetFaces(): ICoincidentTopology

/**
*
*/
getResolveCoincidentTopologyPolygonOffsetParameters(): ICoincidentTopology;

/**
* Return currently active image. By default, there can only be one image
* for this mapper, if an input is set.
*/
getCurrentImage(): Nullable<vtkImageData>;

/**
* Get the slice number at a focal point.
*/
getSliceAtFocalPoint(): boolean;

/**
*
* @param {Number[]} p1 The coordinates of the first point.
* @param {Number[]} p2 The coordinates of the second point.
*/
intersectWithLineForPointPicking(p1: number[], p2: number[]): any;

/**
*
* @param {Number[]} p1 The coordinates of the first point.
* @param {Number[]} p2 The coordinates of the second point.
*/
intersectWithLineForCellPicking(p1: number[], p2: number[]): any;

/**
* Set the closest IJK axis
* @param {IClosestIJKAxis} closestIJKAxis The axis object.
*/
setClosestIJKAxis(closestIJKAxis: IClosestIJKAxis): boolean;

/**
*
* @param {Number} factor
* @param {Number} offset
*/
setRelativeCoincidentTopologyLineOffsetParameters(factor: number, offset: number): boolean;

/**
*
* @param {Number} factor
* @param {Number} offset
*/
setRelativeCoincidentTopologyPointOffsetParameters(factor: number, offset: number): boolean;

/**
*
* @param {Number} factor
* @param {Number} offset
*/
setRelativeCoincidentTopologyPolygonOffsetParameters(factor: number, offset: number): boolean;

/**
*
* @param resolveCoincidentTopology
* @default false
*/
setResolveCoincidentTopology(resolveCoincidentTopology: boolean): boolean;

/**
*
* @param {Number} factor
* @param {Number} offset
*/
setResolveCoincidentTopologyLineOffsetParameters(factor: number, offset: number): boolean;

/**
*
* @param {Number} factor
* @param {Number} offset
*/
setResolveCoincidentTopologyPointOffsetParameters(factor: number, offset: number): boolean;

/**
*
* @param value
*/
setResolveCoincidentTopologyPolygonOffsetFaces(value: number): boolean;

/**
*
* @param {Number} factor
* @param {Number} offset
*/
setResolveCoincidentTopologyPolygonOffsetParameters(factor: number, offset: number): boolean;

/**
*
*/
setResolveCoincidentTopologyToDefault(): boolean;

/**
*
*/
setResolveCoincidentTopologyToOff(): boolean;

/**
*
*/
setResolveCoincidentTopologyToPolygonOffset(): boolean;

/**
*
* @param {Boolean} renderToRectangle
*/
setRenderToRectangle(renderToRectangle: boolean): boolean;

/**
*
* @param {Number} slice The slice index.
*/
setSlice(slice: number): boolean;

/**
* Set the slice from a given camera.
* @param {vtkCamera} cam The camera object.
*/
setSliceFromCamera(cam: vtkCamera): boolean;

/**
* Set the slice from a given focal point.
* @param {Boolean} sliceAtFocalPoint
*/
setSliceAtFocalPoint(sliceAtFocalPoint: boolean): boolean;

/**
* Set the slice for the X axis.
* @param {Number} id The slice index.
*/
setXSlice(id: number): boolean;

/**
* Set the slice for the Y axis.
* @param {Number} id The slice index.
*/
setYSlice(id: number): boolean;

/**
* Set the slice for the Z axis.
* @param {Number} id The slice index.
*/
setZSlice(id: number): boolean;

/**
* Set the slice for the I axis.
* @param {Number} id The slice index.
*/
setISlice(id: number): boolean;

/**
* Set the slice for the J axis.
* @param {Number} id The slice index.
*/
setJSlice(id: number): boolean;

/**
* Set the slice for the K axis.
* @param {Number} id The slice index.
*/
setKSlice(id: number): boolean;

/**
*
*/
getSlicingModeNormal(): number[];

/**
* Set the slicing mode.
* @param {Number} mode The slicing mode.
*/
setSlicingMode(mode: number): boolean;
}

/**
* Method use to decorate a given object (publicAPI+model) with vtkImageMapper characteristics.
*
* @param publicAPI object on which methods will be bounds (public)
* @param model object on which data structure will be bounds (protected)
* @param {IImageMapperInitialValues} [initialValues] (default: {})
*/
export function extend(publicAPI: object, model: object, initialValues?: IImageMapperInitialValues): void;

/**
* Method use to create a new instance of vtkImageMapper
* @param {IImageMapperInitialValues} [initialValues] for pre-setting some of its content
*/
export function newInstance(initialValues?: IImageMapperInitialValues): vtkImageMapper;

/**
* vtkImageMapper provides 2D image display support for vtk.
* It can be associated with a vtkImageSlice prop and placed within a Renderer.
*
* This class resolves coincident topology with the same methods as vtkMapper.
*/
export declare const vtkImageMapper: {
newInstance: typeof newInstance;
extend: typeof extend;
SlicingMode: typeof SlicingMode;
}
export default vtkImageMapper;
index.js
import Constants from 'vtk.js/Sources/Rendering/Core/ImageMapper/Constants';
import macro from 'vtk.js/Sources/macros';
import vtkAbstractImageMapper from 'vtk.js/Sources/Rendering/Core/AbstractImageMapper';
import * as pickingHelper from 'vtk.js/Sources/Rendering/Core/AbstractImageMapper/helper';
import * as vtkMath from 'vtk.js/Sources/Common/Core/Math';
import CoincidentTopologyHelper from 'vtk.js/Sources/Rendering/Core/Mapper/CoincidentTopologyHelper';

const { staticOffsetAPI, otherStaticMethods } = CoincidentTopologyHelper;
const { vtkWarningMacro } = macro;
const { SlicingMode } = Constants;

// ----------------------------------------------------------------------------
// vtkImageMapper methods
// ----------------------------------------------------------------------------

function vtkImageMapper(publicAPI, model) {
// Set our className
model.classHierarchy.push('vtkImageMapper');

publicAPI.getSliceAtPosition = (pos) => {
const image = publicAPI.getCurrentImage();

let pos3;
if (pos.length === 3) {
pos3 = pos;
} else if (Number.isFinite(pos)) {
const bds = image.getBounds();
switch (model.slicingMode) {
case SlicingMode.X:
pos3 = [pos, (bds[3] + bds[2]) / 2, (bds[5] + bds[4]) / 2];
break;
case SlicingMode.Y:
pos3 = [(bds[1] + bds[0]) / 2, pos, (bds[5] + bds[4]) / 2];
break;
case SlicingMode.Z:
pos3 = [(bds[1] + bds[0]) / 2, (bds[3] + bds[2]) / 2, pos];
break;
default:
break;
}
}

const ijk = [0, 0, 0];
image.worldToIndex(pos3, ijk);

const ex = image.getExtent();
const { ijkMode } = publicAPI.getClosestIJKAxis();
let slice = 0;
switch (ijkMode) {
case SlicingMode.I:
slice = vtkMath.clampValue(ijk[0], ex[0], ex[1]);
break;
case SlicingMode.J:
slice = vtkMath.clampValue(ijk[1], ex[2], ex[3]);
break;
case SlicingMode.K:
slice = vtkMath.clampValue(ijk[2], ex[4], ex[5]);
break;
default:
return 0;
}

return slice;
};

publicAPI.setSliceFromCamera = (cam) => {
const fp = cam.getFocalPoint();
switch (model.slicingMode) {
case SlicingMode.I:
case SlicingMode.J:
case SlicingMode.K:
{
const slice = publicAPI.getSliceAtPosition(fp);
publicAPI.setSlice(slice);
}
break;
case SlicingMode.X:
publicAPI.setSlice(fp[0]);
break;
case SlicingMode.Y:
publicAPI.setSlice(fp[1]);
break;
case SlicingMode.Z:
publicAPI.setSlice(fp[2]);
break;
default:
break;
}
};

publicAPI.setXSlice = (id) => {
publicAPI.setSlicingMode(SlicingMode.X);
publicAPI.setSlice(id);
};

publicAPI.setYSlice = (id) => {
publicAPI.setSlicingMode(SlicingMode.Y);
publicAPI.setSlice(id);
};

publicAPI.setZSlice = (id) => {
publicAPI.setSlicingMode(SlicingMode.Z);
publicAPI.setSlice(id);
};

publicAPI.setISlice = (id) => {
publicAPI.setSlicingMode(SlicingMode.I);
publicAPI.setSlice(id);
};

publicAPI.setJSlice = (id) => {
publicAPI.setSlicingMode(SlicingMode.J);
publicAPI.setSlice(id);
};

publicAPI.setKSlice = (id) => {
publicAPI.setSlicingMode(SlicingMode.K);
publicAPI.setSlice(id);
};

publicAPI.getSlicingModeNormal = () => {
const out = [0, 0, 0];
const mat3 = publicAPI.getCurrentImage().getDirection();

switch (model.slicingMode) {
case SlicingMode.X:
out[0] = 1;
break;
case SlicingMode.Y:
out[1] = 1;
break;
case SlicingMode.Z:
out[2] = 1;
break;
case SlicingMode.I:
vtkMath.multiply3x3_vect3(mat3, [1, 0, 0], out);
break;
case SlicingMode.J:
vtkMath.multiply3x3_vect3(mat3, [0, 1, 0], out);
break;
case SlicingMode.K:
vtkMath.multiply3x3_vect3(mat3, [0, 0, 1], out);
break;
default:
break;
}
return out;
};

function computeClosestIJKAxis() {
let inVec3;
switch (model.slicingMode) {
case SlicingMode.X:
inVec3 = [1, 0, 0];
break;
case SlicingMode.Y:
inVec3 = [0, 1, 0];
break;
case SlicingMode.Z:
inVec3 = [0, 0, 1];
break;
default:
model.closestIJKAxis = {
ijkMode: model.slicingMode,
flip: false,
};
return;
}

// Project vec3 onto direction cosines
const out = [0, 0, 0];
// The direction matrix in vtkImageData is the indexToWorld rotation matrix
// with a column-major data layout since it is stored as a WebGL matrix.
// We need the worldToIndex rotation matrix for the projection, and it needs
// to be in a row-major data layout to use vtkMath for operations.
// To go from the indexToWorld column-major matrix to the worldToIndex
// row-major matrix, we need to transpose it (column -> row) then inverse it.
// However, that 3x3 matrix is a rotation matrix which is orthonormal, meaning
// that its inverse is equal to its transpose. We therefore need to apply two
// transpositions resulting in a no-op.
const a = publicAPI.getCurrentImage().getDirection();
vtkMath.multiply3x3_vect3(a, inVec3, out);

let maxAbs = 0.0;
let ijkMode = -1;
let flip = false;
for (let axis = 0; axis < out.length; ++axis) {
const absValue = Math.abs(out[axis]);
if (absValue > maxAbs) {
maxAbs = absValue;
flip = out[axis] < 0.0;
ijkMode = axis;
}
}

if (maxAbs !== 1.0) {
const xyzLabel = 'IJKXYZ'[model.slicingMode];
const ijkLabel = 'IJKXYZ'[ijkMode];
vtkWarningMacro(
`Unaccurate slicing along ${xyzLabel} axis which ` +
`is not aligned with any IJK axis of the image data. ` +
`Using ${ijkLabel} axis as a fallback (${maxAbs}% aligned). ` +
`Necessitates slice reformat that is not yet implemented. ` +
`You can switch the slicing mode on your mapper to do IJK slicing instead.`
);
}

model.closestIJKAxis = { ijkMode, flip };
}

publicAPI.setSlicingMode = (mode) => {
if (model.slicingMode === mode) {
return;
}
model.slicingMode = mode;
if (publicAPI.getCurrentImage()) {
computeClosestIJKAxis();
}
publicAPI.modified();
};

publicAPI.getClosestIJKAxis = () => {
if (
(model.closestIJKAxis === undefined ||
model.closestIJKAxis.ijkMode === SlicingMode.NONE) &&
publicAPI.getCurrentImage()
) {
computeClosestIJKAxis();
}
return model.closestIJKAxis;
};

publicAPI.getBounds = () => {
const image = publicAPI.getCurrentImage();
if (!image) {
return vtkMath.createUninitializedBounds();
}
if (!model.useCustomExtents) {
return image.getBounds();
}

const ex = model.customDisplayExtent.slice();
const { ijkMode } = publicAPI.getClosestIJKAxis();
let nSlice = model.slice;
if (ijkMode !== model.slicingMode) {
// If not IJK slicing, get the IJK slice from the XYZ position/slice
nSlice = publicAPI.getSliceAtPosition(model.slice);
}
switch (ijkMode) {
case SlicingMode.I:
ex[0] = nSlice;
ex[1] = nSlice;
break;
case SlicingMode.J:
ex[2] = nSlice;
ex[3] = nSlice;
break;
case SlicingMode.K:
ex[4] = nSlice;
ex[5] = nSlice;
break;
default:
break;
}

return image.extentToBounds(ex);
};

publicAPI.getBoundsForSlice = (slice = model.slice, halfThickness = 0) => {
const image = publicAPI.getCurrentImage();
if (!image) {
return vtkMath.createUninitializedBounds();
}
const extent = image.getSpatialExtent();
const { ijkMode } = publicAPI.getClosestIJKAxis();
let nSlice = slice;
if (ijkMode !== model.slicingMode) {
// If not IJK slicing, get the IJK slice from the XYZ position/slice
nSlice = publicAPI.getSliceAtPosition(slice);
}
switch (ijkMode) {
case SlicingMode.I:
extent[0] = nSlice - halfThickness;
extent[1] = nSlice + halfThickness;
break;
case SlicingMode.J:
extent[2] = nSlice - halfThickness;
extent[3] = nSlice + halfThickness;
break;
case SlicingMode.K:
extent[4] = nSlice - halfThickness;
extent[5] = nSlice + halfThickness;
break;
default:
break;
}
return image.extentToBounds(extent);
};

publicAPI.intersectWithLineForPointPicking = (p1, p2) =>
pickingHelper.intersectWithLineForPointPicking(p1, p2, publicAPI);

publicAPI.intersectWithLineForCellPicking = (p1, p2) =>
pickingHelper.intersectWithLineForCellPicking(p1, p2, publicAPI);

publicAPI.getCurrentImage = () => publicAPI.getInputData();
}

// ----------------------------------------------------------------------------
// Object factory
// ----------------------------------------------------------------------------

const DEFAULT_VALUES = {
slicingMode: SlicingMode.NONE,
closestIJKAxis: { ijkMode: SlicingMode.NONE, flip: false },
renderToRectangle: false,
sliceAtFocalPoint: false,
preferSizeOverAccuracy: false, // Whether to use halfFloat representation of float, when it is inaccurate
};

// ----------------------------------------------------------------------------

export function extend(publicAPI, model, initialValues = {}) {
Object.assign(model, DEFAULT_VALUES, initialValues);

// Build VTK API
vtkAbstractImageMapper.extend(publicAPI, model, initialValues);

macro.get(publicAPI, model, ['slicingMode']);
macro.setGet(publicAPI, model, [
'closestIJKAxis',
'renderToRectangle',
'sliceAtFocalPoint',
'preferSizeOverAccuracy',
]);

CoincidentTopologyHelper.implementCoincidentTopologyMethods(publicAPI, model);

// Object methods
vtkImageMapper(publicAPI, model);
}

// ----------------------------------------------------------------------------

export const newInstance = macro.newInstance(extend, 'vtkImageMapper');

// ----------------------------------------------------------------------------

export default {
newInstance,
extend,
...staticOffsetAPI,
...otherStaticMethods,
...Constants,
};