@thewtex/vtk.js-esm
Version:
Visualization Toolkit for the Web
294 lines (240 loc) • 11 kB
JavaScript
import _defineProperty from '@babel/runtime/helpers/defineProperty';
import Constants from './LandmarkTransform/Constants.js';
import macro from '../../macro.js';
import { o as jacobiN, p as perpendiculars } from '../Core/Math/index.js';
import { i as identity } from '../../vendor/gl-matrix/esm/mat4.js';
import { a as invert, m as multiply } from '../../vendor/gl-matrix/esm/mat3.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 Mode = Constants.Mode; // ----------------------------------------------------------------------------
// vtkLandmarkTransform methods
// ----------------------------------------------------------------------------
function vtkLandmarkTransform(publicAPI, model) {
// Set our className
model.classHierarchy.push('vtkLandmarkTransform'); // Convert a mat4 matrix to a Matrix 2 dimensions
function mat4To2DArray(mat) {
var output = [[0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0]];
var cpt = 0;
for (var c = 0; c < 4; c++) {
for (var r = 0; r < 4; r++) {
output[r][c] = mat[cpt++];
}
}
return output;
}
function update() {
identity(model.matrix);
var N_PTS = model.sourceLandmark.getNumberOfPoints();
if (model.targetLandmark.getNumberOfPoints() !== N_PTS || model.sourceLandmark === null || model.targetLandmark === null || N_PTS === 0) {
console.error('Error : Bad inputs of vtkLandmarkTransform');
return model.matrix;
} // -- find the centroid of each set --
var sourceCentroid = [0, 0, 0];
var targetCentroid = [0, 0, 0];
var p = [0, 0, 0];
for (var i = 0; i < N_PTS; i++) {
model.sourceLandmark.getPoint(i, p);
sourceCentroid[0] += p[0];
sourceCentroid[1] += p[1];
sourceCentroid[2] += p[2];
model.targetLandmark.getPoint(i, p);
targetCentroid[0] += p[0];
targetCentroid[1] += p[1];
targetCentroid[2] += p[2];
}
sourceCentroid[0] /= N_PTS;
sourceCentroid[1] /= N_PTS;
sourceCentroid[2] /= N_PTS;
targetCentroid[0] /= N_PTS;
targetCentroid[1] /= N_PTS;
targetCentroid[2] /= N_PTS; // -- if only one point, stop right here
if (N_PTS === 1) {
identity(model.matrix);
model.matrix.elem[12] = targetCentroid[0] - sourceCentroid[0];
model.matrix.elem[13] = targetCentroid[1] - sourceCentroid[1];
model.matrix.elem[14] = targetCentroid[2] - sourceCentroid[2];
return model.matrix;
} // -- build the 3x3 matrix M --
var M = new Float64Array(9);
var AAT = new Float64Array(9);
var a = [0, 0, 0];
var b = [0, 0, 0];
var sa = 0.0;
var sb = 0.0;
for (var pt = 0; pt < N_PTS; pt++) {
// get the origin-centred point (a) in the source set
model.sourceLandmark.getPoint(pt, a);
a[0] -= sourceCentroid[0];
a[1] -= sourceCentroid[1];
a[2] -= sourceCentroid[2]; // get the origin-centred point (b) in the target set
model.targetLandmark.getPoint(pt, b);
b[0] -= targetCentroid[0];
b[1] -= targetCentroid[1];
b[2] -= targetCentroid[2]; // accumulate the products a*T(b) into the matrix M
for (var _i = 0; _i < 3; _i++) {
M[3 * 0 + _i] += a[_i] * b[0];
M[3 * 1 + _i] += a[_i] * b[1];
M[3 * 2 + _i] += a[_i] * b[2]; // for the affine transform, compute ((a.a^t)^-1 . a.b^t)^t.
// a.b^t is already in M. here we put a.a^t in AAT.
if (model.mode === Mode.AFFINE) {
AAT[3 * 0 + _i] += a[_i] * a[0];
AAT[3 * 1 + _i] += a[_i] * a[1];
AAT[3 * 2 + _i] += a[_i] * a[2];
}
} // accumulate scale factors (if desired)
sa += a[0] * a[0] + a[1] * a[1] + a[2] * a[2];
sb += b[0] * b[0] + b[1] * b[1] + b[2] * b[2];
}
if (model.mode === Mode.AFFINE) {
// AAT = (a.a^t)^-1
invert(AAT, AAT); // M = (a.a^t)^-1 . a.b^t
multiply(M, AAT, M); // this->Matrix = M^t
for (var _i2 = 0; _i2 < 3; ++_i2) {
for (var j = 0; j < 3; ++j) {
model.matrix[4 * j + _i2] = M[4 * _i2 + j];
}
}
} else {
var scale = Math.sqrt(sb / sa); // -- build the 4x4 matrix N --
var N = new Float64Array(16); // on-diagonal elements
N[0] = M[0] + M[4] + M[8];
N[5] = M[0] - M[4] - M[8];
N[10] = -M[0] + M[4] - M[8];
N[15] = -M[0] - M[4] + M[8]; // off-diagonal elements
/* eslint-disable no-multi-assign */
N[4] = N[1] = M[7] - M[5];
N[8] = N[2] = M[2] - M[6];
N[12] = N[3] = M[3] - M[1];
N[9] = N[6] = M[3] + M[1];
N[13] = N[7] = M[2] + M[6];
N[14] = N[11] = M[7] + M[5];
/* eslint-enable no-multi-assign */
// -- eigen-decompose N (is symmetric) --
var eigenVectors = [[0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0]];
var eigenValues = [0.0, 0.0, 0.0, 0.0];
var NMatrix = mat4To2DArray(N);
jacobiN(NMatrix, 4, eigenValues, eigenVectors); // The eigenvector with the largest eigenvalue is the quaternion we want
// (they are sorted in decreasing order for us by JacobiN)
var w;
var x;
var y;
var z; // first; if points are collinear, choose the quaternion that
// results in the smallest rotation
if (eigenValues[0] === eigenValues[1] || N_PTS === 2) {
var s0 = [0, 0, 0];
var t0 = [0, 0, 0];
var s1 = [0, 0, 0];
var t1 = [0, 0, 0];
model.sourceLandmark.getPoint(0, s0);
model.targetLandmark.getPoint(0, t0);
model.sourceLandmark.getPoint(1, s1);
model.targetLandmark.getPoint(1, t1);
var ds;
var dt;
var rs = 0;
var rt = 0;
for (var _i3 = 0; _i3 < 3; _i3++) {
ds[_i3] = s1[_i3] - s0[_i3]; // vector between points
rs = ds[_i3] * ds[_i3] + rs;
dt[_i3] = t1[_i3] - t0[_i3];
rt = dt[_i3] * dt[_i3] + rt;
} // normalize the two vectors
rs = Math.sqrt(rs);
ds[0] /= rs;
ds[1] /= rs;
ds[2] /= rs;
rt = Math.sqrt(rt);
dt[0] /= rt;
dt[1] /= rt;
dt[2] /= rt; // take dot & cross product
w = ds[0] * dt[0] + ds[1] * dt[1] + ds[2] * dt[2];
x = ds[1] * dt[2] - ds[2] * dt[1];
y = ds[2] * dt[0] - ds[0] * dt[2];
z = ds[0] * dt[1] - ds[1] * dt[0];
var r = Math.sqrt(x * x + y * y + z * z);
var theta = Math.atan2(r, w); // construct quaternion
w = Math.cos(theta / 2);
if (r !== 0) {
r = Math.sin(theta / 2) / r;
x *= r;
y *= r;
z *= r;
} else {
// rotation by 180 degrees : special case
// Rotate around a vector perpendicular to ds
perpendiculars(ds, dt, 0, 0);
r = Math.sin(theta / 2);
x = dt[0] * r;
y = dt[1] * r;
z = dt[2] * r;
}
} else {
// points are not collinear
w = eigenVectors[0][0];
x = eigenVectors[1][0];
y = eigenVectors[2][0];
z = eigenVectors[3][0];
} // convert quaternion to a rotation matrix
var ww = w * w;
var wx = w * x;
var wy = w * y;
var wz = w * z;
var xx = x * x;
var yy = y * y;
var zz = z * z;
var xy = x * y;
var xz = x * z;
var yz = y * z;
model.matrix[0] = ww + xx - yy - zz;
model.matrix[1] = 2.0 * (wz + xy);
model.matrix[2] = 2.0 * (-wy + xz);
model.matrix[4] = 2.0 * (-wz + xy);
model.matrix[5] = ww - xx + yy - zz;
model.matrix[6] = 2.0 * (wx + yz);
model.matrix[8] = 2.0 * (wy + xz);
model.matrix[9] = 2.0 * (-wx + yz);
model.matrix[10] = ww - xx - yy + zz; // add in the scale factor (if desired)
if (model.mode !== Mode.RIGID_BODY) {
for (var _i4 = 0; _i4 < 3; _i4++) {
model.matrix[4 * 0 + _i4] = model.matrix[4 * 0 + _i4] * scale;
model.matrix[4 * 1 + _i4] = model.matrix[4 * 1 + _i4] * scale;
model.matrix[4 * 2 + _i4] = model.matrix[4 * 2 + _i4] * scale;
}
}
} // the translation is given by the difference in the transformed source
// centroid and the target centroid
var sx = model.matrix[0] * sourceCentroid[0] + model.matrix[4] * sourceCentroid[1] + model.matrix[8] * sourceCentroid[2];
var sy = model.matrix[1] * sourceCentroid[0] + model.matrix[5] * sourceCentroid[1] + model.matrix[9] * sourceCentroid[2];
var sz = model.matrix[2] * sourceCentroid[0] + model.matrix[6] * sourceCentroid[1] + model.matrix[10] * sourceCentroid[2];
model.matrix[12] = targetCentroid[0] - sx;
model.matrix[13] = targetCentroid[1] - sy;
model.matrix[14] = targetCentroid[2] - sz; // fill the bottom row of the 4x4 matrix
model.matrix[3] = 0.0;
model.matrix[7] = 0.0;
model.matrix[11] = 0.0;
model.matrix[15] = 1.0;
return model.matrix;
} // Expose method
publicAPI.update = update;
} // ----------------------------------------------------------------------------
// Object factory
// ----------------------------------------------------------------------------
var DEFAULT_VALUES = {
mode: Mode.SIMILARITY
}; // ----------------------------------------------------------------------------
function extend(publicAPI, model) {
var initialValues = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
Object.assign(model, DEFAULT_VALUES, initialValues);
macro.obj(publicAPI, model); // Internal objects initialization
model.matrix = identity(new Float64Array(16));
macro.setGet(publicAPI, model, ['sourceLandmark', 'targetLandmark', 'mode']);
macro.get(publicAPI, model, ['matrix']);
vtkLandmarkTransform(publicAPI, model);
} // ----------------------------------------------------------------------------
var newInstance = macro.newInstance(extend, 'vtkLandmarkTransform'); // ----------------------------------------------------------------------------
var vtkLandmarkTransform$1 = _objectSpread({
newInstance: newInstance,
extend: extend
}, Constants);
export default vtkLandmarkTransform$1;
export { extend, newInstance };