molstar
Version:
A comprehensive macromolecular library.
281 lines (280 loc) • 14 kB
JavaScript
/**
* Copyright (c) 2020-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { Grid } from './grid';
import { OrderedSet, SortedArray } from '../../mol-data/int';
import { Box3D, Sphere3D } from '../../mol-math/geometry';
import { Vec3, Mat4 } from '../../mol-math/linear-algebra';
import { BoundaryHelper } from '../../mol-math/geometry/boundary-helper';
import { CubeFormat } from '../../mol-model-formats/volume/cube';
import { equalEps } from '../../mol-math/linear-algebra/3d/common';
import { CustomProperties } from '../custom-property';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { toPrecision } from '../../mol-util/number';
import { DscifFormat } from '../../mol-model-formats/volume/density-server';
export var Volume;
(function (Volume) {
function is(x) {
var _a, _b, _c, _d;
// TODO: improve
return (((_d = (_c = (_b = (_a = x === null || x === void 0 ? void 0 : x.grid) === null || _a === void 0 ? void 0 : _a.cells) === null || _b === void 0 ? void 0 : _b.space) === null || _c === void 0 ? void 0 : _c.dimensions) === null || _d === void 0 ? void 0 : _d.length) &&
(x === null || x === void 0 ? void 0 : x.sourceData) &&
(x === null || x === void 0 ? void 0 : x.customProperties) &&
(x === null || x === void 0 ? void 0 : x._propertyData));
}
Volume.is = is;
let IsoValue;
(function (IsoValue) {
function areSame(a, b, stats) {
return equalEps(toAbsolute(a, stats).absoluteValue, toAbsolute(b, stats).absoluteValue, stats.sigma / 100);
}
IsoValue.areSame = areSame;
function absolute(value) { return { kind: 'absolute', absoluteValue: value }; }
IsoValue.absolute = absolute;
function relative(value) { return { kind: 'relative', relativeValue: value }; }
IsoValue.relative = relative;
function calcAbsolute(stats, relativeValue) {
return relativeValue * stats.sigma + stats.mean;
}
IsoValue.calcAbsolute = calcAbsolute;
function calcRelative(stats, absoluteValue) {
return stats.sigma === 0 ? 0 : ((absoluteValue - stats.mean) / stats.sigma);
}
IsoValue.calcRelative = calcRelative;
function toAbsolute(value, stats) {
return value.kind === 'absolute' ? value : { kind: 'absolute', absoluteValue: IsoValue.calcAbsolute(stats, value.relativeValue) };
}
IsoValue.toAbsolute = toAbsolute;
function toRelative(value, stats) {
return value.kind === 'relative' ? value : { kind: 'relative', relativeValue: IsoValue.calcRelative(stats, value.absoluteValue) };
}
IsoValue.toRelative = toRelative;
function toString(value) {
return value.kind === 'relative'
? `${value.relativeValue.toFixed(2)} σ`
: `${value.absoluteValue.toPrecision(4)}`;
}
IsoValue.toString = toString;
})(IsoValue = Volume.IsoValue || (Volume.IsoValue = {}));
// Converts iso value to relative if using downsample VolumeServer data
function adjustedIsoValue(volume, value, kind) {
if (kind === 'relative')
return IsoValue.relative(value);
const absolute = IsoValue.absolute(value);
if (DscifFormat.is(volume.sourceData)) {
const stats = {
min: volume.sourceData.data.volume_data_3d_info.min_source.value(0),
max: volume.sourceData.data.volume_data_3d_info.max_source.value(0),
mean: volume.sourceData.data.volume_data_3d_info.mean_source.value(0),
sigma: volume.sourceData.data.volume_data_3d_info.sigma_source.value(0),
};
return Volume.IsoValue.toRelative(absolute, stats);
}
return absolute;
}
Volume.adjustedIsoValue = adjustedIsoValue;
const defaultStats = { min: -1, max: 1, mean: 0, sigma: 0.1 };
function createIsoValueParam(defaultValue, stats) {
const sts = stats || defaultStats;
const { min, max, mean, sigma } = sts;
// using ceil/floor could lead to "ouf of bounds" when converting
const relMin = (min - mean) / sigma;
const relMax = (max - mean) / sigma;
let def = defaultValue;
if (defaultValue.kind === 'absolute') {
if (defaultValue.absoluteValue < min)
def = Volume.IsoValue.absolute(min);
else if (defaultValue.absoluteValue > max)
def = Volume.IsoValue.absolute(max);
}
else {
if (defaultValue.relativeValue < relMin)
def = Volume.IsoValue.relative(relMin);
else if (defaultValue.relativeValue > relMax)
def = Volume.IsoValue.relative(relMax);
}
return PD.Conditioned(def, {
'absolute': PD.Converted((v) => Volume.IsoValue.toAbsolute(v, Grid.One.stats).absoluteValue, (v) => Volume.IsoValue.absolute(v), PD.Numeric(mean, { min, max, step: toPrecision(sigma / 100, 2) }, { immediateUpdate: true })),
'relative': PD.Converted((v) => Volume.IsoValue.toRelative(v, Grid.One.stats).relativeValue, (v) => Volume.IsoValue.relative(v), PD.Numeric(Math.min(1, relMax), { min: relMin, max: relMax, step: toPrecision(Math.round(((max - min) / sigma)) / 100, 2) }, { immediateUpdate: true }))
}, (v) => v.kind === 'absolute' ? 'absolute' : 'relative', (v, c) => c === 'absolute' ? Volume.IsoValue.toAbsolute(v, sts) : Volume.IsoValue.toRelative(v, sts), { isEssential: true });
}
Volume.createIsoValueParam = createIsoValueParam;
Volume.IsoValueParam = createIsoValueParam(Volume.IsoValue.relative(2));
Volume.One = {
label: '',
grid: Grid.One,
sourceData: { kind: '', name: '', data: {} },
customProperties: new CustomProperties(),
_propertyData: Object.create(null),
};
function areEquivalent(volA, volB) {
return Grid.areEquivalent(volA.grid, volB.grid);
}
Volume.areEquivalent = areEquivalent;
function isEmpty(vol) {
return Grid.isEmpty(vol.grid);
}
Volume.isEmpty = isEmpty;
function isOrbitals(volume) {
if (!CubeFormat.is(volume.sourceData))
return false;
return volume.sourceData.data.header.orbitals;
}
Volume.isOrbitals = isOrbitals;
function Loci(volume) { return { kind: 'volume-loci', volume }; }
Volume.Loci = Loci;
function isLoci(x) { return !!x && x.kind === 'volume-loci'; }
Volume.isLoci = isLoci;
function areLociEqual(a, b) { return a.volume === b.volume; }
Volume.areLociEqual = areLociEqual;
function isLociEmpty(loci) { return Grid.isEmpty(loci.volume.grid); }
Volume.isLociEmpty = isLociEmpty;
function getBoundingSphere(volume, boundingSphere) {
return Grid.getBoundingSphere(volume.grid, boundingSphere);
}
Volume.getBoundingSphere = getBoundingSphere;
let Isosurface;
(function (Isosurface) {
function Loci(volume, isoValue) { return { kind: 'isosurface-loci', volume, isoValue }; }
Isosurface.Loci = Loci;
function isLoci(x) { return !!x && x.kind === 'isosurface-loci'; }
Isosurface.isLoci = isLoci;
function areLociEqual(a, b) { return a.volume === b.volume && Volume.IsoValue.areSame(a.isoValue, b.isoValue, a.volume.grid.stats); }
Isosurface.areLociEqual = areLociEqual;
function isLociEmpty(loci) { return loci.volume.grid.cells.data.length === 0; }
Isosurface.isLociEmpty = isLociEmpty;
const bbox = Box3D();
function getBoundingSphere(volume, isoValue, boundingSphere) {
const value = Volume.IsoValue.toAbsolute(isoValue, volume.grid.stats).absoluteValue;
const neg = value < 0;
const c = [0, 0, 0];
const getCoords = volume.grid.cells.space.getCoords;
const d = volume.grid.cells.data;
const [xn, yn, zn] = volume.grid.cells.space.dimensions;
let minx = xn - 1, miny = yn - 1, minz = zn - 1;
let maxx = 0, maxy = 0, maxz = 0;
for (let i = 0, il = d.length; i < il; ++i) {
if ((neg && d[i] <= value) || (!neg && d[i] >= value)) {
getCoords(i, c);
if (c[0] < minx)
minx = c[0];
if (c[1] < miny)
miny = c[1];
if (c[2] < minz)
minz = c[2];
if (c[0] > maxx)
maxx = c[0];
if (c[1] > maxy)
maxy = c[1];
if (c[2] > maxz)
maxz = c[2];
}
}
Vec3.set(bbox.min, minx - 1, miny - 1, minz - 1);
Vec3.set(bbox.max, maxx + 1, maxy + 1, maxz + 1);
const transform = Grid.getGridToCartesianTransform(volume.grid);
Box3D.transform(bbox, bbox, transform);
return Sphere3D.fromBox3D(boundingSphere || Sphere3D(), bbox);
}
Isosurface.getBoundingSphere = getBoundingSphere;
})(Isosurface = Volume.Isosurface || (Volume.Isosurface = {}));
let Cell;
(function (Cell) {
function Loci(volume, indices) { return { kind: 'cell-loci', volume, indices }; }
Cell.Loci = Loci;
function isLoci(x) { return !!x && x.kind === 'cell-loci'; }
Cell.isLoci = isLoci;
function areLociEqual(a, b) { return a.volume === b.volume && OrderedSet.areEqual(a.indices, b.indices); }
Cell.areLociEqual = areLociEqual;
function isLociEmpty(loci) { return OrderedSet.size(loci.indices) === 0; }
Cell.isLociEmpty = isLociEmpty;
function Location(volume, cell) {
return { kind: 'cell-location', volume: volume, cell: cell };
}
Cell.Location = Location;
function isLocation(x) {
return !!x && x.kind === 'cell-location';
}
Cell.isLocation = isLocation;
const boundaryHelper = new BoundaryHelper('98');
const tmpBoundaryPos = Vec3();
function getBoundingSphere(volume, indices, boundingSphere) {
boundaryHelper.reset();
const transform = Grid.getGridToCartesianTransform(volume.grid);
const { getCoords } = volume.grid.cells.space;
for (let i = 0, _i = OrderedSet.size(indices); i < _i; i++) {
const o = OrderedSet.getAt(indices, i);
getCoords(o, tmpBoundaryPos);
Vec3.transformMat4(tmpBoundaryPos, tmpBoundaryPos, transform);
boundaryHelper.includePosition(tmpBoundaryPos);
}
boundaryHelper.finishedIncludeStep();
for (let i = 0, _i = OrderedSet.size(indices); i < _i; i++) {
const o = OrderedSet.getAt(indices, i);
getCoords(o, tmpBoundaryPos);
Vec3.transformMat4(tmpBoundaryPos, tmpBoundaryPos, transform);
boundaryHelper.radiusPosition(tmpBoundaryPos);
}
const bs = boundaryHelper.getSphere(boundingSphere);
return Sphere3D.expand(bs, bs, Mat4.getMaxScaleOnAxis(transform) * 10);
}
Cell.getBoundingSphere = getBoundingSphere;
})(Cell = Volume.Cell || (Volume.Cell = {}));
let Segment;
(function (Segment) {
function Loci(volume, segments) { return { kind: 'segment-loci', volume, segments: SortedArray.ofUnsortedArray(segments) }; }
Segment.Loci = Loci;
function isLoci(x) { return !!x && x.kind === 'segment-loci'; }
Segment.isLoci = isLoci;
function areLociEqual(a, b) { return a.volume === b.volume && SortedArray.areEqual(a.segments, b.segments); }
Segment.areLociEqual = areLociEqual;
function isLociEmpty(loci) { return loci.volume.grid.cells.data.length === 0 || loci.segments.length === 0; }
Segment.isLociEmpty = isLociEmpty;
const bbox = Box3D();
function getBoundingSphere(volume, segments, boundingSphere) {
const segmentation = Volume.Segmentation.get(volume);
if (segmentation) {
Box3D.setEmpty(bbox);
for (let i = 0, il = segments.length; i < il; ++i) {
const b = segmentation.bounds[segments[i]];
Box3D.add(bbox, b.min);
Box3D.add(bbox, b.max);
}
const transform = Grid.getGridToCartesianTransform(volume.grid);
Box3D.transform(bbox, bbox, transform);
return Sphere3D.fromBox3D(boundingSphere || Sphere3D(), bbox);
}
else {
return Volume.getBoundingSphere(volume, boundingSphere);
}
}
Segment.getBoundingSphere = getBoundingSphere;
function Location(volume, segment) {
return { kind: 'segment-location', volume: volume, segment: segment };
}
Segment.Location = Location;
function isLocation(x) {
return !!x && x.kind === 'segment-location';
}
Segment.isLocation = isLocation;
})(Segment = Volume.Segment || (Volume.Segment = {}));
Volume.PickingGranularity = {
set(volume, granularity) {
volume._propertyData['__picking_granularity__'] = granularity;
},
get(volume) {
var _a;
return (_a = volume._propertyData['__picking_granularity__']) !== null && _a !== void 0 ? _a : 'voxel';
}
};
Volume.Segmentation = {
set(volume, segmentation) {
volume._propertyData['__segmentation__'] = segmentation;
},
get(volume) {
return volume._propertyData['__segmentation__'];
}
};
})(Volume || (Volume = {}));