UNPKG

@thewtex/vtk.js-esm

Version:

Visualization Toolkit for the Web

479 lines (372 loc) 14.8 kB
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 };