ImageData

Introduction

vtkImageData is a data object that is a concrete implementation of
vtkDataSet. vtkImageData represents a geometric structure that is a
topological and geometrical regular array of points. Examples include volumes
(voxel data) and pixmaps. All vtkDataSet functions are inherited.

Methods

computeHistogram

Returns an object with { minimum, maximum, average, variance, sigma }
of the imageData points found within the provided worldBounds.

voxelFunc(index, bounds) is an optional function that is called with
the [i,j,k] index and index bounds, expected to return truthy if the
data point should be counted in the histogram, and falsey if not.

Argument Type Required Description
worldBounds Array. Yes The bounds of the world.
voxelFunc No

computeIncrements

Returns an array[3] of values to multiply an [i,j,k] index to convert
into the actual data array index, from the provided extent.
numberOfComponents should match the Scalar components.

Argument Type Required Description
extent Array. Yes
numberOfComponents Number No

computeOffsetIndex

Converts an [i,j,k] index to the flat data array index. Returns NaN
if any of the i,j,k bounds are outside the data Extent.

Argument Type Required Description
ijk Array. Yes The localized [i,j,k] pixel array position. Float values will be rounded.

computeTransforms

Calculates the indexToWorld and worldToIndex conversion matrices from
the origin, direction, and spacing. Shouldn’t need to call this as it is
handled internally, and updated whenever the vtkImageData is modified.

extend

Method used to decorate a given object (publicAPI+model) with vtkImageData 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 IImageDataInitialValues No (default: {})

extentToBounds

Returns a bounds array from a given Extent, useful if you need to
calculate the world bounds of a subset of the imageData’s data.

Argument Type Required Description
ex Array. Yes

getBounds

The Bounds of a vtkImage are returned as pairs of world coordinates

x_max, y_min, y_max, z_min, z_max]``` these are calculated
from the Extent, Origin, and Spacing, defined
through
```js
bounds[6] =
[
i_min*Spacing[0] + Origin[0], i_max*Spacing[0] + Origin[0],
j_min*Spacing[1] + Origin[1], j_max*Spacing[1] + Origin[1],
k_min*Spacing[2] + Origin[2], k_max*Spacing[2] + Origin[2]
];

You can’t directly set the bounds. First you need to decide how many
pixels across your image will be (i.e. what the extent should be), and
then you must find the origin and spacing that will produce the bounds
that you need from the extent that you have. This is simple algebra. In
general, always set the extent to start at zero, e.g. [0, 9, 0, 9, 0, 9] for a 10x10x10 image. Calling setDimensions(10,10,10) does exactly
the same thing as setExtent(0,9,0,9,0,9) but you should always do the
latter to be explicit about where your extent starts.

getCenter

Get the [x,y,z] location of the center of the imageData.

getDimensions

Get dimensions of this structured points dataset. It is the number of
points on each axis. Dimensions are computed from Extents during this
call.

getDirection

Direction is a mat3 matrix corresponding to the axes directions in
world coordinates for the I, J, K axes of the image. Direction must form
an orthonormal basis.

getExtent

The maximal extent of the projection.

getExtentByReference

getIndexToWorld

Returns the mat4 matrices used to convert between world and index.
worldToIndex is the inverse matrix of indexToWorld. Both are made
with Float64Array.

getNumberOfCells

getNumberOfPoints

Get the number of points composing the dataset.

getOffsetIndexFromWorld

Returns the data array index for the point at the provided world position.

Argument Type Required Description
xyz Array. Yes The [x,y,z] array in world coordinates.

getOrigin

Get the origin of the dataset. The origin is the position in world
coordinates of the point of extent (0,0,0). This point does not have to
be part of the dataset, in other words, the dataset extent does not have
to start at (0,0,0) and the origin can be outside of the dataset bounding
box. The origin plus spacing determine the position in space of the
points.

getOriginByReference

Get the origin of the dataset. The origin is the position in world

getPoint

Get the world position of a data point. Index is the point’s index in the
1D data array.

Argument Type Required Description
index Yes

getScalarValueFromWorld

Returns the scalar value for the point at the provided world position, or
NaN if the world bounds are outside the volumeData bounds. comp is
the scalar component index, for multi-component scalar data.

Argument Type Required Description
xyz Array. Yes The [x,y,z] array in world coordinates.
comp Number No The scalar component index for multi-component scalars.

getSpacing

Set the spacing [width, height, length] of the cubical cells that compose
the data set.

getSpacingByReference

getWorldToIndex

Returns the mat4 matrices used to convert between world and index.
worldToIndex is the inverse matrix of indexToWorld. Both are made
with Float64Array.

indexToWorld

Converts the input index vector [i,j,k] to world values [x,y,z].
Modifies the out vector array in place, but also returns it.

Argument Type Required Description
ain ReadonlyVec3 Yes
aout vec3 Yes

indexToWorldBounds

Calculate the corresponding world bounds for the given index bounds
[i_min, i_max, j_min, j_max, k_min, k_max]. Modifies out in place if
provided, or returns a new array.

Argument Type Required Description
bin Array. Yes
bout Array. No

indexToWorldVec3

this is the fast version, requires vec3 arguments

Argument Type Required Description
vin ReadonlyVec3 Yes
vout vec3 Yes

newInstance

Method used to create a new instance of vtkImageData.

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

setDimensions

Set the values of the extent, from 0 to (i-1), etc.

Argument Type Required Description
i Yes
j Yes
k Yes

setDimensions

Set the values of the extent, from 0 to (i-1), etc.

Argument Type Required Description
dims Yes

setDirection

The direction matrix is a 3x3 basis for the I, J, K axes
of the image. The rows of the matrix correspond to the
axes directions in world coordinates. Direction must
form an orthonormal basis, results are undefined if
it is not.

Argument Type Required Description
e00 Yes
e01 Yes
e02 Yes
e10 Yes
e11 Yes
e12 Yes
e20 Yes
e21 Yes
e22 Yes

setDirection

The direction matrix is a 3x3 basis for the I, J, K axes
of the image. The rows of the matrix correspond to the
axes directions in world coordinates. Direction must
form an orthonormal basis, results are undefined if
it is not.

Argument Type Required Description
direction mat3 Yes

setExtent

Argument Type Required Description
x1 Number Yes The x coordinate of the first point.
x2 Number Yes The x coordinate of the second point.
y1 Number Yes The y coordinate of the first point.
y2 Number Yes The y coordinate of the second point.
z1 Number Yes The z coordinate of the first point.
z2 Number Yes The z coordinate of the second point.

setExtent

Set the extent.

Argument Type Required Description
extent Yes

setOrigin

Set the origin of the image.

Argument Type Required Description
origin Vector3 Yes The coordinate of the origin point.

setOriginFrom

Set the origin of the image.

Argument Type Required Description
origin Vector3 Yes The coordinate of the origin point.

setSpacing

Argument Type Required Description
spacing Yes

setSpacingFrom

Argument Type Required Description
spacing Yes

worldToIndex

Converts the input world vector [x,y,z] to approximate index values
[i,j,k]. Should be rounded to integers before attempting to access the
index. Modifies the out vector array in place, but also returns it.

Argument Type Required Description
ain Yes
aout Yes

worldToIndexBounds

Calculate the corresponding index bounds for the given world bounds
[x_min, x_max, y_min, y_max, z_min, z_max]. Modifies out in place if
provided, or returns a new array.

Argument Type Required Description
bin Array. Yes
bout Array. No

worldToIndexVec3

this is the fast version, requires vec3 arguments

Argument Type Required Description
vin Yes
vout Yes

Source

index.d.ts
import { mat3, mat4, ReadonlyVec3, vec3 } from 'gl-matrix';
import { Bounds, Vector3 } from '../../../types';
import vtkDataSet, { IDataSetInitialValues } from '../DataSet';

/**
*
*/
export interface IImageDataInitialValues extends IDataSetInitialValues {
spacing?: number[];
origin?: number[];
extent?: number[];
}

interface IComputeHistogram {
minimum: number;
maximum: number;
average: number;
variance: number;
sigma: number;
}

export interface vtkImageData extends vtkDataSet {

/**
* Returns an object with `{ minimum, maximum, average, variance, sigma }`
* of the imageData points found within the provided `worldBounds`.
*
* `voxelFunc(index, bounds)` is an optional function that is called with
* the `[i,j,k]` index and index `bounds`, expected to return truthy if the
* data point should be counted in the histogram, and falsey if not.
* @param {Number[]} worldBounds The bounds of the world.
* @param [voxelFunc]
*/
computeHistogram(worldBounds: number[], voxelFunc?: any): IComputeHistogram;

/**
* Returns an `array[3]` of values to multiply an `[i,j,k]` index to convert
* into the actual data array index, from the provided extent.
* `numberOfComponents` should match the Scalar components.
* @internal
* @param {Number[]} extent
* @param {Number} [numberOfComponents]
*/
computeIncrements(extent: number[], numberOfComponents?: number): number[]

/**
* Converts an `[i,j,k]` index to the flat data array index. Returns `NaN`
* if any of the i,j,k bounds are outside the data Extent.
* @internal
* @param {Number[]} ijk The localized `[i,j,k]` pixel array position. Float values will be rounded.
* @return {Number} the corresponding flattened index in the scalar array
*/
computeOffsetIndex(ijk: number[]): number;

/**
* Calculates the `indexToWorld` and `worldToIndex` conversion matrices from
* the origin, direction, and spacing. Shouldn't need to call this as it is
* handled internally, and updated whenever the vtkImageData is modified.
* @internal
*/
computeTransforms(): void;

/**
* Returns a bounds array from a given Extent, useful if you need to
* calculate the world bounds of a subset of the imageData's data.
* @internal
* @param {Number[]} ex
*/
extentToBounds(ex: number[]): number[];

/**
* The Bounds of a vtkImage are returned as pairs of world coordinates
* ```[x_min, x_max, y_min, y_max, z_min, z_max]``` these are calculated
* from the Extent, Origin, and Spacing, defined
* through
* ```js
* bounds[6] =
* [
* i_min*Spacing[0] + Origin[0], i_max*Spacing[0] + Origin[0],
* j_min*Spacing[1] + Origin[1], j_max*Spacing[1] + Origin[1],
* k_min*Spacing[2] + Origin[2], k_max*Spacing[2] + Origin[2]
* ];
* ```
* You can't directly set the bounds. First you need to decide how many
* pixels across your image will be (i.e. what the extent should be), and
* then you must find the origin and spacing that will produce the bounds
* that you need from the extent that you have. This is simple algebra. In
* general, always set the extent to start at zero, e.g. `[0, 9, 0, 9, 0,
* 9]` for a 10x10x10 image. Calling `setDimensions(10,10,10)` does exactly
* the same thing as `setExtent(0,9,0,9,0,9)` but you should always do the
* latter to be explicit about where your extent starts.
* @return {Bounds} The bounds for the mapper.
*/
getBounds(): Bounds;

/**
* Get the `[x,y,z]` location of the center of the imageData.
*/
getCenter(): number[];

/**
* Get dimensions of this structured points dataset. It is the number of
* points on each axis. Dimensions are computed from Extents during this
* call.
*/
getDimensions(): number[];

/**
* Direction is a `mat3` matrix corresponding to the axes directions in
* world coordinates for the I, J, K axes of the image. Direction must form
* an orthonormal basis.
*/
getDirection(): mat3;

/**
* The maximal extent of the projection.
* @default [0, -1, 0, -1, 0, -1]
*/
getExtent(): number[];

/**
*
* @default [0, -1, 0, -1, 0, -1]
*/
getExtentByReference(): number[];

/**
* Returns the data array index for the point at the provided world position.
* @param {Number[]} xyz The [x,y,z] array in world coordinates.
* @return {number|NaN} the corresponding pixel's index in the scalar array.
*/
getOffsetIndexFromWorld(xyz: number[]): number;

/**
*
*/
getNumberOfCells(): number;

/**
* Get the number of points composing the dataset.
*/
getNumberOfPoints(): number;

/**
* Get the world position of a data point. Index is the point's index in the
* 1D data array.
* @param index
*/
getPoint(index: number): number[];

/**
* Get the origin of the dataset. The origin is the position in world
* coordinates of the point of extent (0,0,0). This point does not have to
* be part of the dataset, in other words, the dataset extent does not have
* to start at (0,0,0) and the origin can be outside of the dataset bounding
* box. The origin plus spacing determine the position in space of the
* points.
*/
getOrigin(): number[];

/**
* Get the origin of the dataset. The origin is the position in world
*/
getOriginByReference(): number[];

/**
* Returns the scalar value for the point at the provided world position, or
* `NaN` if the world bounds are outside the volumeData bounds. `comp` is
* the scalar component index, for multi-component scalar data.
* @param {Number[]} xyz The [x,y,z] array in world coordinates.
* @param {Number} [comp] The scalar component index for multi-component scalars.
* @return {number|NaN} The corresponding pixel's scalar value.
*/
getScalarValueFromWorld(xyz: number[], comp?: number): number;

/**
* Set the spacing [width, height, length] of the cubical cells that compose
* the data set.
*/
getSpacing(): number[];

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

/**
* Returns the `mat4` matrices used to convert between world and index.
* `worldToIndex` is the inverse matrix of `indexToWorld`. Both are made
* with `Float64Array`.
*/
getIndexToWorld(): mat4;

/**
* Returns the `mat4` matrices used to convert between world and index.
* `worldToIndex` is the inverse matrix of `indexToWorld`. Both are made
* with `Float64Array`.
*/
getWorldToIndex(): mat4;

/**
* this is the fast version, requires vec3 arguments
* @param {ReadonlyVec3} vin
* @param {vec3} vout
*/
indexToWorldVec3(vin: ReadonlyVec3, vout: vec3): vec3;

/**
* Converts the input index vector `[i,j,k]` to world values `[x,y,z]`.
* Modifies the out vector array in place, but also returns it.
* @param {ReadonlyVec3} ain
* @param {vec3} aout
*/
indexToWorld(ain: ReadonlyVec3, aout: vec3): vec3;

/**
* Calculate the corresponding world bounds for the given index bounds
* `[i_min, i_max, j_min, j_max, k_min, k_max]`. Modifies `out` in place if
* provided, or returns a new array.
* @param {Number[]} bin
* @param {Number[]} [bout]
*/
indexToWorldBounds(bin: number[], bout?: number[]): number[];

/**
* Set the values of the extent, from `0` to `(i-1)`, etc.
* @param dims
*/
setDimensions(dims: number[]): void;

/**
* Set the values of the extent, from `0` to `(i-1)`, etc.
* @param i
* @param j
* @param k
*/
setDimensions(i: number, j: number, k: number): void;

/**
* The direction matrix is a 3x3 basis for the I, J, K axes
* of the image. The rows of the matrix correspond to the
* axes directions in world coordinates. Direction must
* form an orthonormal basis, results are undefined if
* it is not.
* @param {mat3} direction
*/
setDirection(direction: mat3): boolean;

/**
* The direction matrix is a 3x3 basis for the I, J, K axes
* of the image. The rows of the matrix correspond to the
* axes directions in world coordinates. Direction must
* form an orthonormal basis, results are undefined if
* it is not.
* @param e00
* @param e01
* @param e02
* @param e10
* @param e11
* @param e12
* @param e20
* @param e21
* @param e22
*/
setDirection(e00: number, e01: number, e02: number, e10: number, e11: number, e12: number, e20: number, e21: number, e22: number): boolean;

/**
* Set the extent.
* @param extent
*/
setExtent(extent: number[]): boolean;

/**
*
* @param {Number} x1 The x coordinate of the first point.
* @param {Number} x2 The x coordinate of the second point.
* @param {Number} y1 The y coordinate of the first point.
* @param {Number} y2 The y coordinate of the second point.
* @param {Number} z1 The z coordinate of the first point.
* @param {Number} z2 The z coordinate of the second point.
*/
setExtent(x1: number, x2: number, y1: number, y2: number, z1: number, z2: number): void;

/**
* Set the origin of the image.
* @param {Vector3} origin The coordinate of the origin point.
*/
setOrigin(origin: Vector3): boolean;

/**
* Set the origin of the image.
* @param {Vector3} origin The coordinate of the origin point.
*/
setOriginFrom(origin: Vector3): boolean;

/**
*
* @param spacing
*/
setSpacing(spacing: number[]): boolean;

/**
*
* @param spacing
*/
setSpacingFrom(spacing: number[]): boolean;

/**
* this is the fast version, requires vec3 arguments
* @param vin
* @param vout
*/
worldToIndexVec3(vin: ReadonlyVec3, vout: vec3): vec3;

/**
* Converts the input world vector `[x,y,z]` to approximate index values
* `[i,j,k]`. Should be rounded to integers before attempting to access the
* index. Modifies the out vector array in place, but also returns it.
* @param ain
* @param aout
*/
worldToIndex(ain: ReadonlyVec3, aout: vec3): vec3;

/**
* Calculate the corresponding index bounds for the given world bounds
* `[x_min, x_max, y_min, y_max, z_min, z_max]`. Modifies `out` in place if
* provided, or returns a new array.
* @param {Number[]} bin
* @param {Number[]} [bout]
*/
worldToIndexBounds(bin: number[], bout?: number[]): number[];
}

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

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

/**
* vtkImageData is a data object that is a concrete implementation of
* vtkDataSet. vtkImageData represents a geometric structure that is a
* topological and geometrical regular array of points. Examples include volumes
* (voxel data) and pixmaps. All vtkDataSet functions are inherited.
*/
export declare const vtkImageData: {
newInstance: typeof newInstance,
extend: typeof extend,
};
export default vtkImageData;
index.js
import macro from 'vtk.js/Sources/macros';
import * as vtkMath from 'vtk.js/Sources/Common/Core/Math';
import vtkBoundingBox from 'vtk.js/Sources/Common/DataModel/BoundingBox';
import vtkDataSet from 'vtk.js/Sources/Common/DataModel/DataSet';
import vtkStructuredData from 'vtk.js/Sources/Common/DataModel/StructuredData';
import { StructuredType } from 'vtk.js/Sources/Common/DataModel/StructuredData/Constants';
import { vec3, mat3, mat4 } from 'gl-matrix';

const { vtkErrorMacro } = macro;

// ----------------------------------------------------------------------------
// vtkImageData methods
// ----------------------------------------------------------------------------

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

publicAPI.setExtent = (...inExtent) => {
if (model.deleted) {
vtkErrorMacro('instance deleted - cannot call any method');
return false;
}

const extentArray = inExtent.length === 1 ? inExtent[0] : inExtent;

if (extentArray.length !== 6) {
return false;
}

let changeDetected = false;
model.extent.forEach((item, index) => {
if (item !== extentArray[index]) {
if (changeDetected) {
return;
}
changeDetected = true;
}
});

if (changeDetected) {
model.extent = extentArray.slice();
model.dataDescription = vtkStructuredData.getDataDescriptionFromExtent(
model.extent
);
publicAPI.modified();
}
return changeDetected;
};

publicAPI.setDimensions = (...dims) => {
let i;
let j;
let k;

if (model.deleted) {
vtkErrorMacro('instance deleted - cannot call any method');
return;
}

if (dims.length === 1) {
const array = dims[0];
i = array[0];
j = array[1];
k = array[2];
} else if (dims.length === 3) {
i = dims[0];
j = dims[1];
k = dims[2];
} else {
vtkErrorMacro('Bad dimension specification');
return;
}

publicAPI.setExtent(0, i - 1, 0, j - 1, 0, k - 1);
};

publicAPI.getDimensions = () => [
model.extent[1] - model.extent[0] + 1,
model.extent[3] - model.extent[2] + 1,
model.extent[5] - model.extent[4] + 1,
];

publicAPI.getNumberOfCells = () => {
const dims = publicAPI.getDimensions();
let nCells = 1;

for (let i = 0; i < 3; i++) {
if (dims[i] === 0) {
return 0;
}
if (dims[i] > 1) {
nCells *= dims[i] - 1;
}
}

return nCells;
};

publicAPI.getNumberOfPoints = () => {
const dims = publicAPI.getDimensions();
return dims[0] * dims[1] * dims[2];
};

publicAPI.getPoint = (index) => {
const dims = publicAPI.getDimensions();

if (dims[0] === 0 || dims[1] === 0 || dims[2] === 0) {
vtkErrorMacro('Requesting a point from an empty image.');
return null;
}

const ijk = new Float64Array(3);

switch (model.dataDescription) {
case StructuredType.EMPTY:
return null;

case StructuredType.SINGLE_POINT:
break;

case StructuredType.X_LINE:
ijk[0] = index;
break;

case StructuredType.Y_LINE:
ijk[1] = index;
break;

case StructuredType.Z_LINE:
ijk[2] = index;
break;

case StructuredType.XY_PLANE:
ijk[0] = index % dims[0];
ijk[1] = index / dims[0];
break;

case StructuredType.YZ_PLANE:
ijk[1] = index % dims[1];
ijk[2] = index / dims[1];
break;

case StructuredType.XZ_PLANE:
ijk[0] = index % dims[0];
ijk[2] = index / dims[0];
break;

case StructuredType.XYZ_GRID:
ijk[0] = index % dims[0];
ijk[1] = (index / dims[0]) % dims[1];
ijk[2] = index / (dims[0] * dims[1]);
break;

default:
vtkErrorMacro('Invalid dataDescription');
break;
}

const coords = [0, 0, 0];
publicAPI.indexToWorld(ijk, coords);
return coords;
};

// vtkCell *GetCell(vtkIdType cellId) VTK_OVERRIDE;
// void GetCell(vtkIdType cellId, vtkGenericCell *cell) VTK_OVERRIDE;
// void GetCellBounds(vtkIdType cellId, double bounds[6]) VTK_OVERRIDE;
// virtual vtkIdType FindPoint(double x, double y, double z)
// {
// return this->vtkDataSet::FindPoint(x, y, z);
// }
// vtkIdType FindPoint(double x[3]) VTK_OVERRIDE;
// vtkIdType FindCell(
// double x[3], vtkCell *cell, vtkIdType cellId, double tol2,
// int& subId, double pcoords[3], double *weights) VTK_OVERRIDE;
// vtkIdType FindCell(
// double x[3], vtkCell *cell, vtkGenericCell *gencell,
// vtkIdType cellId, double tol2, int& subId,
// double pcoords[3], double *weights) VTK_OVERRIDE;
// vtkCell *FindAndGetCell(double x[3], vtkCell *cell, vtkIdType cellId,
// double tol2, int& subId, double pcoords[3],
// double *weights) VTK_OVERRIDE;
// int GetCellType(vtkIdType cellId) VTK_OVERRIDE;
// void GetCellPoints(vtkIdType cellId, vtkIdList *ptIds) VTK_OVERRIDE
// {vtkStructuredData::GetCellPoints(cellId,ptIds,this->DataDescription,
// this->GetDimensions());}
// void GetPointCells(vtkIdType ptId, vtkIdList *cellIds) VTK_OVERRIDE
// {vtkStructuredData::GetPointCells(ptId,cellIds,this->GetDimensions());}
// void ComputeBounds() VTK_OVERRIDE;
// int GetMaxCellSize() VTK_OVERRIDE {return 8;}; //voxel is the largest

publicAPI.getBounds = () => publicAPI.extentToBounds(model.extent);

publicAPI.extentToBounds = (ex) => {
// prettier-ignore
const corners = [
ex[0], ex[2], ex[4],
ex[1], ex[2], ex[4],
ex[0], ex[3], ex[4],
ex[1], ex[3], ex[4],
ex[0], ex[2], ex[5],
ex[1], ex[2], ex[5],
ex[0], ex[3], ex[5],
ex[1], ex[3], ex[5]];

const idx = new Float64Array([corners[0], corners[1], corners[2]]);
const vout = new Float64Array(3);
publicAPI.indexToWorld(idx, vout);
const bounds = [vout[0], vout[0], vout[1], vout[1], vout[2], vout[2]];
for (let i = 3; i < 24; i += 3) {
vec3.set(idx, corners[i], corners[i + 1], corners[i + 2]);
publicAPI.indexToWorld(idx, vout);
if (vout[0] < bounds[0]) {
bounds[0] = vout[0];
}
if (vout[1] < bounds[2]) {
bounds[2] = vout[1];
}
if (vout[2] < bounds[4]) {
bounds[4] = vout[2];
}
if (vout[0] > bounds[1]) {
bounds[1] = vout[0];
}
if (vout[1] > bounds[3]) {
bounds[3] = vout[1];
}
if (vout[2] > bounds[5]) {
bounds[5] = vout[2];
}
}

return bounds;
};

// Internal, shouldn't need to call this manually.
publicAPI.computeTransforms = () => {
mat4.fromTranslation(model.indexToWorld, model.origin);

model.indexToWorld[0] = model.direction[0];
model.indexToWorld[1] = model.direction[1];
model.indexToWorld[2] = model.direction[2];

model.indexToWorld[4] = model.direction[3];
model.indexToWorld[5] = model.direction[4];
model.indexToWorld[6] = model.direction[5];

model.indexToWorld[8] = model.direction[6];
model.indexToWorld[9] = model.direction[7];
model.indexToWorld[10] = model.direction[8];

mat4.scale(model.indexToWorld, model.indexToWorld, model.spacing);

mat4.invert(model.worldToIndex, model.indexToWorld);
};

//
// The direction matrix is a 3x3 basis for the I, J, K axes
// of the image. The rows of the matrix correspond to the
// axes directions in world coordinates. Direction must
// form an orthonormal basis, results are undefined if
// it is not.
//
publicAPI.setDirection = (...args) => {
if (model.deleted) {
vtkErrorMacro('instance deleted - cannot call any method');
return false;
}

let array = args;
// allow an array passed as a single arg.
if (
array.length === 1 &&
(Array.isArray(array[0]) ||
array[0].constructor === Float32Array ||
array[0].constructor === Float64Array)
) {
array = array[0];
}

if (array.length !== 9) {
throw new RangeError('Invalid number of values for array setter');
}
let changeDetected = false;
model.direction.forEach((item, index) => {
if (item !== array[index]) {
if (changeDetected) {
return;
}
changeDetected = true;
}
});

if (changeDetected) {
for (let i = 0; i < 9; ++i) {
model.direction[i] = array[i];
}
publicAPI.modified();
}
return true;
};

publicAPI.indexToWorld = (ain, aout = []) => {
vec3.transformMat4(aout, ain, model.indexToWorld);
return aout;
};
publicAPI.indexToWorldVec3 = publicAPI.indexToWorld;

publicAPI.worldToIndex = (ain, aout = []) => {
vec3.transformMat4(aout, ain, model.worldToIndex);
return aout;
};
publicAPI.worldToIndexVec3 = publicAPI.worldToIndex;

publicAPI.indexToWorldBounds = (bin, bout = []) => {
const in1 = [0, 0, 0];
const in2 = [0, 0, 0];
vtkBoundingBox.computeCornerPoints(bin, in1, in2);

const out1 = [0, 0, 0];
const out2 = [0, 0, 0];
vec3.transformMat4(out1, in1, model.indexToWorld);
vec3.transformMat4(out2, in2, model.indexToWorld);

return vtkMath.computeBoundsFromPoints(out1, out2, bout);
};

publicAPI.worldToIndexBounds = (bin, bout = []) => {
const in1 = [0, 0, 0];
const in2 = [0, 0, 0];
vtkBoundingBox.computeCornerPoints(bin, in1, in2);

const out1 = [0, 0, 0];
const out2 = [0, 0, 0];
vec3.transformMat4(out1, in1, model.worldToIndex);
vec3.transformMat4(out2, in2, model.worldToIndex);

return vtkMath.computeBoundsFromPoints(out1, out2, bout);
};

// Make sure the transform is correct
publicAPI.onModified(publicAPI.computeTransforms);
publicAPI.computeTransforms();

publicAPI.getCenter = () => {
const bounds = publicAPI.getBounds();
const center = [];

for (let i = 0; i < 3; i++) {
center[i] = (bounds[2 * i + 1] + bounds[2 * i]) / 2;
}

return center;
};

publicAPI.computeHistogram = (worldBounds, voxelFunc = null) => {
const bounds = [0, 0, 0, 0, 0, 0];
publicAPI.worldToIndexBounds(worldBounds, bounds);

const point1 = [0, 0, 0];
const point2 = [0, 0, 0];
vtkBoundingBox.computeCornerPoints(bounds, point1, point2);

vtkMath.roundVector(point1, point1);
vtkMath.roundVector(point2, point2);

const dimensions = publicAPI.getDimensions();

vtkMath.clampVector(
point1,
[0, 0, 0],
[dimensions[0] - 1, dimensions[1] - 1, dimensions[2] - 1],
point1
);
vtkMath.clampVector(
point2,
[0, 0, 0],
[dimensions[0] - 1, dimensions[1] - 1, dimensions[2] - 1],
point2
);

const yStride = dimensions[0];
const zStride = dimensions[0] * dimensions[1];

const pixels = publicAPI.getPointData().getScalars().getData();

let maximum = -Infinity;
let minimum = Infinity;
let sumOfSquares = 0;
let isum = 0;
let inum = 0;

for (let z = point1[2]; z <= point2[2]; z++) {
for (let y = point1[1]; y <= point2[1]; y++) {
let index = point1[0] + y * yStride + z * zStride;
for (let x = point1[0]; x <= point2[0]; x++) {
if (!voxelFunc || voxelFunc([x, y, z], bounds)) {
const pixel = pixels[index];

if (pixel > maximum) maximum = pixel;
if (pixel < minimum) minimum = pixel;
sumOfSquares += pixel * pixel;
isum += pixel;
inum += 1;
}

++index;
}
}
}

const average = inum > 0 ? isum / inum : 0;
const variance = sumOfSquares - average * average;
const sigma = Math.sqrt(variance);

return {
minimum,
maximum,
average,
variance,
sigma,
};
};

// TODO: use the unimplemented `vtkDataSetAttributes` for scalar length, that is currently also a TODO (GetNumberOfComponents).
// Scalar data could be tuples for color information?
publicAPI.computeIncrements = (extent, numberOfComponents = 1) => {
const increments = [];
let incr = numberOfComponents;

// Calculate array increment offsets
// similar to c++ vtkImageData::ComputeIncrements
for (let idx = 0; idx < 3; ++idx) {
increments[idx] = incr;
incr *= extent[idx * 2 + 1] - extent[idx * 2] + 1;
}
return increments;
};

/**
* @param {Number[]} index the localized `[i,j,k]` pixel array position. Float values will be rounded.
* @return {Number} the corresponding flattened index in the scalar array
*/
publicAPI.computeOffsetIndex = ([i, j, k]) => {
const extent = publicAPI.getExtent();
const numberOfComponents = publicAPI
.getPointData()
.getScalars()
.getNumberOfComponents();
const increments = publicAPI.computeIncrements(extent, numberOfComponents);
// Use the array increments to find the pixel index
// similar to c++ vtkImageData::GetArrayPointer
// Math.floor to catch "practically 0" e^-15 scenarios.
return Math.floor(
(Math.round(i) - extent[0]) * increments[0] +
(Math.round(j) - extent[2]) * increments[1] +
(Math.round(k) - extent[4]) * increments[2]
);
};

/**
* @param {Number[]} xyz the [x,y,z] Array in world coordinates
* @return {Number|NaN} the corresponding pixel's index in the scalar array
*/
publicAPI.getOffsetIndexFromWorld = (xyz) => {
const extent = publicAPI.getExtent();
const index = publicAPI.worldToIndex(xyz);

// Confirm indexed i,j,k coords are within the bounds of the volume
for (let idx = 0; idx < 3; ++idx) {
if (index[idx] < extent[idx * 2] || index[idx] > extent[idx * 2 + 1]) {
vtkErrorMacro(
`GetScalarPointer: Pixel ${index} is not in memory. Current extent = ${extent}`
);
return NaN;
}
}

// Assumed the index here is within 0 <-> scalarData.length, but doesn't hurt to check upstream
return publicAPI.computeOffsetIndex(index);
};
/**
* @param {Number[]} xyz the [x,y,z] Array in world coordinates
* @param {Number?} comp the scalar component index for multi-component scalars
* @return {Number|NaN} the corresponding pixel's scalar value
*/
publicAPI.getScalarValueFromWorld = (xyz, comp = 0) => {
const numberOfComponents = publicAPI
.getPointData()
.getScalars()
.getNumberOfComponents();
if (comp < 0 || comp >= numberOfComponents) {
vtkErrorMacro(
`GetScalarPointer: Scalar Component ${comp} is not within bounds. Current Scalar numberOfComponents: ${numberOfComponents}`
);
return NaN;
}
const offsetIndex = publicAPI.getOffsetIndexFromWorld(xyz);
if (Number.isNaN(offsetIndex)) {
// VTK Error Macro will have been tripped already, no need to do it again,
return offsetIndex;
}

return publicAPI
.getPointData()
.getScalars()
.getComponent(offsetIndex, comp);
};
}

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

const DEFAULT_VALUES = {
direction: null, // a mat3
indexToWorld: null, // a mat4
worldToIndex: null, // a mat4
spacing: [1.0, 1.0, 1.0],
origin: [0.0, 0.0, 0.0],
extent: [0, -1, 0, -1, 0, -1],
dataDescription: StructuredType.EMPTY,
};

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

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

// Inheritance
vtkDataSet.extend(publicAPI, model, initialValues);

if (!model.direction) {
model.direction = mat3.identity(new Float64Array(9));
} else if (Array.isArray(model.direction)) {
model.direction = new Float64Array(model.direction.slice(0, 9));
}

model.indexToWorld = new Float64Array(16);
model.worldToIndex = new Float64Array(16);

// Set/Get methods
macro.get(publicAPI, model, ['direction', 'indexToWorld', 'worldToIndex']);
macro.setGetArray(publicAPI, model, ['origin', 'spacing'], 3);
macro.getArray(publicAPI, model, ['extent'], 6);

// Object specific methods
vtkImageData(publicAPI, model);
}

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

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

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

export default { newInstance, extend };