molstar
Version:
A comprehensive macromolecular library.
381 lines (380 loc) • 17.5 kB
JavaScript
"use strict";
/**
* Copyright (c) 2020-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.eachVolumeLoci = eachVolumeLoci;
exports.createVolumeCellLocationIterator = createVolumeCellLocationIterator;
exports.getVolumeTexture2dLayout = getVolumeTexture2dLayout;
exports.createVolumeTexture2d = createVolumeTexture2d;
exports.createVolumeTexture3d = createVolumeTexture3d;
exports.createSegmentTexture2d = createSegmentTexture2d;
exports.createWrappedVolume = createWrappedVolume;
const volume_1 = require("../../mol-model/volume.js");
const common_1 = require("../../mol-math/linear-algebra/3d/common.js");
const vec3_1 = require("../../mol-math/linear-algebra/3d/vec3.js");
const number_packing_1 = require("../../mol-util/number-packing.js");
const set_1 = require("../../mol-util/set.js");
const geometry_1 = require("../../mol-math/geometry.js");
const number_conversion_1 = require("../../mol-util/number-conversion.js");
const interpolate_1 = require("../../mol-math/interpolate.js");
const location_iterator_1 = require("../../mol-geo/util/location-iterator.js");
const interval_1 = require("../../mol-data/int/interval.js");
const ordered_set_1 = require("../../mol-data/int/ordered-set.js");
// avoiding namespace lookup improved performance in Chrome (Aug 2020)
const v3set = vec3_1.Vec3.set;
const v3normalize = vec3_1.Vec3.normalize;
const v3sub = vec3_1.Vec3.sub;
const v3addScalar = vec3_1.Vec3.addScalar;
const v3scale = vec3_1.Vec3.scale;
const v3toArray = vec3_1.Vec3.toArray;
function eachVolumeLoci(loci, volume, props, apply) {
let changed = false;
const cellCount = volume.grid.cells.data.length;
if (volume_1.Volume.isLoci(loci)) {
if (!volume_1.Volume.areEquivalent(loci.volume, volume))
return false;
if (interval_1.Interval.is(loci.instances)) {
const start = interval_1.Interval.start(loci.instances) * cellCount;
const end = interval_1.Interval.end(loci.instances) * cellCount;
if (apply(interval_1.Interval.ofBounds(start, end)))
changed = true;
}
else {
for (let i = 0, il = loci.instances.length; i < il; ++i) {
const offset = loci.instances[i] * cellCount;
if (apply(interval_1.Interval.ofBounds(offset, offset + cellCount)))
changed = true;
}
}
}
else if (volume_1.Volume.Isosurface.isLoci(loci)) {
if (!volume_1.Volume.areEquivalent(loci.volume, volume))
return false;
if (props === null || props === void 0 ? void 0 : props.isoValue) {
if (!volume_1.Volume.IsoValue.areSame(loci.isoValue, props.isoValue, volume.grid.stats))
return false;
if (interval_1.Interval.is(loci.instances)) {
const start = interval_1.Interval.start(loci.instances) * cellCount;
const end = interval_1.Interval.end(loci.instances) * cellCount;
if (apply(interval_1.Interval.ofBounds(start, end)))
changed = true;
}
else {
for (let i = 0, il = loci.instances.length; i < il; ++i) {
const offset = loci.instances[i] * cellCount;
if (apply(interval_1.Interval.ofBounds(offset, offset + cellCount)))
changed = true;
}
}
}
else {
const { stats, cells: { data } } = volume.grid;
const eps = stats.sigma;
const v = volume_1.Volume.IsoValue.toAbsolute(loci.isoValue, stats).absoluteValue;
for (let i = 0, il = data.length; i < il; ++i) {
if ((0, common_1.equalEps)(v, data[i], eps)) {
ordered_set_1.OrderedSet.forEach(loci.instances, j => {
const offset = j * cellCount;
if (apply(interval_1.Interval.ofSingleton(offset + i)))
changed = true;
});
}
}
}
}
else if (volume_1.Volume.Cell.isLoci(loci)) {
if (!volume_1.Volume.areEquivalent(loci.volume, volume))
return false;
for (const { indices, instances } of loci.elements) {
if (interval_1.Interval.is(indices)) {
ordered_set_1.OrderedSet.forEach(instances, j => {
const offset = j * cellCount;
if (apply(interval_1.Interval.offset(indices, offset)))
changed = true;
});
}
else {
ordered_set_1.OrderedSet.forEach(indices, v => {
ordered_set_1.OrderedSet.forEach(instances, j => {
const offset = j * cellCount;
if (apply(interval_1.Interval.ofSingleton(offset + v)))
changed = true;
});
});
}
}
}
else if (volume_1.Volume.Segment.isLoci(loci)) {
if (!volume_1.Volume.areEquivalent(loci.volume, volume))
return false;
if (props === null || props === void 0 ? void 0 : props.segments) {
for (const { segments, instances } of loci.elements) {
if (ordered_set_1.OrderedSet.areIntersecting(segments, props.segments)) {
ordered_set_1.OrderedSet.forEach(instances, j => {
const offset = j * cellCount;
if (apply(interval_1.Interval.ofBounds(offset, offset + cellCount)))
changed = true;
});
}
}
}
else {
const segmentation = volume_1.Volume.Segmentation.get(volume);
if (segmentation) {
const set = new Set();
for (const { segments, instances } of loci.elements) {
for (let i = 0, _i = ordered_set_1.OrderedSet.size(segments); i < _i; i++) {
const o = ordered_set_1.OrderedSet.getAt(segments, i);
set_1.SetUtils.add(set, segmentation.segments.get(o));
}
const s = Array.from(set.values());
const d = volume.grid.cells.data;
for (let i = 0, il = d.length; i < il; ++i) {
if (s.includes(d[i])) {
for (let j = 0, _j = ordered_set_1.OrderedSet.size(instances); j < _j; j++) {
const offset = j * cellCount;
if (apply(interval_1.Interval.ofSingleton(i + offset)))
changed = true;
}
}
}
}
}
}
}
return changed;
}
function createVolumeCellLocationIterator(volume) {
const [xn, yn, zn] = volume.grid.cells.space.dimensions;
const groupCount = xn * yn * zn;
const instanceCount = volume.instances.length;
const location = volume_1.Volume.Cell.Location(volume);
const getLocation = (groupIndex, instanceIndex) => {
location.cell = groupIndex;
location.instance = instanceIndex;
return location;
};
return (0, location_iterator_1.LocationIterator)(groupCount, instanceCount, 1, getLocation);
}
//
function getVolumeTexture2dLayout(dim, padding = 0) {
const area = dim[0] * dim[1] * dim[2];
const squareDim = Math.sqrt(area);
const powerOfTwoSize = Math.pow(2, Math.ceil(Math.log(squareDim) / Math.log(2)));
let width = dim[0] + padding;
let height = dim[1] + padding;
let rows = 1;
let columns = width;
if (powerOfTwoSize < width * dim[2]) {
columns = Math.floor(powerOfTwoSize / width);
rows = Math.ceil(dim[2] / columns);
width *= columns;
height *= rows;
}
else {
width *= dim[2];
}
return { width, height, columns, rows, powerOfTwoSize: height < powerOfTwoSize ? powerOfTwoSize : powerOfTwoSize * 2 };
}
function createVolumeTexture2d(volume, variant, padding = 0, type = 'byte') {
const { cells: { space, data }, stats: { max, min } } = volume.grid;
const dim = space.dimensions;
const { dataOffset: o } = space;
const { width, height } = getVolumeTexture2dLayout(dim, padding);
const itemSize = variant === 'data' ? 1 : 4;
const array = type === 'byte'
? new Uint8Array(width * height * itemSize)
: type === 'halfFloat'
? new Uint16Array(width * height * itemSize)
: new Float32Array(width * height * itemSize);
const textureImage = { array, width, height };
const diff = max - min;
const [xn, yn, zn] = dim;
const xnp = xn + padding;
const ynp = yn + padding;
const n0 = (0, vec3_1.Vec3)();
const n1 = (0, vec3_1.Vec3)();
const xn1 = xn - 1;
const yn1 = yn - 1;
const zn1 = zn - 1;
for (let z = 0; z < zn; ++z) {
for (let y = 0; y < yn; ++y) {
for (let x = 0; x < xn; ++x) {
const column = Math.floor(((z * xnp) % width) / xnp);
const row = Math.floor((z * xnp) / width);
const px = column * xnp + x;
const index = itemSize * ((row * ynp * width) + (y * width) + px);
const offset = o(x, y, z);
let value;
if (type === 'byte') {
value = Math.round(((data[offset] - min) / diff) * 255);
}
else if (type === 'halfFloat') {
value = (0, number_conversion_1.toHalfFloat)((data[offset] - min) / diff);
}
else {
value = (data[offset] - min) / diff;
}
if (variant === 'data') {
array[index] = value;
}
else {
if (variant === 'groups') {
if (type === 'halfFloat') {
let group = (0, interpolate_1.clamp)(Math.round(offset), 0, 16777216 - 1) + 1;
array[index + 2] = (0, number_conversion_1.toHalfFloat)(group % 256);
group = Math.floor(group / 256);
array[index + 1] = (0, number_conversion_1.toHalfFloat)(group % 256);
group = Math.floor(group / 256);
array[index] = (0, number_conversion_1.toHalfFloat)(group % 256);
}
else {
(0, number_packing_1.packIntToRGBArray)(offset, array, index);
}
}
else {
v3set(n0, data[o(Math.max(0, x - 1), y, z)], data[o(x, Math.max(0, y - 1), z)], data[o(x, y, Math.max(0, z - 1))]);
v3set(n1, data[o(Math.min(xn1, x + 1), y, z)], data[o(x, Math.min(yn1, y + 1), z)], data[o(x, y, Math.min(zn1, z + 1))]);
v3normalize(n0, v3sub(n0, n0, n1));
v3addScalar(n0, v3scale(n0, n0, 0.5), 0.5);
if (type === 'byte') {
v3toArray(v3scale(n0, n0, 255), array, index);
}
else if (type === 'halfFloat') {
array[index] = (0, number_conversion_1.toHalfFloat)(n0[0]);
array[index + 1] = (0, number_conversion_1.toHalfFloat)(n0[1]);
array[index + 2] = (0, number_conversion_1.toHalfFloat)(n0[2]);
}
else {
v3toArray(n0, array, index);
}
}
array[index + 3] = value;
}
}
}
}
return textureImage;
}
function createVolumeTexture3d(volume, type = 'byte') {
const { cells: { space, data }, stats: { max, min } } = volume.grid;
const [width, height, depth] = space.dimensions;
const { dataOffset: o } = space;
const array = type === 'byte'
? new Uint8Array(width * height * depth * 4)
: type === 'halfFloat'
? new Uint16Array(width * height * depth * 4)
: new Float32Array(width * height * depth * 4);
const textureVolume = { array, width, height, depth };
const diff = max - min;
const n0 = (0, vec3_1.Vec3)();
const n1 = (0, vec3_1.Vec3)();
const width1 = width - 1;
const height1 = height - 1;
const depth1 = depth - 1;
let i = 0;
for (let z = 0; z < depth; ++z) {
for (let y = 0; y < height; ++y) {
for (let x = 0; x < width; ++x) {
const offset = o(x, y, z);
v3set(n0, data[o(Math.max(0, x - 1), y, z)], data[o(x, Math.max(0, y - 1), z)], data[o(x, y, Math.max(0, z - 1))]);
v3set(n1, data[o(Math.min(width1, x + 1), y, z)], data[o(x, Math.min(height1, y + 1), z)], data[o(x, y, Math.min(depth1, z + 1))]);
v3normalize(n0, v3sub(n0, n0, n1));
v3addScalar(n0, v3scale(n0, n0, 0.5), 0.5);
if (type === 'byte') {
v3toArray(v3scale(n0, n0, 255), array, i);
array[i + 3] = Math.round(((data[offset] - min) / diff) * 255);
}
else if (type === 'halfFloat') {
array[i] = (0, number_conversion_1.toHalfFloat)(n0[0]);
array[i + 1] = (0, number_conversion_1.toHalfFloat)(n0[1]);
array[i + 2] = (0, number_conversion_1.toHalfFloat)(n0[2]);
array[i + 3] = (0, number_conversion_1.toHalfFloat)((data[offset] - min) / diff);
}
else {
v3toArray(n0, array, i);
array[i + 3] = (data[offset] - min) / diff;
}
i += 4;
}
}
}
return textureVolume;
}
function createSegmentTexture2d(volume, set, bbox, padding = 0) {
const data = volume.grid.cells.data;
const dim = geometry_1.Box3D.size((0, vec3_1.Vec3)(), bbox);
const o = volume.grid.cells.space.dataOffset;
const { width, height } = getVolumeTexture2dLayout(dim, padding);
const itemSize = 1;
const array = new Uint8Array(width * height * itemSize);
const textureImage = { array, width, height };
const [xn, yn, zn] = dim;
const xn1 = xn - 1;
const yn1 = yn - 1;
const zn1 = zn - 1;
const xnp = xn + padding;
const ynp = yn + padding;
const [minx, miny, minz] = bbox.min;
const [maxx, maxy, maxz] = bbox.max;
for (let z = 0; z < zn; ++z) {
for (let y = 0; y < yn; ++y) {
for (let x = 0; x < xn; ++x) {
const column = Math.floor(((z * xnp) % width) / xnp);
const row = Math.floor((z * xnp) / width);
const px = column * xnp + x;
const index = itemSize * ((row * ynp * width) + (y * width) + px);
const v0 = set.includes(data[o(x + minx, y + miny, z + minz)]) ? 255 : 0;
const xp = set.includes(data[o(Math.min(xn1 + maxx, x + 1 + minx), y + miny, z + minz)]) ? 255 : 0;
const xn = set.includes(data[o(Math.max(0, x - 1 + minx), y + miny, z + minz)]) ? 255 : 0;
const yp = set.includes(data[o(x + minx, Math.min(yn1 + maxy, y + 1 + miny), z + minz)]) ? 255 : 0;
const yn = set.includes(data[o(x + minx, Math.max(0, y - 1 + miny), z + minz)]) ? 255 : 0;
const zp = set.includes(data[o(x + minx, y + miny, Math.min(zn1 + maxz, z + 1 + minz))]) ? 255 : 0;
const zn = set.includes(data[o(x + minx, y + miny, Math.max(0, z - 1 + minz))]) ? 255 : 0;
array[index] = Math.round((v0 + v0 + xp + xn + yp + yn + zp + zn) / 8);
}
}
}
return textureImage;
}
/**
* Create a new volume that is wrapped by one cell in all dimensions.
* Reuses the original volume grid data with new data accessors.
* Only intended for isosurface construction.
*/
function createWrappedVolume(volume) {
const { grid } = volume;
const { space } = grid.cells;
const { get, set, add, dataOffset } = space;
const [xn, yn, zn] = space.dimensions;
const _dimensions = vec3_1.Vec3.create(xn + 1, yn + 1, zn + 1);
const _get = (data, x, y, z) => get(data, x % xn, y % yn, z % zn);
const _set = (data, x, y, z, d) => set(data, x % xn, y % yn, z % zn, d);
const _add = (data, x, y, z, d) => add(data, x % xn, y % yn, z % zn, d);
const _dataOffset = (x, y, z) => dataOffset(x % xn, y % yn, z % zn);
const _space = {
...space,
dimensions: _dimensions,
get: _get,
set: _set,
add: _add,
dataOffset: _dataOffset,
};
const matrix = volume_1.Grid.getGridToCartesianTransform(volume.grid);
const _transform = { kind: 'matrix', matrix };
const _grid = {
...grid,
transform: _transform,
cells: {
...grid.cells,
space: _space
}
};
return {
...volume,
grid: _grid
};
}