@kitware/vtk.js
Version:
Visualization Toolkit for the Web
902 lines (855 loc) • 34.5 kB
JavaScript
import { mat4, vec4 } from 'gl-matrix';
import { m as macro, v as vtkWarningMacro } from '../../macros2.js';
import vtkDataArray from '../../Common/Core/DataArray.js';
import { b as vtkMath } from '../../Common/Core/Math/index.js';
import vtkMatrixBuilder from '../../Common/Core/MatrixBuilder.js';
import { VtkDataTypes } from '../../Common/Core/DataArray/Constants.js';
import vtkBoundingBox from '../../Common/DataModel/BoundingBox.js';
import vtkImageData from '../../Common/DataModel/ImageData.js';
import vtkImageInterpolator from './ImageInterpolator.js';
import vtkImagePointDataIterator from './ImagePointDataIterator.js';
import { InterpolationMode, ImageBorderMode } from './AbstractImageInterpolator/Constants.js';
import { vtkInterpolationMathClamp, vtkInterpolationMathFloor, vtkInterpolationMathRound } from './AbstractImageInterpolator/InterpolationInfo.js';
import Constants from './ImageReslice/Constants.js';
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 = outData[0]?.initialize() || 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;
// 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 (rescaleScalars) {
publicAPI.rescaleScalars(floatPtr, inComponents, idXmax - idXmin + 1, model.scalarShift, model.scalarScale);
}
{
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];
}
}
}
}
};
/**
* 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).error;
const fy = vtkInterpolationMathFloor(y).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
};
// ----------------------------------------------------------------------------
function extend(publicAPI, model) {
let initialValues = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
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);
}
// ----------------------------------------------------------------------------
const newInstance = macro.newInstance(extend, 'vtkImageReslice');
// ----------------------------------------------------------------------------
var vtkImageReslice$1 = {
newInstance,
extend,
...Constants
};
export { vtkImageReslice$1 as default, extend, newInstance };