@thewtex/vtk.js-esm
Version:
Visualization Toolkit for the Web
479 lines (372 loc) • 14.8 kB
JavaScript
import _defineProperty from '@babel/runtime/helpers/defineProperty';
import Constants from './ImageMapper/Constants.js';
import macro from '../../macro.js';
import vtkAbstractMapper from './AbstractMapper.js';
import { A as clampValue, N as multiply3x3_vect3, M as createUninitializedBounds } from '../../Common/Core/Math/index.js';
import vtkPlane from '../../Common/DataModel/Plane.js';
import CoincidentTopologyHelper from './Mapper/CoincidentTopologyHelper.js';
import { n as normalize } from '../../vendor/gl-matrix/esm/vec3.js';
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
var staticOffsetAPI = CoincidentTopologyHelper.staticOffsetAPI,
otherStaticMethods = CoincidentTopologyHelper.otherStaticMethods;
var vtkWarningMacro = macro.vtkWarningMacro;
var SlicingMode = Constants.SlicingMode; // ----------------------------------------------------------------------------
// vtkImageMapper methods
// ----------------------------------------------------------------------------
function vtkImageMapper(publicAPI, model) {
// Set our className
model.classHierarchy.push('vtkImageMapper');
publicAPI.getSliceAtPosition = function (pos) {
var image = publicAPI.getInputData();
var pos3;
if (pos.length === 3) {
pos3 = pos;
} else if (Number.isFinite(pos)) {
var bds = image.getBounds();
switch (model.slicingMode) {
case SlicingMode.X:
pos3 = [pos, (bds[3] + bds[2]) / 2, (bds[5] + bds[4]) / 2];
break;
case SlicingMode.Y:
pos3 = [(bds[1] + bds[0]) / 2, pos, (bds[5] + bds[4]) / 2];
break;
case SlicingMode.Z:
pos3 = [(bds[1] + bds[0]) / 2, (bds[3] + bds[2]) / 2, pos];
break;
}
}
var ijk = [0, 0, 0];
image.worldToIndex(pos3, ijk);
var ex = image.getExtent();
var _publicAPI$getClosest = publicAPI.getClosestIJKAxis(),
ijkMode = _publicAPI$getClosest.ijkMode;
var slice = 0;
switch (ijkMode) {
case SlicingMode.I:
slice = clampValue(ijk[0], ex[0], ex[1]);
slice = Math.round(slice);
break;
case SlicingMode.J:
slice = clampValue(ijk[1], ex[2], ex[3]);
slice = Math.round(slice);
break;
case SlicingMode.K:
slice = clampValue(ijk[2], ex[4], ex[5]);
slice = Math.round(slice);
break;
default:
return 0;
}
return slice;
};
publicAPI.setSliceFromCamera = function (cam) {
var fp = cam.getFocalPoint();
switch (model.slicingMode) {
case SlicingMode.I:
case SlicingMode.J:
case SlicingMode.K:
{
var slice = publicAPI.getSliceAtPosition(fp);
publicAPI.setSlice(slice);
}
break;
case SlicingMode.X:
publicAPI.setSlice(fp[0]);
break;
case SlicingMode.Y:
publicAPI.setSlice(fp[1]);
break;
case SlicingMode.Z:
publicAPI.setSlice(fp[2]);
break;
}
};
publicAPI.setXSlice = function (id) {
publicAPI.setSlicingMode(SlicingMode.X);
publicAPI.setSlice(id);
};
publicAPI.setYSlice = function (id) {
publicAPI.setSlicingMode(SlicingMode.Y);
publicAPI.setSlice(id);
};
publicAPI.setZSlice = function (id) {
publicAPI.setSlicingMode(SlicingMode.Z);
publicAPI.setSlice(id);
};
publicAPI.setISlice = function (id) {
publicAPI.setSlicingMode(SlicingMode.I);
publicAPI.setSlice(id);
};
publicAPI.setJSlice = function (id) {
publicAPI.setSlicingMode(SlicingMode.J);
publicAPI.setSlice(id);
};
publicAPI.setKSlice = function (id) {
publicAPI.setSlicingMode(SlicingMode.K);
publicAPI.setSlice(id);
};
publicAPI.getSlicingModeNormal = function () {
var out = [0, 0, 0];
var a = publicAPI.getInputData().getDirection();
var mat3 = [[a[0], a[1], a[2]], [a[3], a[4], a[5]], [a[6], a[7], a[8]]];
switch (model.slicingMode) {
case SlicingMode.X:
out[0] = 1;
break;
case SlicingMode.Y:
out[1] = 1;
break;
case SlicingMode.Z:
out[2] = 1;
break;
case SlicingMode.I:
multiply3x3_vect3(mat3, [1, 0, 0], out);
break;
case SlicingMode.J:
multiply3x3_vect3(mat3, [0, 1, 0], out);
break;
case SlicingMode.K:
multiply3x3_vect3(mat3, [0, 0, 1], out);
break;
}
return out;
};
function computeClosestIJKAxis() {
var inVec3;
switch (model.slicingMode) {
case SlicingMode.X:
inVec3 = [1, 0, 0];
break;
case SlicingMode.Y:
inVec3 = [0, 1, 0];
break;
case SlicingMode.Z:
inVec3 = [0, 0, 1];
break;
default:
model.closestIJKAxis = {
ijkMode: model.slicingMode,
flip: false
};
return;
} // Project vec3 onto direction cosines
var out = [0, 0, 0]; // The direction matrix in vtkImageData is the indexToWorld rotation matrix
// with a column-major data layout since it is stored as a WebGL matrix.
// We need the worldToIndex rotation matrix for the projection, and it needs
// to be in a row-major data layout to use vtkMath for operations.
// To go from the indexToWorld column-major matrix to the worldToIndex
// row-major matrix, we need to transpose it (column -> row) then inverse it.
// However, that 3x3 matrix is a rotation matrix which is orthonormal, meaning
// that its inverse is equal to its transpose. We therefore need to apply two
// transpositions resulting in a no-op.
var a = publicAPI.getInputData().getDirection();
var mat3 = [[a[0], a[1], a[2]], [a[3], a[4], a[5]], [a[6], a[7], a[8]]];
multiply3x3_vect3(mat3, inVec3, out);
var maxAbs = 0.0;
var ijkMode = -1;
var flip = false;
for (var axis = 0; axis < out.length; ++axis) {
var absValue = Math.abs(out[axis]);
if (absValue > maxAbs) {
maxAbs = absValue;
flip = out[axis] < 0.0;
ijkMode = axis;
}
}
if (maxAbs !== 1.0) {
var xyzLabel = 'IJKXYZ'[model.slicingMode];
var ijkLabel = 'IJKXYZ'[ijkMode];
vtkWarningMacro("Unaccurate slicing along ".concat(xyzLabel, " axis which ") + "is not aligned with any IJK axis of the image data. " + "Using ".concat(ijkLabel, " axis as a fallback (").concat(maxAbs, "% aligned). ") + "Necessitates slice reformat that is not yet implemented. " + "You can switch the slicing mode on your mapper to do IJK slicing instead.");
}
model.closestIJKAxis = {
ijkMode: ijkMode,
flip: flip
};
}
publicAPI.setSlicingMode = function (mode) {
if (model.slicingMode === mode) {
return;
}
model.slicingMode = mode;
if (publicAPI.getInputData()) {
computeClosestIJKAxis();
}
publicAPI.modified();
};
publicAPI.getClosestIJKAxis = function () {
if ((model.closestIJKAxis === undefined || model.closestIJKAxis.ijkMode === SlicingMode.NONE) && publicAPI.getInputData()) {
computeClosestIJKAxis();
}
return model.closestIJKAxis;
};
publicAPI.getBounds = function () {
var image = publicAPI.getInputData();
if (!image) {
return createUninitializedBounds();
}
if (!model.useCustomExtents) {
return image.getBounds();
}
var ex = model.customDisplayExtent.slice();
var _publicAPI$getClosest2 = publicAPI.getClosestIJKAxis(),
ijkMode = _publicAPI$getClosest2.ijkMode;
var nSlice = model.slice;
if (ijkMode !== model.slicingMode) {
// If not IJK slicing, get the IJK slice from the XYZ position/slice
nSlice = publicAPI.getSliceAtPosition(model.slice);
}
switch (ijkMode) {
case SlicingMode.I:
ex[0] = nSlice;
ex[1] = nSlice;
break;
case SlicingMode.J:
ex[2] = nSlice;
ex[3] = nSlice;
break;
case SlicingMode.K:
ex[4] = nSlice;
ex[5] = nSlice;
break;
}
return image.extentToBounds(ex);
};
publicAPI.getBoundsForSlice = function () {
var slice = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : model.slice;
var thickness = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
var image = publicAPI.getInputData();
if (!image) {
return createUninitializedBounds();
}
var extent = image.getExtent();
var _publicAPI$getClosest3 = publicAPI.getClosestIJKAxis(),
ijkMode = _publicAPI$getClosest3.ijkMode;
var nSlice = slice;
if (ijkMode !== model.slicingMode) {
// If not IJK slicing, get the IJK slice from the XYZ position/slice
nSlice = publicAPI.getSliceAtPosition(slice);
}
switch (ijkMode) {
case SlicingMode.I:
extent[0] = nSlice - thickness;
extent[1] = nSlice + thickness;
break;
case SlicingMode.J:
extent[2] = nSlice - thickness;
extent[3] = nSlice + thickness;
break;
case SlicingMode.K:
extent[4] = nSlice - thickness;
extent[5] = nSlice + thickness;
break;
}
return image.extentToBounds(extent);
};
publicAPI.getIsOpaque = function () {
return true;
};
function doPicking(p1, p2) {
var imageData = publicAPI.getInputData();
var extent = imageData.getExtent(); // Slice origin
var ijk = [extent[0], extent[2], extent[4]];
var _publicAPI$getClosest4 = publicAPI.getClosestIJKAxis(),
ijkMode = _publicAPI$getClosest4.ijkMode;
var nSlice = model.slice;
if (ijkMode !== model.slicingMode) {
// If not IJK slicing, get the IJK slice from the XYZ position/slice
nSlice = publicAPI.getSliceAtPosition(nSlice);
}
ijk[ijkMode] += nSlice;
var worldOrigin = [0, 0, 0];
imageData.indexToWorld(ijk, worldOrigin); // Normal computation
ijk[ijkMode] += 1;
var worldNormal = [0, 0, 0];
imageData.indexToWorld(ijk, worldNormal);
worldNormal[0] -= worldOrigin[0];
worldNormal[1] -= worldOrigin[1];
worldNormal[2] -= worldOrigin[2];
normalize(worldNormal, worldNormal);
var intersect = vtkPlane.intersectWithLine(p1, p2, worldOrigin, worldNormal);
if (intersect.intersection) {
var point = intersect.x;
var absoluteIJK = [0, 0, 0];
imageData.worldToIndex(point, absoluteIJK); // `t` is the parametric position along the line
// defined in Plane.intersectWithLine
return {
t: intersect.t,
absoluteIJK: absoluteIJK
};
}
return null;
}
publicAPI.intersectWithLineForPointPicking = function (p1, p2) {
var pickingData = doPicking(p1, p2);
if (pickingData) {
var imageData = publicAPI.getInputData();
var extent = imageData.getExtent(); // Get closer integer ijk
// NB: point picking means closest slice, means rounding
var ijk = [Math.round(pickingData.absoluteIJK[0]), Math.round(pickingData.absoluteIJK[1]), Math.round(pickingData.absoluteIJK[2])]; // Are we outside our actual extent
if (ijk[0] < extent[0] || ijk[0] > extent[1] || ijk[1] < extent[2] || ijk[1] > extent[3] || ijk[2] < extent[4] || ijk[2] > extent[5]) {
return null;
}
return {
t: pickingData.t,
ijk: ijk
};
}
return null;
};
publicAPI.intersectWithLineForCellPicking = function (p1, p2) {
var pickingData = doPicking(p1, p2);
if (pickingData) {
var imageData = publicAPI.getInputData();
var extent = imageData.getExtent();
var absIJK = pickingData.absoluteIJK; // Get closer integer ijk
// NB: cell picking means closest voxel, means flooring
var ijk = [Math.floor(absIJK[0]), Math.floor(absIJK[1]), Math.floor(absIJK[2])]; // Are we outside our actual extent
if (ijk[0] < extent[0] || ijk[0] > extent[1] - 1 || ijk[1] < extent[2] || ijk[1] > extent[3] - 1 || ijk[2] < extent[4] || ijk[2] > extent[5] - 1) {
return null;
} // Parametric coordinates within cell
var pCoords = [absIJK[0] - ijk[0], absIJK[1] - ijk[1], absIJK[2] - ijk[2]];
return {
t: pickingData.t,
ijk: ijk,
pCoords: pCoords
};
}
return null;
};
} // ----------------------------------------------------------------------------
// Object factory
// ----------------------------------------------------------------------------
var DEFAULT_VALUES = {
displayExtent: [0, 0, 0, 0, 0, 0],
customDisplayExtent: [0, 0, 0, 0],
useCustomExtents: false,
slice: 0,
slicingMode: SlicingMode.NONE,
closestIJKAxis: {
ijkMode: SlicingMode.NONE,
flip: false
},
renderToRectangle: false,
sliceAtFocalPoint: false
}; // ----------------------------------------------------------------------------
function extend(publicAPI, model) {
var initialValues = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
Object.assign(model, DEFAULT_VALUES, initialValues); // Build VTK API
vtkAbstractMapper.extend(publicAPI, model, initialValues);
macro.get(publicAPI, model, ['slicingMode']);
macro.setGet(publicAPI, model, ['slice', 'closestIJKAxis', 'useCustomExtents', 'renderToRectangle', 'sliceAtFocalPoint']);
macro.setGetArray(publicAPI, model, ['customDisplayExtent'], 4);
CoincidentTopologyHelper.implementCoincidentTopologyMethods(publicAPI, model); // Object methods
vtkImageMapper(publicAPI, model);
} // ----------------------------------------------------------------------------
var newInstance = macro.newInstance(extend, 'vtkImageMapper'); // ----------------------------------------------------------------------------
var vtkImageMapper$1 = _objectSpread(_objectSpread(_objectSpread({
newInstance: newInstance,
extend: extend
}, staticOffsetAPI), otherStaticMethods), Constants);
export default vtkImageMapper$1;
export { extend, newInstance };