ImageArrayMapper

Introduction

vtkImageArrayMapper provides display support for a collection of single/multi-frame images.
Images can have variable dimensions (width, height, depth in pixels), can be mixture of
color (RGB) and grayscale images, origin point and direction cosines.
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

computeSlice

Calculate the global slice number that corresponds to the provided image and subSlice number.
The global slice number corresponds to the total number of 2D image frames that a collection has.

Argument Type Required Description
imageIndex Yes The image number is the index of the vtkImageData object in the input collection.
subSlice Yes The subSlice number is the k-index of a slice within a vtkImageData object in the input collection.

computeTotalSlices

Calculate the total number of slices in the input collection.

extend

Method use to decorate a given object (publicAPI+model) with vtkImageArrayMapper 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 IImageArrayMapperInitialValues 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. This depends on the currently active slice number.

getImage

Get vtkImageData corresponding to the provided (global) slice number.

Argument Type Required Description
slice Yes (global) slice number. If a slice number is not provided, the function uses the current slice number (i.e. the output of getSlice()).

getImageIndex

Get the vtkImageData index corresponding to the provided global slice number.

Argument Type Required Description
slice Yes global slice number. If a slice number is not provided, the function uses the current slice number (i.e. the output of getSlice()).

getResolveCoincidentTopology

getResolveCoincidentTopologyAsString

getResolveCoincidentTopologyLineOffsetParameters

getResolveCoincidentTopologyPointOffsetParameters

getResolveCoincidentTopologyPolygonOffsetFaces

getResolveCoincidentTopologyPolygonOffsetParameters

getSubSlice

Given a global slice number, identify the subSlice number (slice k-index within a vtkImageData).

Argument Type Required Description
slice Yes global slice number. If a slice number is not provided, the function uses the current slice number (i.e. the output of getSlice()).

getTotalSlices

Fetch the pre-calculated total number of slices in the input collection.

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 vtkImageArrayMapper

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

setInputData

Argument Type Required Description
inputData Yes set input as a vtkCollection of vtkImageData objects.

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

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.

setSlicingMode

Set the slicing mode.

Argument Type Required Description
mode Number Yes The slicing mode.

Source

index.d.ts
import vtkAbstractImageMapper, { IAbstractImageMapperInitialValues } from "../AbstractImageMapper";
import IClosestIJKAxis from "../ImageMapper";
import { Bounds, Nullable } from "../../../types";
import { SlicingMode } from "../ImageMapper/Constants";
import vtkImageData from "../../../Common/DataModel/ImageData";
import vtkCollection from "../../../Common/DataModel/Collection";


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

interface ISliceToSubSlice {
imageIndex: number;
subSlice: number;
}

export interface IImageArrayMapperInitialValues extends IAbstractImageMapperInitialValues {
slicingMode: SlicingMode.K,
sliceToSubSliceMap: ISliceToSubSlice[],
}

export interface vtkImageArrayMapper extends vtkAbstractImageMapper {

/**
*
* @param inputData set input as a vtkCollection of vtkImageData objects.
*/
setInputData(inputData: vtkCollection): void;

/**
* Get vtkImageData corresponding to the provided (global) slice number.
* @param slice (global) slice number. If a slice number is not provided,
* the function uses the current slice number (i.e. the output of getSlice()).
*/
getImage(slice?: number): Nullable<vtkImageData>;

/**
* Return currently active image. This depends on the currently active slice number.
*/
getCurrentImage(): Nullable<vtkImageData>;

/**
* 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 {Bounds} The bounds for a given slice.
*/
getBoundsForSlice(slice?: number, halfThickness?: number): Bounds;

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

/**
* Calculate the total number of slices in the input collection.
*/
computeTotalSlices(): number;

/**
* Fetch the pre-calculated total number of slices in the input collection.
*/
getTotalSlices(): number;

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

/**
* Calculate the global slice number that corresponds to the provided image and subSlice number.
* The global slice number corresponds to the total number of 2D image frames that a collection has.
* @param imageIndex The image number is the index of the vtkImageData object in the input collection.
* @param subSlice The subSlice number is the k-index of a slice within a vtkImageData object in the input collection.
*/
computeSlice(imageIndex: number, subSlice: number): number;

/**
* Get the vtkImageData index corresponding to the provided global slice number.
* @param slice global slice number. If a slice number is not provided,
* the function uses the current slice number (i.e. the output of getSlice()).
*/
getImageIndex(slice?: number): number;

/**
* Given a global slice number, identify the subSlice number (slice k-index within a vtkImageData).
* @param slice global slice number. If a slice number is not provided,
* the function uses the current slice number (i.e. the output of getSlice()).
*/
getSubSlice(slice?: number): number;


/**
*
*/
getResolveCoincidentTopology(): ICoincidentTopology

/**
*
*/
getResolveCoincidentTopologyAsString(): ICoincidentTopology

/**
*
*/
getResolveCoincidentTopologyLineOffsetParameters(): ICoincidentTopology

/**
*
*/
getResolveCoincidentTopologyPointOffsetParameters(): ICoincidentTopology

/**
*
*/
getResolveCoincidentTopologyPolygonOffsetFaces(): ICoincidentTopology

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

/**
*
* @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;

/**
* Set the slicing mode.
* @param {Number} mode The slicing mode.
*/
setSlicingMode(mode: number): 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;
}

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

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

/**
* vtkImageArrayMapper provides display support for a collection of single/multi-frame images.
* Images can have variable dimensions (width, height, depth in pixels), can be mixture of
* color (RGB) and grayscale images, origin point and direction cosines.
* 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 vtkImageArrayMapper: {
newInstance: typeof newInstance;
extend: typeof extend;
SlicingMode: typeof SlicingMode;
}
export default vtkImageArrayMapper;
index.js
import macro from 'vtk.js/Sources/macros';
import vtkAbstractImageMapper from 'vtk.js/Sources/Rendering/Core/AbstractImageMapper';
import vtkImageMapper from 'vtk.js/Sources/Rendering/Core/ImageMapper';
import * as vtkMath from 'vtk.js/Sources/Common/Core/Math';
import * as pickingHelper from 'vtk.js/Sources/Rendering/Core/AbstractImageMapper/helper';
import CoincidentTopologyHelper from 'vtk.js/Sources/Rendering/Core/Mapper/CoincidentTopologyHelper';

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

// ----------------------------------------------------------------------------
// vtkImageArrayMapper methods
// ----------------------------------------------------------------------------

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

//------------------
// Private functions

const _computeSliceToSubSliceMap = () => {
const inputCollection = publicAPI.getInputData();
if (!inputCollection || inputCollection.empty()) {
// clear the map
if (model.sliceToSubSliceMap.length !== 0) {
model.sliceToSubSliceMap.length = 0;
publicAPI.modified();
}
return;
}

if (
model.sliceToSubSliceMap.length === 0 ||
inputCollection.getMTime() > publicAPI.getMTime()
) {
const perImageMap = inputCollection.map((image, index) => {
const dim = image.getDimensions();
const out = new Array(dim[model.slicingMode]);
for (let i = 0; i < out.length; ++i) {
out[i] = { imageIndex: index, subSlice: i };
}
return out;
});

model.sliceToSubSliceMap = perImageMap.flat();
publicAPI.modified();
}
};

const _superSetInputData = publicAPI.setInputData;
const _superSetInputConnection = publicAPI.setInputConnection;

//------------------
// Public functions

publicAPI.setInputData = (inputData) => {
_superSetInputData(inputData);
_computeSliceToSubSliceMap();
};

publicAPI.setInputConnection = (outputPort, port = 0) => {
_superSetInputConnection(outputPort, port);
_computeSliceToSubSliceMap();
};

publicAPI.getImage = (slice = publicAPI.getSlice()) => {
const inputCollection = publicAPI.getInputData();
if (!inputCollection) {
vtkWarningMacro('No input set.');
} else if (slice < 0 || slice >= publicAPI.getTotalSlices()) {
vtkWarningMacro('Invalid slice number.');
} else {
_computeSliceToSubSliceMap();
return inputCollection.getItem(
model.sliceToSubSliceMap[slice].imageIndex
);
}
return null;
};

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

const ex = model.customDisplayExtent.slice();
// use sub-slice of the current image,
// which is the k-coordinate.
const nSlice = publicAPI.getSubSlice();
ex[4] = nSlice;
ex[5] = nSlice;
return image.extentToBounds(ex);
};

publicAPI.getBoundsForSlice = (
slice = publicAPI.getSlice(),
halfThickness = 0
) => {
const image = publicAPI.getImage(slice);
if (!image) {
return vtkMath.createUninitializedBounds();
}
const extent = image.getSpatialExtent();
const nSlice = publicAPI.getSubSlice(slice);
extent[4] = nSlice - halfThickness;
extent[5] = nSlice + halfThickness;
return image.extentToBounds(extent);
};

publicAPI.getClosestIJKAxis = () => ({
ijkMode: model.slicingMode,
flip: false,
});

publicAPI.computeTotalSlices = () => {
const inputCollection = publicAPI.getInputData();
const collectionLength = inputCollection.getNumberOfItems();
let slicesCount = 0;
for (let i = 0; i < collectionLength; ++i) {
const image = inputCollection.getItem(i);
if (image) {
slicesCount += image.getDimensions()[model.slicingMode];
}
}
return slicesCount;
};

publicAPI.getTotalSlices = () => {
_computeSliceToSubSliceMap();
return model.sliceToSubSliceMap.length;
};

// set slice number in terms of imageIndex and subSlice number.
publicAPI.setSlice = (slice) => {
const inputCollection = publicAPI.getInputData();
if (!inputCollection) {
// No input is set
vtkWarningMacro('No input set.');
return;
}

const totalSlices = publicAPI.getTotalSlices();
if (slice >= 0 && slice < totalSlices) {
model.slice = slice;
publicAPI.modified();
} else {
vtkErrorMacro(
`Slice number out of range. Acceptable range is: [0, ${
totalSlices > 0 ? totalSlices - 1 : 0
}]slice <= totalSlices`
);
}
};

publicAPI.computeSlice = (imageIndex, subSlice) => {
_computeSliceToSubSliceMap();
return model.sliceToSubSliceMap.findIndex(
(x) => x.imageIndex === imageIndex && x.subSlice === subSlice
);
};

publicAPI.getImageIndex = (slice = publicAPI.getSlice()) => {
_computeSliceToSubSliceMap();
return model.sliceToSubSliceMap[slice]?.imageIndex;
};

publicAPI.getSubSlice = (slice = publicAPI.getSlice()) => {
_computeSliceToSubSliceMap();
return model.sliceToSubSliceMap[slice]?.subSlice;
};

publicAPI.getCurrentImage = () => publicAPI.getImage(publicAPI.getSlice());

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

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

// ----------------------------------------------------------------------------
const DEFAULT_VALUES = {
slicingMode: SlicingMode.K,
sliceToSubSliceMap: [],
};

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

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

// Build VTK API
macro.get(publicAPI, model, ['slicingMode']);

CoincidentTopologyHelper.implementCoincidentTopologyMethods(publicAPI, model);

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

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

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

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

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