molstar
Version:
A comprehensive macromolecular library.
284 lines (283 loc) • 14.7 kB
JavaScript
/**
* Copyright (c) 2022 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.SegmentRepresentationProvider = exports.SegmentParams = exports.SegmentMeshParams = exports.VolumeSegmentParams = void 0;
exports.SegmentVisual = SegmentVisual;
exports.eachSegment = eachSegment;
exports.createVolumeSegmentMesh = createVolumeSegmentMesh;
exports.SegmentMeshVisual = SegmentMeshVisual;
exports.SegmentTextureMeshVisual = SegmentTextureMeshVisual;
exports.getSegmentParams = getSegmentParams;
exports.SegmentRepresentation = SegmentRepresentation;
const param_definition_1 = require("../../mol-util/param-definition");
const volume_1 = require("../../mol-model/volume");
const mesh_1 = require("../../mol-geo/geometry/mesh/mesh");
const algorithm_1 = require("../../mol-geo/util/marching-cubes/algorithm");
const representation_1 = require("./representation");
const location_iterator_1 = require("../../mol-geo/util/location-iterator");
const representation_2 = require("../representation");
const loci_1 = require("../../mol-model/loci");
const int_1 = require("../../mol-data/int");
const linear_algebra_1 = require("../../mol-math/linear-algebra");
const array_1 = require("../../mol-util/array");
const util_1 = require("./util");
const texture_mesh_1 = require("../../mol-geo/geometry/texture-mesh/texture-mesh");
const base_1 = require("../../mol-geo/geometry/base");
const value_cell_1 = require("../../mol-util/value-cell");
const isosurface_1 = require("../../mol-gl/compute/marching-cubes/isosurface");
const box3d_1 = require("../../mol-math/geometry/primitives/box3d");
exports.VolumeSegmentParams = {
segments: param_definition_1.ParamDefinition.Converted((v) => v.map(x => `${x}`), (v) => v.map(x => parseInt(x)), param_definition_1.ParamDefinition.MultiSelect(['0'], param_definition_1.ParamDefinition.arrayToOptions(['0']), {
isEssential: true
}))
};
function gpuSupport(webgl) {
return webgl.extensions.colorBufferFloat && webgl.extensions.textureFloat && webgl.extensions.drawBuffers;
}
const Padding = 1;
function suitableForGpu(volume, webgl) {
// small volumes are about as fast or faster on CPU vs integrated GPU
if (volume.grid.cells.data.length < Math.pow(10, 3))
return false;
// the GPU is much more memory contraint, especially true for integrated GPUs,
// fallback to CPU for large volumes
const gridDim = volume.grid.cells.space.dimensions;
const { powerOfTwoSize } = (0, util_1.getVolumeTexture2dLayout)(gridDim, Padding);
return powerOfTwoSize <= webgl.maxTextureSize / 2;
}
const _translate = (0, linear_algebra_1.Mat4)();
function getSegmentTransform(grid, segmentBox) {
const transform = volume_1.Grid.getGridToCartesianTransform(grid);
const translate = linear_algebra_1.Mat4.fromTranslation(_translate, segmentBox.min);
return linear_algebra_1.Mat4.mul((0, linear_algebra_1.Mat4)(), transform, translate);
}
function SegmentVisual(materialId, volume, key, props, webgl) {
if (props.tryUseGpu && webgl && gpuSupport(webgl) && suitableForGpu(volume, webgl)) {
return SegmentTextureMeshVisual(materialId);
}
return SegmentMeshVisual(materialId);
}
function getLoci(volume, props) {
return volume_1.Volume.Segment.Loci(volume, props.segments);
}
function getSegmentLoci(pickingId, volume, key, props, id) {
const { objectId, groupId } = pickingId;
if (id === objectId) {
const granularity = volume_1.Volume.PickingGranularity.get(volume);
if (granularity === 'volume') {
return volume_1.Volume.Loci(volume);
}
else if (granularity === 'object') {
return volume_1.Volume.Segment.Loci(volume, [key]);
}
else {
return volume_1.Volume.Cell.Loci(volume, int_1.Interval.ofSingleton(groupId));
}
}
return loci_1.EmptyLoci;
}
function eachSegment(loci, volume, key, props, apply) {
const segments = int_1.SortedArray.ofSingleton(key);
return (0, util_1.eachVolumeLoci)(loci, volume, { segments }, apply);
}
//
function getSegmentCells(set, bbox, cells) {
const data = cells.data;
const o = cells.space.dataOffset;
const dim = box3d_1.Box3D.size((0, linear_algebra_1.Vec3)(), bbox);
const [xn, yn, zn] = dim;
const xn1 = xn - 1;
const yn1 = yn - 1;
const zn1 = zn - 1;
const [minx, miny, minz] = bbox.min;
const [maxx, maxy, maxz] = bbox.max;
const axisOrder = [...cells.space.axisOrderSlowToFast];
const segmentSpace = linear_algebra_1.Tensor.Space(dim, axisOrder, Uint8Array);
const segmentCells = linear_algebra_1.Tensor.create(segmentSpace, segmentSpace.create());
const segData = segmentCells.data;
const segSet = segmentSpace.set;
for (let z = 0; z < zn; ++z) {
for (let y = 0; y < yn; ++y) {
for (let x = 0; x < xn; ++x) {
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;
segSet(segData, x, y, z, Math.round((v0 + v0 + xp + xn + yp + yn + zp + zn) / 8));
}
}
}
return segmentCells;
}
async function createVolumeSegmentMesh(ctx, volume, key, theme, props, mesh) {
const segmentation = volume_1.Volume.Segmentation.get(volume);
if (!segmentation)
throw new Error('missing volume segmentation');
ctx.runtime.update({ message: 'Marching cubes...' });
const bbox = box3d_1.Box3D.clone(segmentation.bounds[key]);
box3d_1.Box3D.expand(bbox, bbox, linear_algebra_1.Vec3.create(2, 2, 2));
const set = Array.from(segmentation.segments.get(key).values());
const cells = getSegmentCells(set, bbox, volume.grid.cells);
const ids = (0, array_1.fillSerial)(new Int32Array(cells.data.length));
const surface = await (0, algorithm_1.computeMarchingCubesMesh)({
isoLevel: 128,
scalarField: cells,
idField: linear_algebra_1.Tensor.create(cells.space, linear_algebra_1.Tensor.Data1(ids))
}, mesh).runAsChild(ctx.runtime);
const transform = getSegmentTransform(volume.grid, bbox);
mesh_1.Mesh.transform(surface, transform);
if (ctx.webgl && !ctx.webgl.isWebGL2) {
// 2nd arg means not to split triangles based on group id. Splitting triangles
// is too expensive if each cell has its own group id as is the case here.
mesh_1.Mesh.uniformTriangleGroup(surface, false);
value_cell_1.ValueCell.updateIfChanged(surface.varyingGroup, false);
}
else {
value_cell_1.ValueCell.updateIfChanged(surface.varyingGroup, true);
}
surface.setBoundingSphere(volume_1.Volume.Segment.getBoundingSphere(volume, [key]));
return surface;
}
exports.SegmentMeshParams = {
...mesh_1.Mesh.Params,
...texture_mesh_1.TextureMesh.Params,
...exports.VolumeSegmentParams,
quality: { ...mesh_1.Mesh.Params.quality, isEssential: false },
tryUseGpu: param_definition_1.ParamDefinition.Boolean(true),
};
function SegmentMeshVisual(materialId) {
return (0, representation_1.VolumeVisual)({
defaultProps: param_definition_1.ParamDefinition.getDefaultValues(exports.SegmentMeshParams),
createGeometry: createVolumeSegmentMesh,
createLocationIterator: (volume, key) => {
const l = volume_1.Volume.Segment.Location(volume, key);
return (0, location_iterator_1.LocationIterator)(volume.grid.cells.data.length, 1, 1, () => l);
},
getLoci: getSegmentLoci,
eachLocation: eachSegment,
setUpdateState: (state, volume, newProps, currentProps) => {
},
geometryUtils: mesh_1.Mesh.Utils,
mustRecreate: (volumeKey, props, webgl) => {
return props.tryUseGpu && !!webgl && suitableForGpu(volumeKey.volume, webgl);
}
}, materialId);
}
//
const SegmentTextureName = 'segment-texture';
function getSegmentTexture(volume, segment, webgl) {
const segmentation = volume_1.Volume.Segmentation.get(volume);
if (!segmentation)
throw new Error('missing volume segmentation');
const { resources } = webgl;
const bbox = box3d_1.Box3D.clone(segmentation.bounds[segment]);
box3d_1.Box3D.expand(bbox, bbox, linear_algebra_1.Vec3.create(2, 2, 2));
const transform = getSegmentTransform(volume.grid, bbox);
const gridDimension = box3d_1.Box3D.size((0, linear_algebra_1.Vec3)(), bbox);
const { width, height, powerOfTwoSize: texDim } = (0, util_1.getVolumeTexture2dLayout)(gridDimension, Padding);
const gridTexDim = linear_algebra_1.Vec3.create(width, height, 0);
const gridTexScale = linear_algebra_1.Vec2.create(width / texDim, height / texDim);
// console.log({ texDim, width, height, gridDimension });
if (texDim > webgl.maxTextureSize / 2) {
throw new Error('volume too large for gpu segment extraction');
}
if (!webgl.namedTextures[SegmentTextureName]) {
webgl.namedTextures[SegmentTextureName] = resources.texture('image-uint8', 'alpha', 'ubyte', 'linear');
}
const texture = webgl.namedTextures[SegmentTextureName];
texture.define(texDim, texDim);
// load volume into sub-section of texture
const set = Array.from(segmentation.segments.get(segment).values());
texture.load((0, util_1.createSegmentTexture2d)(volume, set, bbox, Padding), true);
gridDimension[0] += Padding;
gridDimension[1] += Padding;
return {
texture,
transform,
gridDimension,
gridTexDim,
gridTexScale
};
}
async function createVolumeSegmentTextureMesh(ctx, volume, segment, theme, props, textureMesh) {
if (!ctx.webgl)
throw new Error('webgl context required to create volume segment texture-mesh');
if (volume.grid.cells.data.length <= 1) {
return texture_mesh_1.TextureMesh.createEmpty(textureMesh);
}
const { texture, gridDimension, gridTexDim, gridTexScale, transform } = getSegmentTexture(volume, segment, ctx.webgl);
const axisOrder = volume.grid.cells.space.axisOrderSlowToFast;
const buffer = textureMesh === null || textureMesh === void 0 ? void 0 : textureMesh.doubleBuffer.get();
const gv = (0, isosurface_1.extractIsosurface)(ctx.webgl, texture, gridDimension, gridTexDim, gridTexScale, transform, 0.5, false, false, axisOrder, true, buffer === null || buffer === void 0 ? void 0 : buffer.vertex, buffer === null || buffer === void 0 ? void 0 : buffer.group, buffer === null || buffer === void 0 ? void 0 : buffer.normal);
const groupCount = volume.grid.cells.data.length;
const surface = texture_mesh_1.TextureMesh.create(gv.vertexCount, groupCount, gv.vertexTexture, gv.groupTexture, gv.normalTexture, volume_1.Volume.Segment.getBoundingSphere(volume, [segment]), textureMesh);
return surface;
}
function SegmentTextureMeshVisual(materialId) {
return (0, representation_1.VolumeVisual)({
defaultProps: param_definition_1.ParamDefinition.getDefaultValues(exports.SegmentMeshParams),
createGeometry: createVolumeSegmentTextureMesh,
createLocationIterator: (volume, segment) => {
const l = volume_1.Volume.Segment.Location(volume, segment);
return (0, location_iterator_1.LocationIterator)(volume.grid.cells.data.length, 1, 1, () => l);
},
getLoci: getSegmentLoci,
eachLocation: eachSegment,
setUpdateState: (state, volume, newProps, currentProps) => {
},
geometryUtils: texture_mesh_1.TextureMesh.Utils,
mustRecreate: (volumeKey, props, webgl) => {
return !props.tryUseGpu || !webgl || !suitableForGpu(volumeKey.volume, webgl);
},
dispose: (geometry) => {
geometry.vertexTexture.ref.value.destroy();
geometry.groupTexture.ref.value.destroy();
geometry.normalTexture.ref.value.destroy();
geometry.doubleBuffer.destroy();
}
}, materialId);
}
//
function getSegments(props) {
return int_1.SortedArray.ofUnsortedArray(props.segments);
}
const SegmentVisuals = {
'segment': (ctx, getParams) => (0, representation_1.VolumeRepresentation)('Segment mesh', ctx, getParams, SegmentVisual, getLoci, getSegments),
};
exports.SegmentParams = {
...exports.SegmentMeshParams,
visuals: param_definition_1.ParamDefinition.MultiSelect(['segment'], param_definition_1.ParamDefinition.objectToOptions(SegmentVisuals)),
bumpFrequency: param_definition_1.ParamDefinition.Numeric(1, { min: 0, max: 10, step: 0.1 }, base_1.BaseGeometry.ShadingCategory),
};
function getSegmentParams(ctx, volume) {
const p = param_definition_1.ParamDefinition.clone(exports.SegmentParams);
const segmentation = volume_1.Volume.Segmentation.get(volume);
if (segmentation) {
const segments = Array.from(segmentation.segments.keys());
p.segments = param_definition_1.ParamDefinition.Converted((v) => v.map(x => `${x}`), (v) => v.map(x => parseInt(x)), param_definition_1.ParamDefinition.MultiSelect(segments.map(x => `${x}`), param_definition_1.ParamDefinition.arrayToOptions(segments.map(x => `${x}`)), {
isEssential: true
}));
}
return p;
}
function SegmentRepresentation(ctx, getParams) {
return representation_2.Representation.createMulti('Segment', ctx, getParams, representation_2.Representation.StateBuilder, SegmentVisuals);
}
exports.SegmentRepresentationProvider = (0, representation_1.VolumeRepresentationProvider)({
name: 'segment',
label: 'Segment',
description: 'Displays a triangulated segment of volumetric data.',
factory: SegmentRepresentation,
getParams: getSegmentParams,
defaultValues: param_definition_1.ParamDefinition.getDefaultValues(exports.SegmentParams),
defaultColorTheme: { name: 'volume-segment' },
defaultSizeTheme: { name: 'uniform' },
isApplicable: (volume) => !volume_1.Volume.isEmpty(volume) && !!volume_1.Volume.Segmentation.get(volume)
});
;