@acransac/vtk.js
Version:
Visualization Toolkit for the Web
1,147 lines (1,005 loc) • 36.1 kB
JavaScript
import { vec4, mat4 } from 'gl-matrix';
import macro from 'vtk.js/Sources/macro';
import vtkDataArray from 'vtk.js/Sources/Common/Core/DataArray';
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 { SlabMode } from './Constants';
const { TYPED_ARRAYS, capitalize, vtkErrorMacro } = macro;
// ----------------------------------------------------------------------------
// vtkImageReslice methods
// ----------------------------------------------------------------------------
function vtkImageReslice(publicAPI, model) {
// Set our className
model.classHierarchy.push('vtkImageReslice');
let indexMatrix = null;
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.setResliceAxes = (resliceAxes) => {
if (!model.resliceAxes) {
model.resliceAxes = new Float64Array(16);
mat4.identity(model.resliceAxes);
}
if (!mat4.exactEquals(model.resliceAxes, resliceAxes)) {
mat4.copy(model.resliceAxes, resliceAxes);
publicAPI.modified();
return true;
}
return null;
};
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];
let matrix = new Float64Array(16);
mat4.identity(matrix);
if (model.resliceAxes) {
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 = new TYPED_ARRAYS[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);
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];
}
// 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],
];
// allocate an output row of type double
let floatPtr = null;
if (!optimizeNearest) {
floatPtr = new Float64Array(inComponents * (outExt[1] - outExt[0]));
}
const background = new TYPED_ARRAYS[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 = new TYPED_ARRAYS[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) {
// apply the AbstractTransform if there is one
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);
}
}
}
}
};
publicAPI.getIndexMatrix = (input, output) => {
// first verify that we have to update the matrix
if (indexMatrix === null) {
indexMatrix = new Float64Array(16);
mat4.identity(indexMatrix);
}
const inOrigin = input.getOrigin();
const inSpacing = input.getSpacing();
const outOrigin = output.getOrigin();
const outSpacing = output.getSpacing();
const transform = new Float64Array(16);
mat4.identity(transform);
const inMatrix = new Float64Array(16);
mat4.identity(inMatrix);
const outMatrix = new Float64Array(16);
mat4.identity(outMatrix);
if (optimizedTransform) {
optimizedTransform = null;
}
if (model.resliceAxes) {
mat4.copy(transform, model.resliceAxes);
}
if (model.resliceTransform) {
// TODO
}
// check to see if we have an identity matrix
let isIdentity = publicAPI.isIdentityMatrix(transform);
// the outMatrix takes OutputData indices to OutputData coordinates,
// the inMatrix takes InputData coordinates to InputData indices
for (let i = 0; i < 3; i++) {
if (
(optimizedTransform === null &&
(inSpacing[i] !== outSpacing[i] || inOrigin[i] !== outOrigin[i])) ||
(optimizedTransform !== null &&
(outSpacing[i] !== 1.0 || outOrigin[i] !== 0.0))
) {
isIdentity = false;
}
inMatrix[4 * i + i] = 1.0 / inSpacing[i];
inMatrix[4 * 3 + i] = -inOrigin[i] / inSpacing[i];
outMatrix[4 * i + i] = outSpacing[i];
outMatrix[4 * 3 + i] = outOrigin[i];
}
if (!isIdentity) {
// transform.PreMultiply();
// transform.Concatenate(outMatrix);
mat4.multiply(transform, transform, outMatrix);
// the optimizedTransform requires data coords, not
// index coords, as its input
if (optimizedTransform == null) {
// transform->PostMultiply();
// transform->Concatenate(inMatrix);
mat4.multiply(transform, inMatrix, transform);
}
}
mat4.copy(indexMatrix, transform);
return indexMatrix;
};
publicAPI.getAutoCroppedOutputBounds = (input) => {
const inOrigin = input.getOrigin();
const inSpacing = input.getSpacing();
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);
}
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 (model.resliceTransform) {
// TODO
}
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 'Uint8Array':
case 'Uint8ClampedArray':
default:
return { min: 0, max: 255 };
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 };
}
};
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 0;
}
}
return 1;
};
// 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 0;
}
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 0;
}
}
return 1;
};
}
function setNullArray(publicAPI, model, fieldNames) {
fieldNames.forEach((field) => {
const setterName = `set${capitalize(field)}`;
const superSet = publicAPI[setterName];
publicAPI[setterName] = (...args) => {
if ((args.length === 1 && args[0] == null) || model[field] == null) {
if (args[0] !== model[field]) {
model[field] = args[0];
publicAPI.modified();
return true;
}
return null;
}
return superSet(...args);
};
});
}
// ----------------------------------------------------------------------------
// Object factory
// ----------------------------------------------------------------------------
const DEFAULT_VALUES = {
transformInputSampling: true,
autoCropOutput: false,
outputDimensionality: 3,
outputSpacing: null, // automatically computed if null
outputOrigin: null, // automatically computed 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',
'backgroundColor',
'slabMode',
'slabTrapezoidIntegration',
'slabNumberOfSlices',
'slabSliceSpacingFraction',
]);
macro.setGetArray(publicAPI, model, ['outputOrigin', 'outputSpacing'], 3);
macro.setGetArray(publicAPI, model, ['outputExtent'], 6);
setNullArray(publicAPI, model, [
'outputOrigin',
'outputSpacing',
'outputExtent',
]);
macro.get(publicAPI, model, ['resliceAxes']);
// Object specific methods
macro.algo(publicAPI, model, 1, 1);
vtkImageReslice(publicAPI, model);
}
// ----------------------------------------------------------------------------
export const newInstance = macro.newInstance(extend, 'vtkImageReslice');
// ----------------------------------------------------------------------------
export default { newInstance, extend };