import macro from 'vtk.js/Sources/macro'; import * as vtkMath from 'vtk.js/Sources/Common/Core/Math'; import vtkBoundingBox from 'vtk.js/Sources/Common/DataModel/BoundingBox'; import vtkDataSet from 'vtk.js/Sources/Common/DataModel/DataSet'; import vtkStructuredData from 'vtk.js/Sources/Common/DataModel/StructuredData'; import { StructuredType } from 'vtk.js/Sources/Common/DataModel/StructuredData/Constants'; import { vec3, mat3, mat4 } from 'gl-matrix';
const { vtkErrorMacro } = macro;
function vtkImageData(publicAPI, model) { model.classHierarchy.push('vtkImageData');
publicAPI.setExtent = (...inExtent) => { if (model.deleted) { vtkErrorMacro('instance deleted - cannot call any method'); return false; }
const extentArray = inExtent.length === 1 ? inExtent[0] : inExtent;
if (extentArray.length !== 6) { return false; }
let changeDetected = false; model.extent.forEach((item, index) => { if (item !== extentArray[index]) { if (changeDetected) { return; } changeDetected = true; } });
if (changeDetected) { model.extent = extentArray.slice(); model.dataDescription = vtkStructuredData.getDataDescriptionFromExtent( model.extent ); publicAPI.modified(); } return changeDetected; };
publicAPI.setDimensions = (...dims) => { let i; let j; let k;
if (model.deleted) { vtkErrorMacro('instance deleted - cannot call any method'); return; }
if (dims.length === 1) { const array = dims[0]; i = array[0]; j = array[1]; k = array[2]; } else if (dims.length === 3) { i = dims[0]; j = dims[1]; k = dims[2]; } else { vtkErrorMacro('Bad dimension specification'); return; }
publicAPI.setExtent(0, i - 1, 0, j - 1, 0, k - 1); };
publicAPI.getDimensions = () => [ model.extent[1] - model.extent[0] + 1, model.extent[3] - model.extent[2] + 1, model.extent[5] - model.extent[4] + 1, ];
publicAPI.getNumberOfCells = () => { const dims = publicAPI.getDimensions(); let nCells = 1;
for (let i = 0; i < 3; i++) { if (dims[i] === 0) { return 0; } if (dims[i] > 1) { nCells *= dims[i] - 1; } }
return nCells; };
publicAPI.getNumberOfPoints = () => { const dims = publicAPI.getDimensions(); return dims[0] * dims[1] * dims[2]; };
publicAPI.getPoint = (index) => { const dims = publicAPI.getDimensions(); const ijk = vec3.fromValues(0, 0, 0); const coords = [0, 0, 0];
if (dims[0] === 0 || dims[1] === 0 || dims[2] === 0) { vtkErrorMacro('Requesting a point from an empty image.'); return null; }
switch (model.dataDescription) { case StructuredType.EMPTY: return null;
case StructuredType.SINGLE_POINT: break;
case StructuredType.X_LINE: ijk[0] = index; break;
case StructuredType.Y_LINE: ijk[1] = index; break;
case StructuredType.Z_LINE: ijk[2] = index; break;
case StructuredType.XY_PLANE: ijk[0] = index % dims[0]; ijk[1] = index / dims[0]; break;
case StructuredType.YZ_PLANE: ijk[1] = index % dims[1]; ijk[2] = index / dims[1]; break;
case StructuredType.XZ_PLANE: ijk[0] = index % dims[0]; ijk[2] = index / dims[0]; break;
case StructuredType.XYZ_GRID: ijk[0] = index % dims[0]; ijk[1] = (index / dims[0]) % dims[1]; ijk[2] = index / (dims[0] * dims[1]); break;
default: vtkErrorMacro('Invalid dataDescription'); break; }
const vout = vec3.create(); publicAPI.indexToWorldVec3(ijk, vout); vec3.copy(coords, vout); return coords; };
publicAPI.getBounds = () => publicAPI.extentToBounds(model.extent);
publicAPI.extentToBounds = (ex) => { const corners = [ ex[0], ex[2], ex[4], ex[1], ex[2], ex[4], ex[0], ex[3], ex[4], ex[1], ex[3], ex[4], ex[0], ex[2], ex[5], ex[1], ex[2], ex[5], ex[0], ex[3], ex[5], ex[1], ex[3], ex[5]];
const idx = vec3.fromValues(corners[0], corners[1], corners[2]); const vout = vec3.create(); publicAPI.indexToWorldVec3(idx, vout); const bounds = [vout[0], vout[0], vout[1], vout[1], vout[2], vout[2]]; for (let i = 3; i < 24; i += 3) { vec3.set(idx, corners[i], corners[i + 1], corners[i + 2]); publicAPI.indexToWorldVec3(idx, vout); if (vout[0] < bounds[0]) { bounds[0] = vout[0]; } if (vout[1] < bounds[2]) { bounds[2] = vout[1]; } if (vout[2] < bounds[4]) { bounds[4] = vout[2]; } if (vout[0] > bounds[1]) { bounds[1] = vout[0]; } if (vout[1] > bounds[3]) { bounds[3] = vout[1]; } if (vout[2] > bounds[5]) { bounds[5] = vout[2]; } }
return bounds; };
publicAPI.computeTransforms = () => { const trans = vec3.fromValues( model.origin[0], model.origin[1], model.origin[2] ); mat4.fromTranslation(model.indexToWorld, trans);
model.indexToWorld[0] = model.direction[0]; model.indexToWorld[1] = model.direction[1]; model.indexToWorld[2] = model.direction[2];
model.indexToWorld[4] = model.direction[3]; model.indexToWorld[5] = model.direction[4]; model.indexToWorld[6] = model.direction[5];
model.indexToWorld[8] = model.direction[6]; model.indexToWorld[9] = model.direction[7]; model.indexToWorld[10] = model.direction[8];
const scale = vec3.fromValues( model.spacing[0], model.spacing[1], model.spacing[2] ); mat4.scale(model.indexToWorld, model.indexToWorld, scale);
mat4.invert(model.worldToIndex, model.indexToWorld); };
publicAPI.setDirection = (...args) => { if (model.deleted) { vtkErrorMacro('instance deleted - cannot call any method'); return false; }
let array = args; if ( array.length === 1 && (Array.isArray(array[0]) || array[0].constructor === Float32Array || array[0].constructor === Float64Array) ) { array = array[0]; }
if (array.length !== 9) { throw new RangeError('Invalid number of values for array setter'); } let changeDetected = false; model.direction.forEach((item, index) => { if (item !== array[index]) { if (changeDetected) { return; } changeDetected = true; } });
if (changeDetected) { for (let i = 0; i < 9; ++i) { model.direction[i] = array[i]; } publicAPI.modified(); } return true; };
publicAPI.indexToWorldVec3 = (vin, vout) => { vec3.transformMat4(vout, vin, model.indexToWorld); return vout; };
publicAPI.indexToWorld = (ain, aout = []) => { const vin = vec3.fromValues(ain[0], ain[1], ain[2]); const vout = vec3.create(); vec3.transformMat4(vout, vin, model.indexToWorld); vec3.copy(aout, vout); return aout; };
publicAPI.worldToIndexVec3 = (vin, vout) => { vec3.transformMat4(vout, vin, model.worldToIndex); return vout; };
publicAPI.worldToIndex = (ain, aout = []) => { const vin = vec3.fromValues(ain[0], ain[1], ain[2]); const vout = vec3.create(); vec3.transformMat4(vout, vin, model.worldToIndex); vec3.copy(aout, vout); return aout; };
publicAPI.indexToWorldBounds = (bin, bout = []) => { const in1 = [0, 0, 0]; const in2 = [0, 0, 0]; vtkBoundingBox.computeCornerPoints(in1, in2, bin);
const out1 = [0, 0, 0]; const out2 = [0, 0, 0]; vec3.transformMat4(out1, in1, model.indexToWorld); vec3.transformMat4(out2, in2, model.indexToWorld);
vtkMath.computeBoundsFromPoints(out1, out2, bout);
return bout; };
publicAPI.worldToIndexBounds = (bin, bout = []) => { const in1 = [0, 0, 0]; const in2 = [0, 0, 0]; vtkBoundingBox.computeCornerPoints(in1, in2, bin);
const out1 = [0, 0, 0]; const out2 = [0, 0, 0]; vec3.transformMat4(out1, in1, model.worldToIndex); vec3.transformMat4(out2, in2, model.worldToIndex);
vtkMath.computeBoundsFromPoints(out1, out2, bout);
return bout; };
publicAPI.onModified(publicAPI.computeTransforms); publicAPI.computeTransforms();
publicAPI.getCenter = () => { const bounds = publicAPI.getBounds(); const center = [];
for (let i = 0; i < 3; i++) { center[i] = (bounds[2 * i + 1] + bounds[2 * i]) / 2; }
return center; };
publicAPI.computeHistogram = (worldBounds, voxelFunc = null) => { const bounds = [0, 0, 0, 0, 0, 0]; publicAPI.worldToIndexBounds(worldBounds, bounds);
const point1 = [0, 0, 0]; const point2 = [0, 0, 0]; vtkBoundingBox.computeCornerPoints(point1, point2, bounds);
vtkMath.roundVector(point1, point1); vtkMath.roundVector(point2, point2);
const dimensions = publicAPI.getDimensions();
vtkMath.clampVector( point1, [0, 0, 0], [dimensions[0] - 1, dimensions[1] - 1, dimensions[2] - 1], point1 ); vtkMath.clampVector( point2, [0, 0, 0], [dimensions[0] - 1, dimensions[1] - 1, dimensions[2] - 1], point2 );
const yStride = dimensions[0]; const zStride = dimensions[0] * dimensions[1];
const pixels = publicAPI.getPointData().getScalars().getData();
let maximum = -Infinity; let minimum = Infinity; let sumOfSquares = 0; let isum = 0; let inum = 0;
for (let z = point1[2]; z <= point2[2]; z++) { for (let y = point1[1]; y <= point2[1]; y++) { let index = point1[0] + y * yStride + z * zStride; for (let x = point1[0]; x <= point2[0]; x++) { if (!voxelFunc || voxelFunc([x, y, z], bounds)) { const pixel = pixels[index];
if (pixel > maximum) maximum = pixel; if (pixel < minimum) minimum = pixel; sumOfSquares += pixel * pixel; isum += pixel; inum += 1; }
++index; } } }
const average = inum > 0 ? isum / inum : 0; const variance = sumOfSquares - average * average; const sigma = Math.sqrt(variance);
return { minimum, maximum, average, variance, sigma, }; };
publicAPI.computeIncrements = (extent, numberOfComponents = 1) => { const increments = []; let incr = numberOfComponents;
for (let idx = 0; idx < 3; ++idx) { increments[idx] = incr; incr *= extent[idx * 2 + 1] - extent[idx * 2] + 1; } return increments; };
publicAPI.computeOffsetIndex = ([i, j, k]) => { const extent = publicAPI.getExtent(); const numberOfComponents = publicAPI .getPointData() .getScalars() .getNumberOfComponents(); const increments = publicAPI.computeIncrements(extent, numberOfComponents); return Math.floor( (Math.round(i) - extent[0]) * increments[0] + (Math.round(j) - extent[2]) * increments[1] + (Math.round(k) - extent[4]) * increments[2] ); };
publicAPI.getOffsetIndexFromWorld = (xyz) => { const extent = publicAPI.getExtent(); const index = publicAPI.worldToIndex(xyz);
for (let idx = 0; idx < 3; ++idx) { if (index[idx] < extent[idx * 2] || index[idx] > extent[idx * 2 + 1]) { vtkErrorMacro( `GetScalarPointer: Pixel ${index} is not in memory. Current extent = ${extent}` ); return NaN; } }
return publicAPI.computeOffsetIndex(index); };
publicAPI.getScalarValueFromWorld = (xyz, comp = 0) => { const numberOfComponents = publicAPI .getPointData() .getScalars() .getNumberOfComponents(); if (comp < 0 || comp >= numberOfComponents) { vtkErrorMacro( `GetScalarPointer: Scalar Component ${comp} is not within bounds. Current Scalar numberOfComponents: ${numberOfComponents}` ); return NaN; } const offsetIndex = publicAPI.getOffsetIndexFromWorld(xyz); if (Number.isNaN(offsetIndex)) { return offsetIndex; }
return publicAPI .getPointData() .getScalars() .getComponent(offsetIndex, comp); }; }
const DEFAULT_VALUES = { direction: null, indexToWorld: null, worldToIndex: null, spacing: [1.0, 1.0, 1.0], origin: [0.0, 0.0, 0.0], extent: [0, -1, 0, -1, 0, -1], dataDescription: StructuredType.EMPTY, };
export function extend(publicAPI, model, initialValues = {}) { Object.assign(model, DEFAULT_VALUES, initialValues);
vtkDataSet.extend(publicAPI, model, initialValues);
if (!model.direction) { model.direction = mat3.create(); } else if (Array.isArray(model.direction)) { const dvals = model.direction.slice(0); model.direction = mat3.create(); for (let i = 0; i < 9; ++i) { model.direction[i] = dvals[i]; } }
model.indexToWorld = new Float64Array(16); model.worldToIndex = new Float64Array(16);
macro.get(publicAPI, model, ['direction', 'indexToWorld', 'worldToIndex']); macro.setGetArray(publicAPI, model, ['origin', 'spacing'], 3); macro.getArray(publicAPI, model, ['extent'], 6);
vtkImageData(publicAPI, model); }
export const newInstance = macro.newInstance(extend, 'vtkImageData');
export default { newInstance, extend };
|