ImageReslice

Introduction

vtkImageReslice - Reslices a volume along a new set of axes

vtkImageReslice is the swiss-army-knife of image geometry filters:
It can permute, rotate, flip, scale, resample, deform, and pad image
data in any combination with reasonably high efficiency. Simple
operations such as permutation, resampling and padding are done
with similar efficiently to the specialized vtkImagePermute,
vtkImageResample, and vtkImagePad filters. There are a number of
tasks that vtkImageReslice is well suited for:

  1. Application of simple rotations, scales, and translations to
    an image. It is often a good idea to use vtkImageChangeInformation
    to center the image first, so that scales and rotations occur around
    the center rather than around the lower-left corner of the image.
  2. Extraction of slices from an image volume. The method
    SetOutputDimensionality(2) is used to specify that want to output a
    slice rather than a volume. You can use both the resliceAxes and the
    resliceTransform at the same time, in order to extract slices from a
    volume that you have applied a transformation to.

Methods

applyTransform

Apply newTrans, then translate by -origin, then scale by inInvSpacing

Argument Type Required Description
newTrans Yes
inPoint Yes
inOrigin Yes
inInvSpacing Yes

Returns

Type Description

canUseNearestNeighbor

Argument Type Required Description
matrix Yes
outExt Yes

Returns

Type Description

clamp

Internal function uses vtkInterpolationMathClamp on numscalars * n elements

Argument Type Required Description
outPtr Yes
inPtr Yes
numscalars Yes
n Yes
min Yes
max Yes

Returns

Type Description
The number of elements processed

convert

Internal function uses Math.round on numscalars * n elements

Argument Type Required Description
outPtr Yes
inPtr Yes
numscalars Yes
n Yes

Returns

Type Description
The number of elements processed

extend

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

getAutoCropOutput

Returns

Type Description

getAutoCroppedOutputBounds

Compute the bounds required to ensure that none of the data will be cropped

Argument Type Required Description
input Yes

Returns

Type Description

getBackgroundColor

Returns

Type Description

getBorder

Returns

Type Description

getCompositeFunc

Returns the right composition function

Argument Type Required Description
slabMode Yes
slabTrapezoidIntegration Yes

Returns

Type Description

getConversionFunc

Setup the conversion function and return the right one

Argument Type Required Description
inputType Yes
dataType Yes
scalarShift Yes
scalarScale Yes
forceClamping Yes

Returns

Type Description
publicAPI.convert or publicAPI.clamp

getDataTypeMinMax

The min and max of each data type
Defaults to a range of 0 to 255

Argument Type Required Description
dataType Yes

Returns

Type Description

getIndexMatrix

The transform matrix supplied by the user converts output coordinates
to input coordinates.
To speed up the pixel lookup, the following function provides a
matrix which converts output pixel indices to input pixel indices.
This will also concatenate the ResliceAxes and the ResliceTransform
if possible (if the ResliceTransform is a 4x4 matrix transform).
If it does, this->OptimizedTransform will be set to nullptr, otherwise
this->OptimizedTransform will be equal to this->ResliceTransform.

Argument Type Required Description
input vtkImageData Yes
output vtkImageData Yes

Returns

Type Description

getInterpolationMode

Returns

Type Description

getMirror

Returns

Type Description

getOutputDimensionality

Returns

Type Description

getOutputDirection

Returns

Type Description

getOutputExtent

Returns

Type Description

getOutputOrigin

Returns

Type Description

getOutputScalarType

Returns

Type Description

getOutputSpacing

Returns

Type Description

getResliceAxes

Returns

Type Description

getResliceTransform

Returns

Type Description

getScalarScale

Returns

Type Description

getScalarShift

Returns

Type Description

getSetPixelsFunc

Returns the right function used to copy the pixels

Argument Type Required Description
dataType Yes
dataSize Yes
numscalars Yes
dataPtr Yes

Returns

Type Description
publicAPI.set or publicAPI.set1

getSlabMode

Returns

Type Description

getSlabNumberOfSlices

Returns

Type Description

getSlabSliceSpacingFraction

Returns

Type Description

getSlabTrapezoidIntegration

Returns

Type Description

getTransformInputSampling

Returns

Type Description

getWrap

Returns

Type Description

isIdentityMatrix

Argument Type Required Description
matrix Yes

Returns

Type Description

isPermutationMatrix

A permutation matrix is the identity matrix with the colomn (or rows) shuffled

Argument Type Required Description
matrix Yes

Returns

Type Description

isPerspectiveMatrix

Argument Type Required Description
matrix Yes

Returns

Type Description

newInstance

Method used to create a new instance of vtkImageReslice

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

requestData

Argument Type Required Description
inData Yes
outData Yes

rescaleScalars

Argument Type Required Description
floatData Yes
components Yes
n Yes
scalarShift Yes
scalarScale Yes

Returns

Type Description

set

Copy the numscalars * n first elements from an array to another

Argument Type Required Description
outPtr Yes
inPtr Yes
numscalars Yes
n Yes

Returns

Type Description
The number of copied elements

set1

Fill the n first elements of the output array with the very first element of the input array

Argument Type Required Description
outPtr Yes
inPtr Yes
numscalars Yes
n Yes

Returns

Type Description
The number of elements set in the output array

setAutoCropOutput

Turn this on if you want to guarantee that the extent of the output will
be large enough to ensure that none of the data will be cropped.
Defaults to false.

Argument Type Required Description
autoCropOutput Yes

Returns

Type Description

setBackgroundColor

Set the background color (for multi-component images).
Defaults to full opaque black.

Argument Type Required Description
backgroundColor Yes

Returns

Type Description

setBorder

Extend the apparent input border by a half voxel.
This changes how interpolation is handled at the borders of the
input image: if the center of an output voxel is beyond the edge
of the input image, but is within a half voxel width of the edge
(using the input voxel width), then the value of the output voxel
is calculated as if the input’s edge voxels were duplicated past
the edges of the input.
This has no effect if Mirror or Wrap are on.
Defaults to true.

Argument Type Required Description
border Yes

Returns

Type Description

setInterpolationMode

Set interpolation mode.
Only nearest neighbor is supported at the moment.
Defaults to nearest neighbor.

Argument Type Required Description
interpolationMode Yes

Returns

Type Description

setMirror

Turn on mirror-pad feature. This will override the wrap-pad.
Defaults to false.

Argument Type Required Description
mirror Yes

Returns

Type Description

setOutputDimensionality

Force the dimensionality of the output to either 1, 2, 3 or 0.
If the dimensionality is 2D, then the Z extent of the output is forced to
(0,0) and the Z origin of the output is forced to 0.0 (i.e. the output
extent is confined to the xy plane). If the dimensionality is 1D, the
output extent is confined to the x axis. For 0D, the output extent
consists of a single voxel at (0,0,0).
Defaults to 3.

Argument Type Required Description
outputDimensionality Yes

Returns

Type Description

setOutputDirection

Set the direction for the output data.
By default, the direction of the input data is passed to the output.
But if SetOutputDirection() is used, then the image will be resliced
according to the new output direction. Unlike SetResliceAxes(), this does
not change the physical coordinate system for the image. Instead, it
changes the orientation of the sampling grid while maintaining the same
physical coordinate system.
Defaults to null (automatically computed).

Argument Type Required Description
outputDirection Yes

Returns

Type Description

setOutputExtent

Set the extent for the output data.
The default output extent is the input extent permuted through the
ResliceAxes.
Defaults to null (automatically computed).

Argument Type Required Description
outputExtent Yes

Returns

Type Description

setOutputOrigin

Set the origin for the output data.
The default output origin is the input origin permuted through the
ResliceAxes.
Defaults to null (automatically computed).

Argument Type Required Description
outputOrigin Yes

Returns

Type Description

setOutputScalarType

Set the scalar type of the output to be different from the input.
The default value is null, which means that the input scalar type will be
used to set the output scalar type. Otherwise, this must be set to one
of the following types: VtkDataTypes.CHAR, VtkDataTypes.SIGNED_CHAR,
VtkDataTypes.UNSIGNED_CHAR, VtkDataTypes.SHORT, VtkDataTypes.UNSIGNED_SHORT,
VtkDataTypes.INT, VtkDataTypes.UNSIGNED_INT, VtkDataTypes.FLOAT or
VtkDataTypes.DOUBLE. Other types are not permitted. If the output type
is an integer type, the output will be rounded and clamped to the limits of
the type.

See the documentation for vtkDataArray::getDataType() for additional data type settings.
Defaults to null (automatically computed).

Argument Type Required Description
outputScalarType Yes

Returns

Type Description

setOutputSpacing

Set the voxel spacing for the output data.
The default output spacing is the input spacing permuted through the
ResliceAxes.
Defaults to null (automatically computed).

Argument Type Required Description
outputSpacing Yes

Returns

Type Description

setResliceAxes

This method is used to set up the axes for the output voxels.
The output Spacing, Origin, and Extent specify the locations
of the voxels within the coordinate system defined by the axes.
The ResliceAxes are used most often to permute the data, e.g.
to extract ZY or XZ slices of a volume as 2D XY images.
The first column of the matrix specifies the x-axis
vector (the fourth element must be set to zero), the second
column specifies the y-axis, and the third column the
z-axis. The fourth column is the origin of the
axes (the fourth element must be set to one).

Argument Type Required Description
resliceAxes Yes

Returns

Type Description

setResliceTransform

Set a transform to be applied to the resampling grid that has been
defined via the ResliceAxes and the output Origin, Spacing and Extent.
Note that applying a transform to the resampling grid (which lies in
the output coordinate system) is equivalent to applying the inverse of
that transform to the input volume. Nonlinear transforms such as
vtkGridTransform and vtkThinPlateSplineTransform can be used here.
Defaults to undefined.

Argument Type Required Description
resliceTransform Yes

Returns

Type Description

setScalarScale

Set multiplication factor to apply to all the output voxels.
After a sample value has been interpolated from the input image, the
equation u = (v + ScalarShift)*ScalarScale will be applied to it before
it is written to the output image. The result will always be clamped to
the limits of the output data type.
Defaults to 1.

Argument Type Required Description
scalarScale Yes

Returns

Type Description

setScalarShift

Set a value to add to all the output voxels.
After a sample value has been interpolated from the input image, the
equation u = (v + ScalarShift)*ScalarScale will be applied to it before
it is written to the output image. The result will always be clamped to
the limits of the output data type.
Defaults to 0.

Argument Type Required Description
scalarShift Yes

Returns

Type Description

setSlabMode

Set the slab mode, for generating thick slices.
The default is SlabMode.MIN. If SetSlabNumberOfSlices(N) is called with N
greater than one, then each output slice will actually be a composite of N
slices. This method specifies the compositing mode to be used.

Argument Type Required Description
slabMode Yes

Returns

Type Description

setSlabNumberOfSlices

Set the number of slices that will be combined to create the slab.
Defaults to 1.

Argument Type Required Description
numberOfSlices Yes

Returns

Type Description

setSlabSliceSpacingFraction

The slab spacing as a fraction of the output slice spacing.
When one of the various slab modes is chosen, each output slice is
produced by generating several “temporary” output slices and then
combining them according to the slab mode. By default, the spacing between
these temporary slices is the Z component of the OutputSpacing. This
method sets the spacing between these temporary slices to be a fraction of
the output spacing.
Defaults to 1.

Argument Type Required Description
slabSliceSpacingFraction Yes

Returns

Type Description

setSlabTrapezoidIntegration

Use trapezoid integration for slab computation.
All this does is weigh the first and last slices by half when doing sum
and mean. It is off by default.
Defaults to false.

Argument Type Required Description
slabTrapezoidIntegration Yes

Returns

Type Description

setTransformInputSampling

Specify whether to transform the spacing, origin and extent of the Input
(or the InformationInput) according to the direction cosines and origin of
the ResliceAxes before applying them as the default output spacing, origin
and extent.
Defaults to false.

Argument Type Required Description
transformInputSampling Yes

Returns

Type Description

setWrap

Turn on wrap-pad feature.
Defaults to false.

Argument Type Required Description
wrap Yes

Returns

Type Description

vtkImageResliceExecute

Main filter logic

Argument Type Required Description
input Yes
output Yes

Returns

Type Description

Source

Constants.d.ts
export declare enum SlabMode {
MIN = 0,
MAX = 1,
MEAN = 2,
SUM = 3,
}

declare const _default: {
SlabMode: typeof SlabMode;
};

export default _default;
Constants.js
export const SlabMode = {
MIN: 0,
MAX: 1,
MEAN: 2,
SUM: 3,
};

export default {
SlabMode,
};
index.d.ts
import { Bounds, Extent, Nullable, RGBAColor, TypedArray } from "../../../types";
import { InterpolationMode } from "../AbstractImageInterpolator/Constants";
import { SlabMode } from "./Constants";
import { vtkAlgorithm, vtkObject, vtkRange } from "../../../interfaces";
import vtkImageData from "../../../Common/DataModel/ImageData";
import vtkTransform from "../../../Common/Transform/Transform";

import { mat3, mat4, vec3, vec4 } from 'gl-matrix';

/**
*
*/
export interface IImageResliceInitialValues {
transformInputSampling: boolean,
autoCropOutput: boolean,
outputDimensionality: number,
outputSpacing: Nullable<vec3>, // automatically computed if null
outputOrigin: Nullable<vec3>, // automatically computed if null
outputDirection: Nullable<mat3>, // identity if null
outputExtent: Nullable<Extent>, // automatically computed if null
outputScalarType: Nullable<string>,
wrap: boolean, // don't wrap
mirror: boolean, // don't mirror
border: boolean, // apply a border
interpolationMode: InterpolationMode, // only NEAREST supported so far
slabMode: SlabMode,
slabTrapezoidIntegration: boolean,
slabNumberOfSlices: number,
slabSliceSpacingFraction: number,
optimization: boolean, // not supported yet
scalarShift: number, // for rescaling the data
scalarScale: number,
backgroundColor: RGBAColor,
resliceAxes: Nullable<mat4>,
resliceTransform?: vtkTransform,
interpolator: any, // A vtkImageInterpolator (missing typescript header)
usePermuteExecute: boolean, // no supported yet
}

type vtkImageResliceBase = Omit<vtkObject, 'set'> & vtkAlgorithm;

export interface vtkImageReslice extends vtkImageResliceBase {
/**
*
* @param inData
* @param outData
*/
requestData(inData: any, outData: any): void;

/**
* Main filter logic
* @param input
* @param output
* @returns
*/
vtkImageResliceExecute: (input: vtkImageData, output: vtkImageData) => void;

/**
* The transform matrix supplied by the user converts output coordinates
* to input coordinates.
* To speed up the pixel lookup, the following function provides a
* matrix which converts output pixel indices to input pixel indices.
* This will also concatenate the ResliceAxes and the ResliceTransform
* if possible (if the ResliceTransform is a 4x4 matrix transform).
* If it does, this->OptimizedTransform will be set to nullptr, otherwise
* this->OptimizedTransform will be equal to this->ResliceTransform.
* @param {vtkImageData} input
* @param {vtkImageData} output
* @returns
*/
getIndexMatrix: (input: vtkImageData, output: vtkImageData) => mat4;

/**
* Compute the bounds required to ensure that none of the data will be cropped
* @param input
* @returns
*/
getAutoCroppedOutputBounds: (input: vtkImageData) => Bounds;

/**
* The min and max of each data type
* Defaults to a range of 0 to 255
* @param dataType
* @returns
*/
getDataTypeMinMax: (dataType: string) => vtkRange;

/**
* Internal function uses vtkInterpolationMathClamp on `numscalars * n` elements
* @param outPtr
* @param inPtr
* @param numscalars
* @param n
* @param min
* @param max
* @returns The number of elements processed
*/
clamp: (outPtr: TypedArray, inPtr: TypedArray, numscalars: number, n: number, min: number, max: number) => number;

/**
* Internal function uses Math.round on `numscalars * n` elements
* @param outPtr
* @param inPtr
* @param numscalars
* @param n
* @returns The number of elements processed
*/
convert: (outPtr: TypedArray, inPtr: TypedArray, numscalars: number, n: number) => number;

/**
* Setup the conversion function and return the right one
* @param inputType
* @param dataType
* @param scalarShift
* @param scalarScale
* @param forceClamping
* @returns publicAPI.convert or publicAPI.clamp
*/
getConversionFunc: (inputType: string, dataType: string, scalarShift: number, scalarScale: number, forceClamping: boolean) => (this["convert"] & this["clamp"]);

/**
* Copy the `numscalars * n` first elements from an array to another
* @param outPtr
* @param inPtr
* @param numscalars
* @param n
* @returns The number of copied elements
*/
set: (outPtr: TypedArray, inPtr: TypedArray, numscalars: number, n: number) => number;

/**
* Fill the `n` first elements of the output array with the very first element of the input array
* @param outPtr
* @param inPtr
* @param numscalars
* @param n
* @returns The number of elements set in the output array
*/
set1: (outPtr: TypedArray, inPtr: TypedArray, numscalars: number, n: number) => number;

/**
* Returns the right function used to copy the pixels
* @param dataType
* @param dataSize
* @param numscalars
* @param dataPtr
* @returns publicAPI.set or publicAPI.set1
*/
getSetPixelsFunc: (dataType: any, dataSize: any, numscalars: number, dataPtr: any) => (this["set"] & this["set1"]);

/**
* Returns the right composition function
* @param slabMode
* @param slabTrapezoidIntegration
* @returns
*/
getCompositeFunc: (slabMode: SlabMode, slabTrapezoidIntegration: boolean) => ((tmpPtr: TypedArray, inComponents: number, sampleCount: number) => void);

/**
* Apply `newTrans`, then translate by `-origin`, then scale by `inInvSpacing`
* @param newTrans
* @param inPoint
* @param inOrigin
* @param inInvSpacing
* @returns
*/
applyTransform: (newTrans: mat4, inPoint: vec4, inOrigin: vec3, inInvSpacing: vec3) => void;

/**
*
* @param floatData
* @param components
* @param n
* @param scalarShift
* @param scalarScale
* @returns
*/
rescaleScalars: (floatData: TypedArray, components: number, n: number, scalarShift: number, scalarScale: number) => void;

/**
* A permutation matrix is the identity matrix with the colomn (or rows) shuffled
* @param matrix
* @returns
*/
isPermutationMatrix: (matrix: mat4) => boolean;

/**
*
* @param matrix
* @returns
*/
isIdentityMatrix: (matrix: mat4) => boolean;

/**
*
* @param matrix
* @returns
*/
isPerspectiveMatrix: (matrix: mat4) => boolean;

/**
*
* @param matrix
* @param outExt
* @returns
*/
canUseNearestNeighbor: (matrix: mat4, outExt: Extent) => boolean;

// Setters and getters

/**
* Set the slab mode, for generating thick slices.
* The default is SlabMode.MIN. If SetSlabNumberOfSlices(N) is called with N
* greater than one, then each output slice will actually be a composite of N
* slices. This method specifies the compositing mode to be used.
* @param slabMode
* @returns
*/
setSlabMode: (slabMode: SlabMode) => boolean;

/**
* @see setSlabMode
* @returns
*/
getSlabMode: () => SlabMode;

/**
* Set the number of slices that will be combined to create the slab.
* Defaults to 1.
* @param numberOfSlices
* @returns
*/
setSlabNumberOfSlices: (slabNumberOfSlices: number) => boolean;

/**
* @see setSlabNumberOfSlices
* @returns
*/
getSlabNumberOfSlices: () => number;

/**
* Specify whether to transform the spacing, origin and extent of the Input
* (or the InformationInput) according to the direction cosines and origin of
* the ResliceAxes before applying them as the default output spacing, origin
* and extent.
* Defaults to false.
* @param transformInputSampling
* @returns
*/
setTransformInputSampling: (transformInputSampling: boolean) => boolean;

/**
* @see setTransformInputSampling
* @returns
*/
getTransformInputSampling: () => boolean;

/**
* Turn this on if you want to guarantee that the extent of the output will
* be large enough to ensure that none of the data will be cropped.
* Defaults to false.
* @param autoCropOutput
* @returns
*/
setAutoCropOutput: (autoCropOutput: boolean) => boolean;

/**
* @see setAutoCropOutput
* @returns
*/
getAutoCropOutput: () => boolean;

/**
* Force the dimensionality of the output to either 1, 2, 3 or 0.
* If the dimensionality is 2D, then the Z extent of the output is forced to
* (0,0) and the Z origin of the output is forced to 0.0 (i.e. the output
* extent is confined to the xy plane). If the dimensionality is 1D, the
* output extent is confined to the x axis. For 0D, the output extent
* consists of a single voxel at (0,0,0).
* Defaults to 3.
* @param outputDimensionality
* @returns
*/
setOutputDimensionality: (outputDimensionality: number) => boolean;

/**
* @see setOutputDimensionality
* @returns
*/
getOutputDimensionality: () => number;

/**
* Set the voxel spacing for the output data.
* The default output spacing is the input spacing permuted through the
* ResliceAxes.
* Defaults to null (automatically computed).
* @param outputSpacing
* @returns
*/
setOutputSpacing: (outputSpacing: vec3 | null) => boolean;

/**
* @see setOutputSpacing
* @returns
*/
getOutputSpacing: () => vec3 | null;

/**
* Set the origin for the output data.
* The default output origin is the input origin permuted through the
* ResliceAxes.
* Defaults to null (automatically computed).
* @param outputOrigin
* @returns
*/
setOutputOrigin: (outputOrigin: vec3 | null) => boolean;

/**
* @see setOutputOrigin
* @returns
*/
getOutputOrigin: () => vec3 | null;

/**
* Set the extent for the output data.
* The default output extent is the input extent permuted through the
* ResliceAxes.
* Defaults to null (automatically computed).
* @param outputExtent
* @returns
*/
setOutputExtent: (outputExtent: Extent | null) => boolean;

/**
* @see setOutputExtent
* @returns
*/
getOutputExtent: () => Extent | null;

/**
* Set the direction for the output data.
* By default, the direction of the input data is passed to the output.
* But if SetOutputDirection() is used, then the image will be resliced
* according to the new output direction. Unlike SetResliceAxes(), this does
* not change the physical coordinate system for the image. Instead, it
* changes the orientation of the sampling grid while maintaining the same
* physical coordinate system.
* Defaults to null (automatically computed).
* @param outputDirection
* @returns
*/
setOutputDirection: (outputDirection: mat3 | null) => boolean;

/**
* @see setOutputDirection
* @returns
*/
getOutputDirection: () => mat3 | null;

/**
* Set the background color (for multi-component images).
* Defaults to full opaque black.
* @param backgroundColor
* @returns
*/
setBackgroundColor: (backgroundColor: RGBAColor) => boolean;

/**
* @see setBackgroundColor
* @returns
*/
getBackgroundColor: () => RGBAColor;

/**
* This method is used to set up the axes for the output voxels.
* The output Spacing, Origin, and Extent specify the locations
* of the voxels within the coordinate system defined by the axes.
* The ResliceAxes are used most often to permute the data, e.g.
* to extract ZY or XZ slices of a volume as 2D XY images.
* The first column of the matrix specifies the x-axis
* vector (the fourth element must be set to zero), the second
* column specifies the y-axis, and the third column the
* z-axis. The fourth column is the origin of the
* axes (the fourth element must be set to one).
* @param resliceAxes
* @returns
*/
setResliceAxes: (resliceAxes: mat4) => boolean;

/**
* @see setResliceAxes
* @returns
*/
getResliceAxes: () => mat4;

/**
* Set the scalar type of the output to be different from the input.
* The default value is null, which means that the input scalar type will be
* used to set the output scalar type. Otherwise, this must be set to one
* of the following types: VtkDataTypes.CHAR, VtkDataTypes.SIGNED_CHAR,
* VtkDataTypes.UNSIGNED_CHAR, VtkDataTypes.SHORT, VtkDataTypes.UNSIGNED_SHORT,
* VtkDataTypes.INT, VtkDataTypes.UNSIGNED_INT, VtkDataTypes.FLOAT or
* VtkDataTypes.DOUBLE. Other types are not permitted. If the output type
* is an integer type, the output will be rounded and clamped to the limits of
* the type.
*
* See the documentation for [vtkDataArray::getDataType()](../api/Common_Core_DataArray.html#getDataType-String) for additional data type settings.
* Defaults to null (automatically computed).
* @param outputScalarType
* @returns
*/
setOutputScalarType: (outputScalarType: string | null) => boolean;

/**
* @see setOutputScalarType
* @returns
*/
getOutputScalarType: () => string | null;

/**
* Set a value to add to all the output voxels.
* After a sample value has been interpolated from the input image, the
* equation u = (v + ScalarShift)*ScalarScale will be applied to it before
* it is written to the output image. The result will always be clamped to
* the limits of the output data type.
* Defaults to 0.
* @param scalarShift
* @returns
*/
setScalarShift: (scalarShift: number) => boolean;

/**
* @see setScalarShift
* @returns
*/
getScalarShift: () => number;

/**
* Set multiplication factor to apply to all the output voxels.
* After a sample value has been interpolated from the input image, the
* equation u = (v + ScalarShift)*ScalarScale will be applied to it before
* it is written to the output image. The result will always be clamped to
* the limits of the output data type.
* Defaults to 1.
* @param scalarScale
* @returns
*/
setScalarScale: (scalarScale: number) => boolean;

/**
* @see setScalarScale
* @returns
*/
getScalarScale: () => number;

/**
* Turn on wrap-pad feature.
* Defaults to false.
* @param wrap
* @returns
*/
setWrap: (wrap: boolean) => boolean;

/**
* @see setWrap
* @returns
*/
getWrap: () => boolean;

/**
* Turn on mirror-pad feature. This will override the wrap-pad.
* Defaults to false.
* @param mirror
* @returns
*/
setMirror: (mirror: boolean) => boolean;

/**
* @see setMirror
* @returns
*/
getMirror: () => boolean;

/**
* Extend the apparent input border by a half voxel.
* This changes how interpolation is handled at the borders of the
* input image: if the center of an output voxel is beyond the edge
* of the input image, but is within a half voxel width of the edge
* (using the input voxel width), then the value of the output voxel
* is calculated as if the input's edge voxels were duplicated past
* the edges of the input.
* This has no effect if Mirror or Wrap are on.
* Defaults to true.
* @param border
* @returns
*/
setBorder: (border: boolean) => boolean;

/**
* @see setBorder
* @returns
*/
getBorder: () => boolean;

/**
* Set interpolation mode.
* Only nearest neighbor is supported at the moment.
* Defaults to nearest neighbor.
* @param interpolationMode
* @returns
*/
setInterpolationMode: (interpolationMode: InterpolationMode) => boolean;

/**
* @see setInterpolationMode
* @returns
*/
getInterpolationMode: () => InterpolationMode;

/**
* Set a transform to be applied to the resampling grid that has been
* defined via the ResliceAxes and the output Origin, Spacing and Extent.
* Note that applying a transform to the resampling grid (which lies in
* the output coordinate system) is equivalent to applying the inverse of
* that transform to the input volume. Nonlinear transforms such as
* vtkGridTransform and vtkThinPlateSplineTransform can be used here.
* Defaults to undefined.
* @param resliceTransform
* @returns
*/
setResliceTransform: (resliceTransform: vtkTransform | undefined) => boolean;

/**
* @see setResliceTransform
* @returns
*/
getResliceTransform: () => vtkTransform | undefined;

/**
* Use trapezoid integration for slab computation.
* All this does is weigh the first and last slices by half when doing sum
* and mean. It is off by default.
* Defaults to false.
* @param slabTrapezoidIntegration
* @returns
*/
setSlabTrapezoidIntegration: (slabTrapezoidIntegration: boolean) => boolean;

/**
* @see setSlabTrapezoidIntegration
* @returns
*/
getSlabTrapezoidIntegration: () => boolean;

/**
* The slab spacing as a fraction of the output slice spacing.
* When one of the various slab modes is chosen, each output slice is
* produced by generating several "temporary" output slices and then
* combining them according to the slab mode. By default, the spacing between
* these temporary slices is the Z component of the OutputSpacing. This
* method sets the spacing between these temporary slices to be a fraction of
* the output spacing.
* Defaults to 1.
* @param slabSliceSpacingFraction
* @returns
*/
setSlabSliceSpacingFraction: (slabSliceSpacingFraction: number) => boolean;

/**
* @see setSlabSliceSpacingFraction
* @returns
*/
getSlabSliceSpacingFraction: () => number;
}

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

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


/**
* vtkImageReslice - Reslices a volume along a new set of axes
*
* vtkImageReslice is the swiss-army-knife of image geometry filters:
* It can permute, rotate, flip, scale, resample, deform, and pad image
* data in any combination with reasonably high efficiency. Simple
* operations such as permutation, resampling and padding are done
* with similar efficiently to the specialized vtkImagePermute,
* vtkImageResample, and vtkImagePad filters. There are a number of
* tasks that vtkImageReslice is well suited for:
* 1) Application of simple rotations, scales, and translations to
* an image. It is often a good idea to use vtkImageChangeInformation
* to center the image first, so that scales and rotations occur around
* the center rather than around the lower-left corner of the image.
* 2) Extraction of slices from an image volume. The method
* SetOutputDimensionality(2) is used to specify that want to output a
* slice rather than a volume. You can use both the resliceAxes and the
* resliceTransform at the same time, in order to extract slices from a
* volume that you have applied a transformation to.
* */
export declare const vtkImageReslice: {
newInstance: typeof newInstance;
extend: typeof extend;
}
export default vtkImageReslice;
index.js
import { vec4, mat4 } from 'gl-matrix';

import macro, { vtkWarningMacro } from 'vtk.js/Sources/macros';
import vtkDataArray from 'vtk.js/Sources/Common/Core/DataArray';
import vtkMath from 'vtk.js/Sources/Common/Core/Math';
import vtkMatrixBuilder from 'vtk.js/Sources/Common/Core/MatrixBuilder';
import { VtkDataTypes } from 'vtk.js/Sources/Common/Core/DataArray/Constants';
import vtkBoundingBox from 'vtk.js/Sources/Common/DataModel/BoundingBox';
import vtkImageData from 'vtk.js/Sources/Common/DataModel/ImageData';
import vtkImageInterpolator from 'vtk.js/Sources/Imaging/Core/ImageInterpolator';
import vtkImagePointDataIterator from 'vtk.js/Sources/Imaging/Core/ImagePointDataIterator';
import {
ImageBorderMode,
InterpolationMode,
} from 'vtk.js/Sources/Imaging/Core/AbstractImageInterpolator/Constants';
import {
vtkInterpolationMathFloor,
vtkInterpolationMathRound,
vtkInterpolationMathClamp,
} from 'vtk.js/Sources/Imaging/Core/AbstractImageInterpolator/InterpolationInfo';
import Constants from 'vtk.js/Sources/Imaging/Core/ImageReslice/Constants';

const { SlabMode } = Constants;

const { vtkErrorMacro } = macro;

// ----------------------------------------------------------------------------
// vtkImageReslice methods
// ----------------------------------------------------------------------------

function vtkImageReslice(publicAPI, model) {
// Set our className
model.classHierarchy.push('vtkImageReslice');
const superClass = { ...publicAPI };

const indexMatrix = mat4.identity(new Float64Array(16));
let optimizedTransform = null;

function getImageResliceSlabTrap(tmpPtr, inComponents, sampleCount, f) {
const n = sampleCount - 1;
for (let i = 0; i < inComponents; i += 1) {
let result = tmpPtr[i] * 0.5;
for (let j = 1; j < n; j += 1) {
result += tmpPtr[i + j * inComponents];
}
result += tmpPtr[i + n * inComponents] * 0.5;
tmpPtr[i] = result * f;
}
}

function getImageResliceSlabSum(tmpPtr, inComponents, sampleCount, f) {
for (let i = 0; i < inComponents; i += 1) {
let result = tmpPtr[i];
for (let j = 1; j < sampleCount; j += 1) {
result += tmpPtr[i + j * inComponents];
}
tmpPtr[i] = result * f;
}
}

function getImageResliceCompositeMinValue(tmpPtr, inComponents, sampleCount) {
for (let i = 0; i < inComponents; i += 1) {
let result = tmpPtr[i];
for (let j = 1; j < sampleCount; j += 1) {
result = Math.min(result, tmpPtr[i + j * inComponents]);
}
tmpPtr[i] = result;
}
}

function getImageResliceCompositeMaxValue(tmpPtr, inComponents, sampleCount) {
for (let i = 0; i < inComponents; i += 1) {
let result = tmpPtr[i];
for (let j = 1; j < sampleCount; j += 1) {
result = Math.max(result, tmpPtr[i + j * inComponents]);
}
tmpPtr[i] = result;
}
}

function getImageResliceCompositeMeanValue(
tmpPtr,
inComponents,
sampleCount
) {
const f = 1.0 / sampleCount;
getImageResliceSlabSum(tmpPtr, inComponents, sampleCount, f);
}

function getImageResliceCompositeMeanTrap(tmpPtr, inComponents, sampleCount) {
const f = 1.0 / (sampleCount - 1);
getImageResliceSlabTrap(tmpPtr, inComponents, sampleCount, f);
}

function getImageResliceCompositeSumValue(tmpPtr, inComponents, sampleCount) {
const f = 1.0;
getImageResliceSlabSum(tmpPtr, inComponents, sampleCount, f);
}

function getImageResliceCompositeSumTrap(tmpPtr, inComponents, sampleCount) {
const f = 1.0;
getImageResliceSlabTrap(tmpPtr, inComponents, sampleCount, f);
}

publicAPI.getMTime = () => {
let mTime = superClass.getMTime();
if (model.resliceTransform) {
mTime = Math.max(mTime, model.resliceTransform.getMTime());
}
return mTime;
};

publicAPI.setResliceAxes = (resliceAxes) => {
if (!model.resliceAxes) {
model.resliceAxes = mat4.identity(new Float64Array(16));
}

if (!mat4.exactEquals(model.resliceAxes, resliceAxes)) {
mat4.copy(model.resliceAxes, resliceAxes);

publicAPI.modified();
return true;
}
return false;
};

publicAPI.requestData = (inData, outData) => {
// implement requestData
const input = inData[0];

if (!input) {
vtkErrorMacro('Invalid or missing input');
return;
}

// console.time('reslice');

// Retrieve output and volume data
const origin = input.getOrigin();
const inSpacing = input.getSpacing();
const dims = input.getDimensions();
const inScalars = input.getPointData().getScalars();
const inWholeExt = [0, dims[0] - 1, 0, dims[1] - 1, 0, dims[2] - 1];

const outOrigin = [0, 0, 0];
const outSpacing = [1, 1, 1];
const outWholeExt = [0, 0, 0, 0, 0, 0];
const outDims = [0, 0, 0];

const matrix = mat4.identity(new Float64Array(16));
if (model.resliceAxes) {
mat4.multiply(matrix, matrix, model.resliceAxes);
}
const imatrix = new Float64Array(16);
mat4.invert(imatrix, matrix);

const inCenter = [
origin[0] + 0.5 * (inWholeExt[0] + inWholeExt[1]) * inSpacing[0],
origin[1] + 0.5 * (inWholeExt[2] + inWholeExt[3]) * inSpacing[1],
origin[2] + 0.5 * (inWholeExt[4] + inWholeExt[5]) * inSpacing[2],
];

let maxBounds = null;
if (model.autoCropOutput) {
maxBounds = publicAPI.getAutoCroppedOutputBounds(input);
}

for (let i = 0; i < 3; i++) {
let s = 0; // default output spacing
let d = 0; // default linear dimension
let e = 0; // default extent start
let c = 0; // transformed center-of-volume

if (model.transformInputSampling) {
let r = 0.0;
for (let j = 0; j < 3; j++) {
c += imatrix[4 * j + i] * (inCenter[j] - matrix[4 * 3 + j]);
const tmp = matrix[4 * i + j] * matrix[4 * i + j];
s += tmp * Math.abs(inSpacing[j]);
d +=
tmp *
(inWholeExt[2 * j + 1] - inWholeExt[2 * j]) *
Math.abs(inSpacing[j]);
e += tmp * inWholeExt[2 * j];
r += tmp;
}
s /= r;
d /= r * Math.sqrt(r);
e /= r;
} else {
c = inCenter[i];
s = inSpacing[i];
d = (inWholeExt[2 * i + 1] - inWholeExt[2 * i]) * s;
e = inWholeExt[2 * i];
}

if (model.outputSpacing == null) {
outSpacing[i] = s;
} else {
outSpacing[i] = model.outputSpacing[i];
}

if (i >= model.outputDimensionality) {
outWholeExt[2 * i] = 0;
outWholeExt[2 * i + 1] = 0;
} else if (model.outputExtent == null) {
if (model.autoCropOutput) {
d = maxBounds[2 * i + 1] - maxBounds[2 * i];
}
outWholeExt[2 * i] = Math.round(e);
outWholeExt[2 * i + 1] = Math.round(
outWholeExt[2 * i] + Math.abs(d / outSpacing[i])
);
} else {
outWholeExt[2 * i] = model.outputExtent[2 * i];
outWholeExt[2 * i + 1] = model.outputExtent[2 * i + 1];
}

if (i >= model.outputDimensionality) {
outOrigin[i] = 0;
} else if (model.outputOrigin == null) {
if (model.autoCropOutput) {
// set origin so edge of extent is edge of bounds
outOrigin[i] = maxBounds[2 * i] - outWholeExt[2 * i] * outSpacing[i];
} else {
// center new bounds over center of input bounds
outOrigin[i] =
c -
0.5 * (outWholeExt[2 * i] + outWholeExt[2 * i + 1]) * outSpacing[i];
}
} else {
outOrigin[i] = model.outputOrigin[i];
}
outDims[i] = outWholeExt[2 * i + 1] - outWholeExt[2 * i] + 1;
}

let dataType = inScalars.getDataType();
if (model.outputScalarType) {
dataType = model.outputScalarType;
}

const numComponents = input
.getPointData()
.getScalars()
.getNumberOfComponents(); // or s.numberOfComponents;

const outScalarsData = macro.newTypedArray(
dataType,
outDims[0] * outDims[1] * outDims[2] * numComponents
);
const outScalars = vtkDataArray.newInstance({
name: 'Scalars',
values: outScalarsData,
numberOfComponents: numComponents,
});

// Update output
const output = vtkImageData.newInstance();
output.setDimensions(outDims);
output.setOrigin(outOrigin);
output.setSpacing(outSpacing);
if (model.outputDirection) {
output.setDirection(model.outputDirection);
}
output.getPointData().setScalars(outScalars);

publicAPI.getIndexMatrix(input, output);

let interpolationMode = model.interpolationMode;
model.usePermuteExecute = false;
if (model.optimization) {
if (
optimizedTransform == null &&
model.slabSliceSpacingFraction === 1.0 &&
model.interpolator.isSeparable() &&
publicAPI.isPermutationMatrix(indexMatrix)
) {
model.usePermuteExecute = true;
if (publicAPI.canUseNearestNeighbor(indexMatrix, outWholeExt)) {
interpolationMode = InterpolationMode.NEAREST;
}
}
}
model.interpolator.setInterpolationMode(interpolationMode);

let borderMode = ImageBorderMode.CLAMP;
borderMode = model.wrap ? ImageBorderMode.REPEAT : borderMode;
borderMode = model.mirror ? ImageBorderMode.MIRROR : borderMode;
model.interpolator.setBorderMode(borderMode);

const mintol = 7.62939453125e-6;
const maxtol = 2.0 * 2147483647;
let tol = 0.5 * model.border;
tol = borderMode === ImageBorderMode.CLAMP ? tol : maxtol;
tol = tol > mintol ? tol : mintol;
model.interpolator.setTolerance(tol);

model.interpolator.initialize(input);

publicAPI.vtkImageResliceExecute(input, output);

model.interpolator.releaseData();

outData[0] = output;

// console.timeEnd('reslice');
};

publicAPI.vtkImageResliceExecute = (input, output) => {
// const outDims = output.getDimensions();
const inScalars = input.getPointData().getScalars();
const outScalars = output.getPointData().getScalars();
let outPtr = outScalars.getData();
const outExt = output.getExtent();
const newmat = indexMatrix;
const outputStencil = null;

// multiple samples for thick slabs
const nsamples = Math.max(model.slabNumberOfSlices, 1);

// spacing between slab samples (as a fraction of slice spacing).
const slabSampleSpacing = model.slabSliceSpacingFraction;

// check for perspective transformation
const perspective = publicAPI.isPerspectiveMatrix(newmat);

// extra scalar info for nearest-neighbor optimization
let inPtr = inScalars.getData();
const inputScalarSize = 1; // inScalars.getElementComponentSize(); // inScalars.getDataTypeSize();
const inputScalarType = inScalars.getDataType();
const inComponents = inScalars.getNumberOfComponents(); // interpolator.GetNumberOfComponents();
const componentOffset = model.interpolator.getComponentOffset();
const borderMode = model.interpolator.getBorderMode();
const inDims = input.getDimensions();
const inExt = [0, inDims[0] - 1, 0, inDims[1] - 1, 0, inDims[2] - 1]; // interpolator->GetExtent();
const inInc = [0, 0, 0];
inInc[0] = inScalars.getNumberOfComponents();
inInc[1] = inInc[0] * inDims[0];
inInc[2] = inInc[1] * inDims[1];

const fullSize = inDims[0] * inDims[1] * inDims[2];
if (componentOffset > 0 && componentOffset + inComponents < inInc[0]) {
inPtr = inPtr.subarray(inputScalarSize * componentOffset);
}

let interpolationMode = InterpolationMode.NEAREST;
if (model.interpolator.isA('vtkImageInterpolator')) {
interpolationMode = model.interpolator.getInterpolationMode();
}

const convertScalars = null;
const rescaleScalars =
model.scalarShift !== 0.0 || model.scalarScale !== 1.0;

// is nearest neighbor optimization possible?
const optimizeNearest =
interpolationMode === InterpolationMode.NEAREST &&
borderMode === ImageBorderMode.CLAMP &&
!(
optimizedTransform != null ||
perspective ||
convertScalars != null ||
rescaleScalars
) &&
inputScalarType === outScalars.getDataType() &&
fullSize === inScalars.getNumberOfTuples() &&
model.border === true &&
nsamples <= 1;

// get pixel information
const scalarType = outScalars.getDataType();
const scalarSize = 1; // outScalars.getElementComponentSize() // outScalars.scalarSize;
const outComponents = outScalars.getNumberOfComponents();

// break matrix into a set of axes plus an origin
// (this allows us to calculate the transform Incrementally)
const xAxis = [0, 0, 0, 0];
const yAxis = [0, 0, 0, 0];
const zAxis = [0, 0, 0, 0];
const origin = [0, 0, 0, 0];
for (let i = 0; i < 4; ++i) {
xAxis[i] = newmat[4 * 0 + i];
yAxis[i] = newmat[4 * 1 + i];
zAxis[i] = newmat[4 * 2 + i];
origin[i] = newmat[4 * 3 + i];
}

// allocate an output row of type double
let floatPtr = null;
if (!optimizeNearest) {
floatPtr = new Float64Array(
inComponents * (outExt[1] - outExt[0] + nsamples)
);
}

const background = macro.newTypedArray(
inputScalarType,
model.backgroundColor
);

// set color for area outside of input volume extent
// void *background;
// vtkAllocBackgroundPixel(&background,
// self->GetBackgroundColor(), scalarType, scalarSize, outComponents);

// get various helper functions
const forceClamping =
interpolationMode > InterpolationMode.LINEAR ||
(nsamples > 1 && model.slabMode === SlabMode.SUM);
const convertpixels = publicAPI.getConversionFunc(
inputScalarType,
scalarType,
model.scalarShift,
model.scalarScale,
forceClamping
);
const setpixels = publicAPI.getSetPixelsFunc(
scalarType,
scalarSize,
outComponents,
outPtr
);
const composite = publicAPI.getCompositeFunc(
model.slabMode,
model.slabTrapezoidIntegration
);

// create some variables for when we march through the data
let idY = outExt[2] - 1;
let idZ = outExt[4] - 1;
const inPoint0 = [0.0, 0.0, 0.0, 0.0];
const inPoint1 = [0.0, 0.0, 0.0, 0.0];

// create an iterator to march through the data
const iter = vtkImagePointDataIterator.newInstance();
iter.initialize(output, outExt, model.stencil, null);
const outPtr0 = iter.getScalars(output, 0);
let outPtrIndex = 0;
const outTmp = macro.newTypedArray(
scalarType,
vtkBoundingBox.getDiagonalLength(outExt) * outComponents * 2
);

const interpolatedPtr = new Float64Array(inComponents * nsamples);
const interpolatedPoint = new Float64Array(inComponents);

for (; !iter.isAtEnd(); iter.nextSpan()) {
const span = iter.spanEndId() - iter.getId();
outPtrIndex = iter.getId() * scalarSize * outComponents;

if (!iter.isInStencil()) {
// clear any regions that are outside the stencil
const n = setpixels(outTmp, background, outComponents, span);
for (let i = 0; i < n; ++i) {
outPtr0[outPtrIndex++] = outTmp[i];
}
} else {
// get output index, and compute position in input image
const outIndex = iter.getIndex();

// if Z index increased, then advance position along Z axis
if (outIndex[2] > idZ) {
idZ = outIndex[2];
inPoint0[0] = origin[0] + idZ * zAxis[0];
inPoint0[1] = origin[1] + idZ * zAxis[1];
inPoint0[2] = origin[2] + idZ * zAxis[2];
inPoint0[3] = origin[3] + idZ * zAxis[3];
idY = outExt[2] - 1;
}

// if Y index increased, then advance position along Y axis
if (outIndex[1] > idY) {
idY = outIndex[1];
inPoint1[0] = inPoint0[0] + idY * yAxis[0];
inPoint1[1] = inPoint0[1] + idY * yAxis[1];
inPoint1[2] = inPoint0[2] + idY * yAxis[2];
inPoint1[3] = inPoint0[3] + idY * yAxis[3];
}

// march through one row of the output image
const idXmin = outIndex[0];
const idXmax = idXmin + span - 1;

if (!optimizeNearest) {
let wasInBounds = 1;
let isInBounds = 1;
let startIdX = idXmin;
let idX = idXmin;
const tmpPtr = floatPtr;
let pixelIndex = 0;

while (startIdX <= idXmax) {
for (; idX <= idXmax && isInBounds === wasInBounds; idX++) {
const inPoint2 = [
inPoint1[0] + idX * xAxis[0],
inPoint1[1] + idX * xAxis[1],
inPoint1[2] + idX * xAxis[2],
inPoint1[3] + idX * xAxis[3],
];

const inPoint3 = [0, 0, 0, 0];
let inPoint = inPoint2;
isInBounds = false;

let interpolatedPtrIndex = 0;
for (let sample = 0; sample < nsamples; ++sample) {
if (nsamples > 1) {
let s = sample - 0.5 * (nsamples - 1);
s *= slabSampleSpacing;
inPoint3[0] = inPoint2[0] + s * zAxis[0];
inPoint3[1] = inPoint2[1] + s * zAxis[1];
inPoint3[2] = inPoint2[2] + s * zAxis[2];
inPoint3[3] = inPoint2[3] + s * zAxis[3];
inPoint = inPoint3;
}

if (perspective) {
// only do perspective if necessary
const f = 1 / inPoint[3];
inPoint[0] *= f;
inPoint[1] *= f;
inPoint[2] *= f;
}

if (optimizedTransform !== null) {
// get the input origin and spacing for conversion purposes
const inOrigin = model.interpolator.getOrigin();
const inSpacing = model.interpolator.getSpacing();
const inInvSpacing = [
1.0 / inSpacing[0],
1.0 / inSpacing[1],
1.0 / inSpacing[2],
];

// apply the AbstractTransform if there is one
// TBD: handle inDirection
publicAPI.applyTransform(
optimizedTransform,
inPoint,
inOrigin,
inInvSpacing
);
}

if (model.interpolator.checkBoundsIJK(inPoint)) {
// do the interpolation
isInBounds = 1;
model.interpolator.interpolateIJK(inPoint, interpolatedPoint);
for (let i = 0; i < inComponents; ++i) {
interpolatedPtr[interpolatedPtrIndex++] =
interpolatedPoint[i];
}
}
}

if (interpolatedPtrIndex > inComponents) {
composite(
interpolatedPtr,
inComponents,
interpolatedPtrIndex / inComponents
);
}
for (let i = 0; i < inComponents; ++i) {
tmpPtr[pixelIndex++] = interpolatedPtr[i];
}

// set "was in" to "is in" if first pixel
wasInBounds = idX > idXmin ? wasInBounds : isInBounds;
}

// write a segment to the output
const endIdX = idX - 1 - (isInBounds !== wasInBounds);
const numpixels = endIdX - startIdX + 1;

let n = 0;
if (wasInBounds) {
if (outputStencil) {
outputStencil.insertNextExtent(startIdX, endIdX, idY, idZ);
}

if (rescaleScalars) {
publicAPI.rescaleScalars(
floatPtr,
inComponents,
idXmax - idXmin + 1,
model.scalarShift,
model.scalarScale
);
}

if (convertScalars) {
convertScalars(
floatPtr.subarray(startIdX * inComponents),
outTmp,
inputScalarType,
inComponents,
numpixels,
startIdX,
idY,
idZ
);
n = numpixels * outComponents * scalarSize;
} else {
n = convertpixels(
outTmp,
floatPtr.subarray(startIdX * inComponents),
outComponents,
numpixels
);
}
} else {
n = setpixels(outTmp, background, outComponents, numpixels);
}
for (let i = 0; i < n; ++i) {
outPtr0[outPtrIndex++] = outTmp[i];
}

startIdX += numpixels;
wasInBounds = isInBounds;
}
} else {
// optimize for nearest-neighbor interpolation
const inPtrTmp0 = inPtr;
const outPtrTmp = outPtr;

const inIncX = inInc[0] * inputScalarSize;
const inIncY = inInc[1] * inputScalarSize;
const inIncZ = inInc[2] * inputScalarSize;

const inExtX = inExt[1] - inExt[0] + 1;
const inExtY = inExt[3] - inExt[2] + 1;
const inExtZ = inExt[5] - inExt[4] + 1;

let startIdX = idXmin;
let endIdX = idXmin - 1;
let isInBounds = false;
const bytesPerPixel = inputScalarSize * inComponents;

for (let iidX = idXmin; iidX <= idXmax; iidX++) {
const inPoint = [
inPoint1[0] + iidX * xAxis[0],
inPoint1[1] + iidX * xAxis[1],
inPoint1[2] + iidX * xAxis[2],
];

const inIdX = vtkInterpolationMathRound(inPoint[0]) - inExt[0];
const inIdY = vtkInterpolationMathRound(inPoint[1]) - inExt[2];
const inIdZ = vtkInterpolationMathRound(inPoint[2]) - inExt[4];

if (
inIdX >= 0 &&
inIdX < inExtX &&
inIdY >= 0 &&
inIdY < inExtY &&
inIdZ >= 0 &&
inIdZ < inExtZ
) {
if (!isInBounds) {
// clear leading out-of-bounds pixels
startIdX = iidX;
isInBounds = true;
const n = setpixels(
outTmp,
background,
outComponents,
startIdX - idXmin
);
for (let i = 0; i < n; ++i) {
outPtr0[outPtrIndex++] = outTmp[i];
}
}
// set the final index that was within input bounds
endIdX = iidX;

// perform nearest-neighbor interpolation via pixel copy
let offset = inIdX * inIncX + inIdY * inIncY + inIdZ * inIncZ;

// when memcpy is used with a constant size, the compiler will
// optimize away the function call and use the minimum number
// of instructions necessary to perform the copy
switch (bytesPerPixel) {
case 1:
outPtr0[outPtrIndex++] = inPtrTmp0[offset];
break;
case 2:
case 3:
case 4:
case 8:
case 12:
case 16:
for (let i = 0; i < bytesPerPixel; ++i) {
outPtr0[outPtrIndex++] = inPtrTmp0[offset + i];
}
break;
default: {
// TODO: check bytes
let oc = 0;
do {
outPtr0[outPtrIndex++] = inPtrTmp0[offset++];
} while (++oc !== bytesPerPixel);
break;
}
}
} else if (isInBounds) {
// leaving input bounds
break;
}
}

// clear trailing out-of-bounds pixels
outPtr = outPtrTmp;
const n = setpixels(
outTmp,
background,
outComponents,
idXmax - endIdX
);
for (let i = 0; i < n; ++i) {
outPtr0[outPtrIndex++] = outTmp[i];
}

if (outputStencil && endIdX >= startIdX) {
outputStencil.insertNextExtent(startIdX, endIdX, idY, idZ);
}
}
}
}
};
/**
* The transform matrix supplied by the user converts output coordinates
* to input coordinates.
* To speed up the pixel lookup, the following function provides a
* matrix which converts output pixel indices to input pixel indices.
* This will also concatenate the ResliceAxes and the ResliceTransform
* if possible (if the ResliceTransform is a 4x4 matrix transform).
* If it does, this->OptimizedTransform will be set to nullptr, otherwise
* this->OptimizedTransform will be equal to this->ResliceTransform.
* @param {vtkImageData} input
* @param {vtkImageData} output
* @returns
*/
publicAPI.getIndexMatrix = (input, output) => {
const transform = mat4.identity(new Float64Array(16));
optimizedTransform = null;

if (model.resliceAxes) {
mat4.copy(transform, model.resliceAxes);
}
if (model.resliceTransform) {
if (model.resliceTransform.isA('vtkHomogeneousTransform')) {
mat4.multiply(transform, model.resliceTransform.getMatrix(), transform);
} else {
// TODO
vtkWarningMacro('Non homogeneous transform have not yet been ported');
}
}

// the outMatrix takes OutputData indices to OutputData coordinates,
const outMatrix = output.getIndexToWorld();
mat4.multiply(transform, transform, outMatrix);

// the inMatrix takes InputData coordinates to InputData indices
// the optimizedTransform requires data coords, not index coords, as its input
if (optimizedTransform == null) {
const inMatrix = input.getWorldToIndex();
mat4.multiply(transform, inMatrix, transform);
}

mat4.copy(indexMatrix, transform);

return indexMatrix;
};

publicAPI.getAutoCroppedOutputBounds = (input) => {
const inOrigin = input.getOrigin();
const inSpacing = input.getSpacing();
const inDirection = input.getDirection();
const dims = input.getDimensions();
const inWholeExt = [0, dims[0] - 1, 0, dims[1] - 1, 0, dims[2] - 1];

const matrix = new Float64Array(16);
if (model.resliceAxes) {
mat4.invert(matrix, model.resliceAxes);
} else {
mat4.identity(matrix);
}
let transform = null;
if (model.resliceTransform) {
transform = model.resliceTransform.getInverse();
}
let imageTransform = null;
if (!vtkMath.isIdentity3x3(inDirection)) {
imageTransform = vtkMatrixBuilder
.buildFromRadian()
.translate(inOrigin[0], inOrigin[1], inOrigin[2])
.multiply3x3(inDirection)
.translate(-inOrigin[0], -inOrigin[1], -inOrigin[2])
.invert()
.getMatrix();
}

const bounds = [
Number.MAX_VALUE,
-Number.MAX_VALUE,
Number.MAX_VALUE,
-Number.MAX_VALUE,
Number.MAX_VALUE,
-Number.MAX_VALUE,
];

const point = [0, 0, 0, 0];
for (let i = 0; i < 8; ++i) {
point[0] = inOrigin[0] + inWholeExt[i % 2] * inSpacing[0];
point[1] =
inOrigin[1] + inWholeExt[2 + (Math.floor(i / 2) % 2)] * inSpacing[1];
point[2] =
inOrigin[2] + inWholeExt[4 + (Math.floor(i / 4) % 2)] * inSpacing[2];
point[3] = 1.0;

if (imageTransform) {
vec4.transformMat4(point, point, imageTransform);
}

if (model.resliceTransform) {
transform.transformPoint(point, point);
}

vec4.transformMat4(point, point, matrix);

const f = 1.0 / point[3];
point[0] *= f;
point[1] *= f;
point[2] *= f;

for (let j = 0; j < 3; ++j) {
if (point[j] > bounds[2 * j + 1]) {
bounds[2 * j + 1] = point[j];
}
if (point[j] < bounds[2 * j]) {
bounds[2 * j] = point[j];
}
}
}
return bounds;
};

publicAPI.getDataTypeMinMax = (dataType) => {
switch (dataType) {
case 'Int8Array':
return { min: -128, max: 127 };
case 'Int16Array':
return { min: -32768, max: 32767 };
case 'Uint16Array':
return { min: 0, max: 65535 };
case 'Int32Array':
return { min: -2147483648, max: 2147483647 };
case 'Uint32Array':
return { min: 0, max: 4294967295 };
case 'Float32Array':
return { min: -1.2e38, max: 1.2e38 };
case 'Float64Array':
return { min: -1.2e38, max: 1.2e38 };
case 'Uint8Array':
case 'Uint8ClampedArray':
default:
return { min: 0, max: 255 };
}
};

publicAPI.clamp = (outPtr, inPtr, numscalars, n, min, max) => {
const count = n * numscalars;
for (let i = 0; i < count; ++i) {
outPtr[i] = vtkInterpolationMathClamp(inPtr[i], min, max);
}
return count;
};

publicAPI.convert = (outPtr, inPtr, numscalars, n) => {
const count = n * numscalars;
for (let i = 0; i < count; ++i) {
outPtr[i] = Math.round(inPtr[i]);
}
return count;
};

publicAPI.getConversionFunc = (
inputType,
dataType,
scalarShift,
scalarScale,
forceClamping
) => {
let useClamping = forceClamping;
if (
dataType !== VtkDataTypes.FLOAT &&
dataType !== VtkDataTypes.DOUBLE &&
!forceClamping
) {
const inMinMax = publicAPI.getDataTypeMinMax(inputType);
let checkMin = (inMinMax.min + scalarShift) * scalarScale;
let checkMax = (inMinMax.max + scalarShift) * scalarScale;
const outMinMax = publicAPI.getDataTypeMinMax(dataType);
const outputMin = outMinMax.min;
const outputMax = outMinMax.max;
if (checkMin > checkMax) {
const tmp = checkMax;
checkMax = checkMin;
checkMin = tmp;
}
useClamping = checkMin < outputMin || checkMax > outputMax;
}

if (
useClamping &&
dataType !== VtkDataTypes.FLOAT &&
dataType !== VtkDataTypes.DOUBLE
) {
const minMax = publicAPI.getDataTypeMinMax(dataType);
const clamp = (outPtr, inPtr, numscalars, n) =>
publicAPI.clamp(outPtr, inPtr, numscalars, n, minMax.min, minMax.max);
return clamp;
}
return publicAPI.convert;
};

publicAPI.set = (outPtr, inPtr, numscalars, n) => {
const count = numscalars * n;
for (let i = 0; i < n; ++i) {
outPtr[i] = inPtr[i];
}
return count;
};

publicAPI.set1 = (outPtr, inPtr, numscalars, n) => {
outPtr.fill(inPtr[0], 0, n);
return n;
};

publicAPI.getSetPixelsFunc = (dataType, dataSize, numscalars, dataPtr) =>
numscalars === 1 ? publicAPI.set1 : publicAPI.set;

publicAPI.getCompositeFunc = (slabMode, slabTrapezoidIntegration) => {
let composite = null;
// eslint-disable-next-line default-case
switch (slabMode) {
case SlabMode.MIN:
composite = getImageResliceCompositeMinValue;
break;
case SlabMode.MAX:
composite = getImageResliceCompositeMaxValue;
break;
case SlabMode.MEAN:
if (slabTrapezoidIntegration) {
composite = getImageResliceCompositeMeanTrap;
} else {
composite = getImageResliceCompositeMeanValue;
}
break;
case SlabMode.SUM:
if (slabTrapezoidIntegration) {
composite = getImageResliceCompositeSumTrap;
} else {
composite = getImageResliceCompositeSumValue;
}
break;
}

return composite;
};

publicAPI.applyTransform = (newTrans, inPoint, inOrigin, inInvSpacing) => {
inPoint[3] = 1;
vec4.transformMat4(inPoint, inPoint, newTrans);
inPoint[0] -= inOrigin[0];
inPoint[1] -= inOrigin[1];
inPoint[2] -= inOrigin[2];
inPoint[0] *= inInvSpacing[0];
inPoint[1] *= inInvSpacing[1];
inPoint[2] *= inInvSpacing[2];
};

publicAPI.rescaleScalars = (
floatData,
components,
n,
scalarShift,
scalarScale
) => {
const m = n * components;
for (let i = 0; i < m; ++i) {
floatData[i] = (floatData[i] + scalarShift) * scalarScale;
}
};

publicAPI.isPermutationMatrix = (matrix) => {
for (let i = 0; i < 3; i++) {
if (matrix[4 * i + 3] !== 0) {
return false;
}
}
if (matrix[4 * 3 + 3] !== 1) {
return false;
}
for (let j = 0; j < 3; j++) {
let k = 0;
for (let i = 0; i < 3; i++) {
if (matrix[4 * j + i] !== 0) {
k++;
}
}
if (k !== 1) {
return false;
}
}
return true;
};

// TODO: to move in vtkMath and add tolerance
publicAPI.isIdentityMatrix = (matrix) => {
for (let i = 0; i < 4; ++i) {
for (let j = 0; j < 4; ++j) {
if ((i === j ? 1.0 : 0.0) !== matrix[4 * j + i]) {
return false;
}
}
}
return true;
};

publicAPI.isPerspectiveMatrix = (matrix) =>
matrix[4 * 0 + 3] !== 0 ||
matrix[4 * 1 + 3] !== 0 ||
matrix[4 * 2 + 3] !== 0 ||
matrix[4 * 3 + 3] !== 1;

publicAPI.canUseNearestNeighbor = (matrix, outExt) => {
// loop through dimensions
for (let i = 0; i < 3; i++) {
let j;
for (j = 0; j < 3; j++) {
if (matrix[4 * j + i] !== 0) {
break;
}
}
if (j >= 3) {
return false;
}
let x = matrix[4 * j + i];
let y = matrix[4 * 3 + i];
if (outExt[2 * j] === outExt[2 * j + 1]) {
y += x * outExt[2 * i];
x = 0;
}
const fx = vtkInterpolationMathFloor(x, 0).error;
const fy = vtkInterpolationMathFloor(y, 0).error;
if (fx !== 0 || fy !== 0) {
return false;
}
}
return true;
};
}

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

const DEFAULT_VALUES = {
transformInputSampling: true,
autoCropOutput: false,
outputDimensionality: 3,
outputSpacing: null, // automatically computed if null
outputOrigin: null, // automatically computed if null
outputDirection: null, // identity if null
outputExtent: null, // automatically computed if null
outputScalarType: null,
wrap: false, // don't wrap
mirror: false, // don't mirror
border: true, // apply a border
interpolationMode: InterpolationMode.NEAREST, // only NEAREST supported so far
slabMode: SlabMode.MIN,
slabTrapezoidIntegration: false,
slabNumberOfSlices: 1,
slabSliceSpacingFraction: 1,
optimization: false, // not supported yet
scalarShift: 0, // for rescaling the data
scalarScale: 1,
backgroundColor: [0, 0, 0, 0],
resliceAxes: null,
// resliceTransform: null,
interpolator: vtkImageInterpolator.newInstance(),
usePermuteExecute: false, // no supported yet
};

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

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

// Make this a VTK object
macro.obj(publicAPI, model);

// Also make it an algorithm with one input and one output
macro.algo(publicAPI, model, 1, 1);

macro.setGet(publicAPI, model, [
'outputDimensionality',
'outputScalarType',
'scalarShift',
'scalarScale',
'transformInputSampling',
'autoCropOutput',
'wrap',
'mirror',
'border',
'interpolationMode',
'resliceTransform',
'slabMode',
'slabTrapezoidIntegration',
'slabNumberOfSlices',
'slabSliceSpacingFraction',
]);

macro.setGetArray(publicAPI, model, ['outputOrigin', 'outputSpacing'], 3);
macro.setGetArray(publicAPI, model, ['outputExtent'], 6);
macro.setGetArray(publicAPI, model, ['outputDirection'], 9);
macro.setGetArray(publicAPI, model, ['backgroundColor'], 4);

macro.get(publicAPI, model, ['resliceAxes']);

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

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

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

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

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