UNPKG

@kitware/vtk.js

Version:

Visualization Toolkit for the Web

902 lines (855 loc) 34.5 kB
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 };