@kitware/vtk.js
Version:
Visualization Toolkit for the Web
371 lines (345 loc) • 13.9 kB
JavaScript
import { m as macro } from '../../macros2.js';
import { c as roundVector, e as clampVector } from '../Core/Math/index.js';
import vtkBoundingBox from './BoundingBox.js';
import vtkDataSet from './DataSet.js';
import vtkStructuredData from './StructuredData.js';
import { StructuredType } from './StructuredData/Constants.js';
import { mat3, mat4, vec3 } from 'gl-matrix';
const {
vtkErrorMacro
} = macro;
// ----------------------------------------------------------------------------
// vtkImageData methods
// ----------------------------------------------------------------------------
function vtkImageData(publicAPI, model) {
// Set our className
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;
}
const changeDetected = model.extent.some((item, index) => item !== extentArray[index]);
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();
if (dims[0] === 0 || dims[1] === 0 || dims[2] === 0) {
vtkErrorMacro('Requesting a point from an empty image.');
return null;
}
const ijk = new Float64Array(3);
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 coords = [0, 0, 0];
publicAPI.indexToWorld(ijk, coords);
return coords;
};
// vtkCell *GetCell(vtkIdType cellId) VTK_OVERRIDE;
// void GetCell(vtkIdType cellId, vtkGenericCell *cell) VTK_OVERRIDE;
// void GetCellBounds(vtkIdType cellId, double bounds[6]) VTK_OVERRIDE;
// virtual vtkIdType FindPoint(double x, double y, double z)
// {
// return this->vtkDataSet::FindPoint(x, y, z);
// }
// vtkIdType FindPoint(double x[3]) VTK_OVERRIDE;
// vtkIdType FindCell(
// double x[3], vtkCell *cell, vtkIdType cellId, double tol2,
// int& subId, double pcoords[3], double *weights) VTK_OVERRIDE;
// vtkIdType FindCell(
// double x[3], vtkCell *cell, vtkGenericCell *gencell,
// vtkIdType cellId, double tol2, int& subId,
// double pcoords[3], double *weights) VTK_OVERRIDE;
// vtkCell *FindAndGetCell(double x[3], vtkCell *cell, vtkIdType cellId,
// double tol2, int& subId, double pcoords[3],
// double *weights) VTK_OVERRIDE;
// int GetCellType(vtkIdType cellId) VTK_OVERRIDE;
// void GetCellPoints(vtkIdType cellId, vtkIdList *ptIds) VTK_OVERRIDE
// {vtkStructuredData::GetCellPoints(cellId,ptIds,this->DataDescription,
// this->GetDimensions());}
// void GetPointCells(vtkIdType ptId, vtkIdList *cellIds) VTK_OVERRIDE
// {vtkStructuredData::GetPointCells(ptId,cellIds,this->GetDimensions());}
// void ComputeBounds() VTK_OVERRIDE;
// int GetMaxCellSize() VTK_OVERRIDE {return 8;}; //voxel is the largest
publicAPI.getBounds = () => publicAPI.extentToBounds(publicAPI.getSpatialExtent());
publicAPI.extentToBounds = ex => vtkBoundingBox.transformBounds(ex, model.indexToWorld);
publicAPI.getSpatialExtent = () => vtkBoundingBox.inflate([...model.extent], 0.5);
// Internal, shouldn't need to call this manually.
publicAPI.computeTransforms = () => {
mat4.fromTranslation(model.indexToWorld, model.origin);
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];
mat4.scale(model.indexToWorld, model.indexToWorld, model.spacing);
mat4.invert(model.worldToIndex, model.indexToWorld);
};
publicAPI.indexToWorld = (ain, aout = []) => {
vec3.transformMat4(aout, ain, model.indexToWorld);
return aout;
};
publicAPI.indexToWorldVec3 = publicAPI.indexToWorld;
publicAPI.worldToIndex = (ain, aout = []) => {
vec3.transformMat4(aout, ain, model.worldToIndex);
return aout;
};
publicAPI.worldToIndexVec3 = publicAPI.worldToIndex;
publicAPI.indexToWorldBounds = (bin, bout = []) => vtkBoundingBox.transformBounds(bin, model.indexToWorld, bout);
publicAPI.worldToIndexBounds = (bin, bout = []) => vtkBoundingBox.transformBounds(bin, model.worldToIndex, bout);
// Make sure the transform is correct
model._onOriginChanged = publicAPI.computeTransforms;
model._onDirectionChanged = publicAPI.computeTransforms;
model._onSpacingChanged = publicAPI.computeTransforms;
publicAPI.computeTransforms();
publicAPI.getCenter = () => vtkBoundingBox.getCenter(publicAPI.getBounds());
publicAPI.computeHistogram = (worldBounds, voxelFunction = 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(bounds, point1, point2);
roundVector(point1, point1);
roundVector(point2, point2);
const dimensions = publicAPI.getDimensions();
clampVector(point1, [0, 0, 0], [dimensions[0] - 1, dimensions[1] - 1, dimensions[2] - 1], point1);
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 (!voxelFunction || voxelFunction([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 = inum ? Math.abs(sumOfSquares / inum - average * average) : 0;
const sigma = Math.sqrt(variance);
return {
minimum,
maximum,
average,
variance,
sigma,
count: inum
};
};
// TODO: use the unimplemented `vtkDataSetAttributes` for scalar length, that is currently also a TODO (GetNumberOfComponents).
// Scalar data could be tuples for color information?
publicAPI.computeIncrements = (extent, numberOfComponents = 1) => {
const increments = [];
let incr = numberOfComponents;
// Calculate array increment offsets
// similar to c++ vtkImageData::ComputeIncrements
for (let idx = 0; idx < 3; ++idx) {
increments[idx] = incr;
incr *= extent[idx * 2 + 1] - extent[idx * 2] + 1;
}
return increments;
};
/**
* @param {Number[]} index the localized `[i,j,k]` pixel array position. Float values will be rounded.
* @return {Number} the corresponding flattened index in the scalar array
*/
publicAPI.computeOffsetIndex = ([i, j, k]) => {
const extent = publicAPI.getExtent();
const numberOfComponents = publicAPI.getPointData().getScalars().getNumberOfComponents();
const increments = publicAPI.computeIncrements(extent, numberOfComponents);
// Use the array increments to find the pixel index
// similar to c++ vtkImageData::GetArrayPointer
// Math.floor to catch "practically 0" e^-15 scenarios.
return Math.floor((Math.round(i) - extent[0]) * increments[0] + (Math.round(j) - extent[2]) * increments[1] + (Math.round(k) - extent[4]) * increments[2]);
};
/**
* @param {Number[]} xyz the [x,y,z] Array in world coordinates
* @return {Number|NaN} the corresponding pixel's index in the scalar array
*/
publicAPI.getOffsetIndexFromWorld = xyz => {
const extent = publicAPI.getExtent();
const index = publicAPI.worldToIndex(xyz);
// Confirm indexed i,j,k coords are within the bounds of the volume
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;
}
}
// Assumed the index here is within 0 <-> scalarData.length, but doesn't hurt to check upstream
return publicAPI.computeOffsetIndex(index);
};
/**
* @param {Number[]} xyz the [x,y,z] Array in world coordinates
* @param {Number?} comp the scalar component index for multi-component scalars
* @return {Number|NaN} the corresponding pixel's scalar value
*/
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)) {
// VTK Error Macro will have been tripped already, no need to do it again,
return offsetIndex;
}
return publicAPI.getPointData().getScalars().getComponent(offsetIndex, comp);
};
const superInitialize = publicAPI.initialize;
publicAPI.initialize = () => {
publicAPI.set({
direction: mat3.identity(model.direction),
spacing: [1.0, 1.0, 1.0],
origin: [0.0, 0.0, 0.0],
extent: [0, -1, 0, -1, 0, -1],
dataDescription: StructuredType.EMPTY
});
return superInitialize();
};
}
// ----------------------------------------------------------------------------
// Object factory
// ----------------------------------------------------------------------------
const DEFAULT_VALUES = {
direction: null,
// a mat3
indexToWorld: null,
// a mat4
worldToIndex: null,
// a mat4
spacing: [1.0, 1.0, 1.0],
origin: [0.0, 0.0, 0.0],
extent: [0, -1, 0, -1, 0, -1],
dataDescription: StructuredType.EMPTY
};
// ----------------------------------------------------------------------------
function extend(publicAPI, model, initialValues = {}) {
Object.assign(model, DEFAULT_VALUES, initialValues);
// Inheritance
vtkDataSet.extend(publicAPI, model, initialValues);
if (!model.direction) {
model.direction = mat3.identity(new Float64Array(9));
} else if (Array.isArray(model.direction)) {
model.direction = new Float64Array(model.direction.slice(0, 9));
}
model.indexToWorld = new Float64Array(16);
model.worldToIndex = new Float64Array(16);
// Set/Get methods
macro.get(publicAPI, model, ['indexToWorld', 'worldToIndex']);
macro.setGetArray(publicAPI, model, ['origin', 'spacing'], 3);
macro.setGetArray(publicAPI, model, ['direction'], 9);
macro.getArray(publicAPI, model, ['extent'], 6);
// Object specific methods
vtkImageData(publicAPI, model);
}
// ----------------------------------------------------------------------------
const newInstance = macro.newInstance(extend, 'vtkImageData');
// ----------------------------------------------------------------------------
var vtkImageData$1 = {
newInstance,
extend
};
export { vtkImageData$1 as default, extend, newInstance };