UNPKG

molstar

Version:

A comprehensive macromolecular library.

250 lines (249 loc) 10.5 kB
/** * Copyright (c) 2018-2026 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author David Sehnal <david.sehnal@gmail.com> * @author Alexander Rose <alexander.rose@weirdbyte.de> */ import { Box3D, Sphere3D } from '../../mol-math/geometry.js'; import { Tensor, Mat4, Vec3 } from '../../mol-math/linear-algebra.js'; import { calculateHistogram } from '../../mol-math/histogram.js'; import { lerp } from '../../mol-math/interpolate.js'; // avoiding namespace lookup improved performance in Chrome (Aug 2020) const v3transformMat4 = Vec3.transformMat4; const v3lerp = Vec3.lerp; var Grid; (function (Grid) { Grid.One = { transform: { kind: 'matrix', matrix: Mat4.identity() }, cells: Tensor.create(Tensor.Space([1, 1, 1], [0, 1, 2]), Tensor.Data1([0])), stats: { min: 0, max: 0, mean: 0, sigma: 0 }, }; const _scale = Mat4(), _translate = Mat4(); function getGridToCartesianTransform(grid) { if (grid.transform.kind === 'matrix') { return Mat4.copy(Mat4(), grid.transform.matrix); } if (grid.transform.kind === 'spacegroup') { const { cells: { space } } = grid; const scale = Mat4.fromScaling(_scale, Vec3.div(Vec3(), Box3D.size(Vec3(), grid.transform.fractionalBox), Vec3.ofArray(space.dimensions))); const translate = Mat4.fromTranslation(_translate, grid.transform.fractionalBox.min); return Mat4.mul3(Mat4(), grid.transform.cell.fromFractional, translate, scale); } return Mat4.identity(); } Grid.getGridToCartesianTransform = getGridToCartesianTransform; function areEquivalent(gridA, gridB) { return gridA === gridB; } Grid.areEquivalent = areEquivalent; function isEmpty(grid) { return grid.cells.data.length === 0; } Grid.isEmpty = isEmpty; function getBoundingSphere(grid, boundingSphere) { if (!boundingSphere) boundingSphere = Sphere3D(); const dimensions = grid.cells.space.dimensions; const transform = Grid.getGridToCartesianTransform(grid); return Sphere3D.fromDimensionsAndTransform(boundingSphere, dimensions, transform); } Grid.getBoundingSphere = getBoundingSphere; /** * Compute histogram with given bin count. * Cached on the Grid object. */ function getHistogram(grid, binCount) { let histograms = grid._historams; if (!histograms) { histograms = grid._historams = {}; } if (!histograms[binCount]) { histograms[binCount] = calculateHistogram(grid.cells.data, binCount, { min: grid.stats.min, max: grid.stats.max }); } return histograms[binCount]; } Grid.getHistogram = getHistogram; function makeGetTrilinearlyInterpolated(grid, transform) { const cartnToGrid = Grid.getGridToCartesianTransform(grid); Mat4.invert(cartnToGrid, cartnToGrid); const gridCoords = Vec3(); const { stats } = grid; const { dimensions, get } = grid.cells.space; const data = grid.cells.data; const [mi, mj, mk] = dimensions; const getValue = (i, j, k) => get(data, i, j, k); return function getTrilinearlyInterpolated(position) { v3transformMat4(gridCoords, position, cartnToGrid); const value = trilinearlyInterpolate(gridCoords, mi, mj, mk, getValue); if (Number.isNaN(value)) return value; if (transform === 'relative') { return (value - stats.mean) / stats.sigma; } else { return value; } }; } Grid.makeGetTrilinearlyInterpolated = makeGetTrilinearlyInterpolated; /** * Core trilinear interpolation function. * @param gridCoords - Position in grid coordinates (fractional indices) * @param mi, mj, mk - Grid dimensions * @param getValue - Function to get value at integer grid coordinates * @returns Interpolated value or NaN if out of bounds */ function trilinearlyInterpolate(gridCoords, mi, mj, mk, getValue) { const i = Math.trunc(gridCoords[0]); const j = Math.trunc(gridCoords[1]); const k = Math.trunc(gridCoords[2]); if (i < 0 || i >= mi || j < 0 || j >= mj || k < 0 || k >= mk) { return Number.NaN; } const u = gridCoords[0] - i; const v = gridCoords[1] - j; const w = gridCoords[2] - k; const ii = Math.min(i + 1, mi - 1); const jj = Math.min(j + 1, mj - 1); const kk = Math.min(k + 1, mk - 1); let a = getValue(i, j, k); let b = getValue(ii, j, k); let c = getValue(i, jj, k); let d = getValue(ii, jj, k); const x = lerp(lerp(a, b, u), lerp(c, d, u), v); a = getValue(i, j, kk); b = getValue(ii, j, kk); c = getValue(i, jj, kk); d = getValue(ii, jj, kk); const y = lerp(lerp(a, b, u), lerp(c, d, u), v); return lerp(x, y, w); } Grid.trilinearlyInterpolate = trilinearlyInterpolate; const _a = Vec3(), _b = Vec3(), _c = Vec3(), _d = Vec3(); const _ab = Vec3(), _cd = Vec3(), _x = Vec3(), _y = Vec3(); /** * Core trilinear interpolation function for Vec3 values. * More efficient for interleaved data like gradients since getValue is called once per grid point. * @param gridCoords - Position in grid coordinates (fractional indices) * @param mi, mj, mk - Grid dimensions * @param getValue - Function to get Vec3 value at integer grid coordinates * @param out - Output Vec3 * @returns true if interpolation succeeded, false if out of bounds */ function trilinearlyInterpolateVec3(gridCoords, mi, mj, mk, getValue, out) { const i = Math.trunc(gridCoords[0]); const j = Math.trunc(gridCoords[1]); const k = Math.trunc(gridCoords[2]); if (i < 0 || i >= mi || j < 0 || j >= mj || k < 0 || k >= mk) { return false; } const u = gridCoords[0] - i; const v = gridCoords[1] - j; const w = gridCoords[2] - k; const ii = Math.min(i + 1, mi - 1); const jj = Math.min(j + 1, mj - 1); const kk = Math.min(k + 1, mk - 1); // Interpolate in the k plane getValue(i, j, k, _a); getValue(ii, j, k, _b); getValue(i, jj, k, _c); getValue(ii, jj, k, _d); v3lerp(_ab, _a, _b, u); v3lerp(_cd, _c, _d, u); v3lerp(_x, _ab, _cd, v); // Interpolate in the k+1 plane getValue(i, j, kk, _a); getValue(ii, j, kk, _b); getValue(i, jj, kk, _c); getValue(ii, jj, kk, _d); v3lerp(_ab, _a, _b, u); v3lerp(_cd, _c, _d, u); v3lerp(_y, _ab, _cd, v); // Final interpolation between planes v3lerp(out, _x, _y, w); return true; } Grid.trilinearlyInterpolateVec3 = trilinearlyInterpolateVec3; /** * Pre-compute gradients at each grid cell using central differences. * Returns a single Float32Array with interleaved xyz components (x1, y1, z1, x2, y2, z2, ...). * Cached on the Grid object. */ function getGradients(grid) { if (grid._gradients) { return grid._gradients; } const gradients = computeGradients(grid); grid._gradients = gradients; return gradients; } Grid.getGradients = getGradients; function computeGradients(grid) { const { dimensions, get, dataOffset } = grid.cells.space; const data = grid.cells.data; const [mi, mj, mk] = dimensions; const n = mi * mj * mk; const values = new Float32Array(n * 3); let min = Infinity; let max = -Infinity; let sum = 0; let sumSq = 0; for (let k = 0; k < mk; ++k) { for (let j = 0; j < mj; ++j) { for (let i = 0; i < mi; ++i) { const idx = dataOffset(i, j, k) * 3; // Use central differences where possible, forward/backward at boundaries const im = Math.max(0, i - 1); const ip = Math.min(mi - 1, i + 1); const jm = Math.max(0, j - 1); const jp = Math.min(mj - 1, j + 1); const km = Math.max(0, k - 1); const kp = Math.min(mk - 1, k + 1); // Gradient components (using central differences with proper divisor) const gx = (get(data, ip, j, k) - get(data, im, j, k)) / (ip - im || 1); const gy = (get(data, i, jp, k) - get(data, i, jm, k)) / (jp - jm || 1); const gz = (get(data, i, j, kp) - get(data, i, j, km)) / (kp - km || 1); values[idx] = gx; values[idx + 1] = gy; values[idx + 2] = gz; const mag = gx * gx + gy * gy + gz * gz; if (mag < min) min = mag; if (mag > max) max = mag; sum += mag; sumSq += mag * mag; } } } if (min === Infinity) min = 0; if (max === -Infinity) max = 1; min = Math.sqrt(min); max = Math.sqrt(max); const mean = sum / n; const sigma = Math.sqrt(sumSq / n); return { values, magnitude: { min, max, mean, sigma } }; } /** * Create a function that returns trilinearly interpolated gradient at a grid position. * The gradient is pre-computed at grid cells and interpolated for smooth results. */ function makeGetInterpolatedGradient(grid) { const { values: g } = getGradients(grid); const { dimensions, dataOffset } = grid.cells.space; const [mi, mj, mk] = dimensions; const getGradientVec3 = (i, j, k, out) => { const idx = dataOffset(i, j, k) * 3; out[0] = g[idx]; out[1] = g[idx + 1]; out[2] = g[idx + 2]; }; return function getInterpolatedGradient(gridCoords, out) { return trilinearlyInterpolateVec3(gridCoords, mi, mj, mk, getGradientVec3, out); }; } Grid.makeGetInterpolatedGradient = makeGetInterpolatedGradient; })(Grid || (Grid = {})); export { Grid };