UNPKG

molstar

Version:

A comprehensive macromolecular library.

378 lines (377 loc) 18.4 kB
"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.SliceRepresentationProvider = exports.SliceParams = void 0; exports.getSliceParams = getSliceParams; exports.createImage = createImage; exports.SliceVisual = SliceVisual; exports.SliceRepresentation = SliceRepresentation; const param_definition_1 = require("../../mol-util/param-definition"); const image_1 = require("../../mol-geo/geometry/image/image"); const volume_1 = require("../../mol-model/volume"); const representation_1 = require("./representation"); const location_iterator_1 = require("../../mol-geo/util/location-iterator"); const location_1 = require("../../mol-model/location"); const loci_1 = require("../../mol-model/loci"); const int_1 = require("../../mol-data/int"); const util_1 = require("../../mol-geo/util"); const color_1 = require("../../mol-util/color"); const color_2 = require("../../mol-theme/color"); const number_packing_1 = require("../../mol-util/number-packing"); const util_2 = require("./util"); const vec3_1 = require("../../mol-math/linear-algebra/3d/vec3"); const quat_1 = require("../../mol-math/linear-algebra/3d/quat"); const misc_1 = require("../../mol-math/misc"); const mat4_1 = require("../../mol-math/linear-algebra/3d/mat4"); const interpolate_1 = require("../../mol-math/interpolate"); exports.SliceParams = { ...image_1.Image.Params, quality: { ...image_1.Image.Params.quality, isEssential: false }, dimension: param_definition_1.ParamDefinition.MappedStatic('x', { x: param_definition_1.ParamDefinition.Numeric(0, { min: 0, max: 0, step: 1 }, { immediateUpdate: true }), y: param_definition_1.ParamDefinition.Numeric(0, { min: 0, max: 0, step: 1 }, { immediateUpdate: true }), z: param_definition_1.ParamDefinition.Numeric(0, { min: 0, max: 0, step: 1 }, { immediateUpdate: true }), }, { isEssential: true, hideIf: p => p.mode !== 'grid', description: 'Slice position in grid coordinates.' }), isoValue: volume_1.Volume.IsoValueParam, mode: param_definition_1.ParamDefinition.Select('grid', param_definition_1.ParamDefinition.arrayToOptions(['grid', 'frame']), { description: 'Grid: slice through the volume along the grid axes in integer steps. Frame: slice through the volume along arbitrary axes in any step size.' }), offset: param_definition_1.ParamDefinition.Numeric(0, { min: -1, max: 1, step: 0.01 }, { isEssential: true, immediateUpdate: true, hideIf: p => p.mode !== 'frame', description: 'Relative offset from center.' }), axis: param_definition_1.ParamDefinition.Select('a', param_definition_1.ParamDefinition.arrayToOptions(['a', 'b', 'c']), { isEssential: true, hideIf: p => p.mode !== 'frame', description: 'Axis of the frame.' }), rotation: param_definition_1.ParamDefinition.Group({ axis: param_definition_1.ParamDefinition.Vec3(vec3_1.Vec3.create(1, 0, 0), {}, { description: 'Axis of rotation' }), angle: param_definition_1.ParamDefinition.Numeric(0, { min: -180, max: 180, step: 1 }, { immediateUpdate: true, description: 'Axis rotation angle in Degrees' }), }, { isExpanded: true, hideIf: p => p.mode !== 'frame' }), }; function getSliceParams(ctx, volume) { const p = param_definition_1.ParamDefinition.clone(exports.SliceParams); const dim = volume.grid.cells.space.dimensions; p.dimension = param_definition_1.ParamDefinition.MappedStatic('x', { x: param_definition_1.ParamDefinition.Numeric(0, { min: 0, max: dim[0] - 1, step: 1 }, { immediateUpdate: true }), y: param_definition_1.ParamDefinition.Numeric(0, { min: 0, max: dim[1] - 1, step: 1 }, { immediateUpdate: true }), z: param_definition_1.ParamDefinition.Numeric(0, { min: 0, max: dim[2] - 1, step: 1 }, { immediateUpdate: true }), }, { isEssential: true, hideIf: p => p.mode !== 'grid' }); p.isoValue = volume_1.Volume.createIsoValueParam(volume_1.Volume.IsoValue.absolute(volume.grid.stats.min), volume.grid.stats); return p; } async function createImage(ctx, volume, key, theme, props, image) { if (props.mode === 'frame') { return createFrameImage(ctx, volume, key, theme, props, image); } else { return createGridImage(ctx, volume, key, theme, props, image); } } // async function createFrameImage(ctx, volume, key, theme, props, image) { const { offset, isoValue } = props; const { cells: { space }, stats } = volume.grid; const { min, max } = stats; const isUniform = theme.color.granularity === 'uniform'; const color = 'color' in theme.color && theme.color.color ? theme.color.color : () => (0, color_1.Color)(0xffffff); const { size, major, minor, normal, center, trim, resolution } = getFrame(volume, props); const scaleFactor = 1 / resolution; const scale = vec3_1.Vec3.create(size[0], size[1], 1); const offsetDir = vec3_1.Vec3.setMagnitude((0, vec3_1.Vec3)(), normal, size[2] / 2); const width = Math.floor(size[1] * scaleFactor); const height = Math.floor(size[0] * scaleFactor); const m = mat4_1.Mat4.identity(); const v = (0, vec3_1.Vec3)(); const anchor = (0, vec3_1.Vec3)(); vec3_1.Vec3.add(v, center, major); mat4_1.Mat4.targetTo(m, center, v, minor); vec3_1.Vec3.scaleAndAdd(anchor, center, offsetDir, offset); mat4_1.Mat4.setTranslation(m, anchor); mat4_1.Mat4.mul(m, m, mat4_1.Mat4.rotY90); mat4_1.Mat4.scale(m, m, scale); const imageArray = new Uint8Array(width * height * 4); const groupArray = new Uint8Array(width * height * 4); const valueArray = new Float32Array(width * height); const [mx, my, mz] = space.dimensions; const gridToCartn = volume_1.Grid.getGridToCartesianTransform(volume.grid); const cartnToGrid = mat4_1.Mat4.invert((0, mat4_1.Mat4)(), gridToCartn); const gridCoords = (0, vec3_1.Vec3)(); const pl = (0, location_iterator_1.PositionLocation)((0, vec3_1.Vec3)(), (0, vec3_1.Vec3)()); const getTrilinearlyInterpolated = volume_1.Grid.makeGetTrilinearlyInterpolated(volume.grid, 'none'); let i = 0; for (let ih = 0; ih < height; ++ih) { for (let iw = 0; iw < width; ++iw) { const y = ((0, interpolate_1.clamp)(iw + 0.5, 0, width - 1) / width) - 0.5; const x = ((0, interpolate_1.clamp)(ih + 0.5, 0, height - 1) / height) - 0.5; vec3_1.Vec3.set(v, x, -y, 0); vec3_1.Vec3.transformMat4(v, v, m); vec3_1.Vec3.copy(gridCoords, v); vec3_1.Vec3.transformMat4(gridCoords, gridCoords, cartnToGrid); const ix = Math.trunc(gridCoords[0]); const iy = Math.trunc(gridCoords[1]); const iz = Math.trunc(gridCoords[2]); vec3_1.Vec3.copy(pl.position, v); const c = color(pl, false); color_1.Color.toArray(c, imageArray, i); const val = (0, interpolate_1.normalize)(getTrilinearlyInterpolated(v), min, max); if (isUniform) { imageArray[i] *= val; imageArray[i + 1] *= val; imageArray[i + 2] *= val; } valueArray[i / 4] = val; if (ix >= 0 && ix < mx && iy >= 0 && iy < my && iz >= 0 && iz < mz) { (0, number_packing_1.packIntToRGBArray)(space.dataOffset(ix, iy, iz), groupArray, i); } i += 4; } } const imageTexture = { width, height, array: imageArray, flipY: true }; const groupTexture = { width, height, array: groupArray, flipY: true }; const valueTexture = { width, height, array: valueArray, flipY: true }; const corners = new Float32Array([ -0.5, 0.5, 0, 0.5, 0.5, 0, -0.5, -0.5, 0, 0.5, -0.5, 0 ]); (0, util_1.transformPositionArray)(m, corners, 0, 4); const isoLevel = (0, interpolate_1.clamp)((0, interpolate_1.normalize)(volume_1.Volume.IsoValue.toAbsolute(isoValue, stats).absoluteValue, min, max), 0, 1); return image_1.Image.create(imageTexture, corners, groupTexture, valueTexture, trim, isoLevel, image); } function getFrame(volume, props) { const { axis, rotation, mode } = props; const gridToCartn = volume_1.Grid.getGridToCartesianTransform(volume.grid); const cartnToGrid = mat4_1.Mat4.invert((0, mat4_1.Mat4)(), gridToCartn); const [nx, ny, nz] = volume.grid.cells.space.dimensions; const a = nx - 1; const b = ny - 1; const c = nz - 1; const dirA = vec3_1.Vec3.create(a, 0, 0); const dirB = vec3_1.Vec3.create(0, b, 0); const dirC = vec3_1.Vec3.create(0, 0, c); const resolution = Math.max(a, b, c) / Math.max(nx, ny, nz); const min = vec3_1.Vec3.create(0, 0, 0); const max = vec3_1.Vec3.create(a, b, c); vec3_1.Vec3.transformMat4(min, min, gridToCartn); vec3_1.Vec3.transformMat4(max, max, gridToCartn); const center = vec3_1.Vec3.center((0, vec3_1.Vec3)(), max, min); const size = (0, vec3_1.Vec3)(); const major = (0, vec3_1.Vec3)(); const minor = (0, vec3_1.Vec3)(); const normal = (0, vec3_1.Vec3)(); if (axis === 'c') { vec3_1.Vec3.set(size, a, b, c); vec3_1.Vec3.copy(major, dirA); vec3_1.Vec3.copy(minor, dirB); vec3_1.Vec3.copy(normal, dirC); } else if (axis === 'b') { vec3_1.Vec3.set(size, a, c, b); vec3_1.Vec3.copy(major, dirA); vec3_1.Vec3.copy(normal, dirB); vec3_1.Vec3.copy(minor, dirC); } else { vec3_1.Vec3.set(size, b, c, a); vec3_1.Vec3.copy(normal, dirA); vec3_1.Vec3.copy(major, dirB); vec3_1.Vec3.copy(minor, dirC); } if (rotation.angle !== 0) { const ra = (0, vec3_1.Vec3)(); vec3_1.Vec3.scaleAndAdd(ra, ra, dirA, rotation.axis[0]); vec3_1.Vec3.scaleAndAdd(ra, ra, dirB, rotation.axis[1]); vec3_1.Vec3.scaleAndAdd(ra, ra, dirC, rotation.axis[2]); vec3_1.Vec3.normalize(ra, ra); const rm = mat4_1.Mat4.fromRotation((0, mat4_1.Mat4)(), (0, misc_1.degToRad)(rotation.angle), ra); vec3_1.Vec3.transformDirection(major, major, rm); vec3_1.Vec3.transformDirection(minor, minor, rm); vec3_1.Vec3.transformDirection(normal, normal, rm); } if (mode === 'frame') { const r = vec3_1.Vec3.distance(min, max); const s = vec3_1.Vec3.distance(min, max) * Math.SQRT2; vec3_1.Vec3.set(size, s, s, r); } vec3_1.Vec3.transformDirection(major, major, gridToCartn); vec3_1.Vec3.transformDirection(minor, minor, gridToCartn); vec3_1.Vec3.transformDirection(normal, normal, gridToCartn); const trim = { type: 3, center: vec3_1.Vec3.create(a / 2, b / 2, c / 2), scale: vec3_1.Vec3.create(a, b, c), rotation: quat_1.Quat.identity(), transform: cartnToGrid, }; return { size, major, minor, normal, center, trim, resolution }; } // async function createGridImage(ctx, volume, key, theme, props, image) { const { dimension: { name: dim }, isoValue } = props; const { cells: { space, data }, stats } = volume.grid; const { min, max } = stats; const isUniform = theme.color.granularity === 'uniform'; const color = 'color' in theme.color && theme.color.color ? theme.color.color : () => (0, color_1.Color)(0xffffff); const { width, height, x, y, z, x0, y0, z0, nx, ny, nz } = getSliceInfo(volume.grid, props); const corners = new Float32Array(dim === 'x' ? [x, 0, 0, x, y, 0, x, 0, z, x, y, z] : dim === 'y' ? [0, y, 0, x, y, 0, 0, y, z, x, y, z] : [0, 0, z, 0, y, z, x, 0, z, x, y, z]); const imageArray = new Uint8Array(width * height * 4); const groupArray = getPackedGroupArray(volume.grid, props); const valueArray = new Float32Array(width * height); const gridToCartn = volume_1.Grid.getGridToCartesianTransform(volume.grid); const l = (0, location_iterator_1.PositionLocation)((0, vec3_1.Vec3)(), (0, vec3_1.Vec3)()); let i = 0; for (let iy = y0; iy < ny; ++iy) { for (let ix = x0; ix < nx; ++ix) { for (let iz = z0; iz < nz; ++iz) { vec3_1.Vec3.set(l.position, ix, iy, iz); vec3_1.Vec3.transformMat4(l.position, l.position, gridToCartn); color_1.Color.toArray(color(l, false), imageArray, i); const val = (0, interpolate_1.normalize)(space.get(data, ix, iy, iz), min, max); if (isUniform) { imageArray[i] *= val; imageArray[i + 1] *= val; imageArray[i + 2] *= val; } valueArray[i / 4] = val; i += 4; } } } const imageTexture = { width, height, array: imageArray, flipY: true }; const groupTexture = { width, height, array: groupArray, flipY: true }; const valueTexture = { width, height, array: valueArray, flipY: true }; const transform = volume_1.Grid.getGridToCartesianTransform(volume.grid); (0, util_1.transformPositionArray)(transform, corners, 0, 4); const trim = image_1.Image.createEmptyTrim(); const isoLevel = (0, interpolate_1.clamp)((0, interpolate_1.normalize)(volume_1.Volume.IsoValue.toAbsolute(isoValue, stats).absoluteValue, min, max), 0, 1); return image_1.Image.create(imageTexture, corners, groupTexture, valueTexture, trim, isoLevel, image); } function getSliceInfo(grid, props) { const { dimension: { name: dim, params: index } } = props; const { space } = grid.cells; let width, height; let x, y, z; let x0 = 0, y0 = 0, z0 = 0; let [nx, ny, nz] = space.dimensions; if (dim === 'x') { x = index, y = ny - 1, z = nz - 1; width = nz, height = ny; x0 = x, nx = x0 + 1; } else if (dim === 'y') { x = nx - 1, y = index, z = nz - 1; width = nz, height = nx; y0 = y, ny = y0 + 1; } else { x = nx - 1, y = ny - 1, z = index; width = nx, height = ny; z0 = z, nz = z0 + 1; } return { width, height, x, y, z, x0, y0, z0, nx, ny, nz }; } function getPackedGroupArray(grid, props) { const { space } = grid.cells; const { width, height, x0, y0, z0, nx, ny, nz } = getSliceInfo(grid, props); const groupArray = new Uint8Array(width * height * 4); let j = 0; for (let iy = y0; iy < ny; ++iy) { for (let ix = x0; ix < nx; ++ix) { for (let iz = z0; iz < nz; ++iz) { (0, number_packing_1.packIntToRGBArray)(space.dataOffset(ix, iy, iz), groupArray, j); j += 4; } } } return groupArray; } function getGroupArray(grid, props) { const { space } = grid.cells; const { width, height, x0, y0, z0, nx, ny, nz } = getSliceInfo(grid, props); const groupArray = new Uint32Array(width * height); let j = 0; for (let iy = y0; iy < ny; ++iy) { for (let ix = x0; ix < nx; ++ix) { for (let iz = z0; iz < nz; ++iz) { groupArray[j] = space.dataOffset(ix, iy, iz); j += 1; } } } return groupArray; } function getLoci(volume, props) { // TODO: cache somehow? if (props.mode === 'grid') { const groupArray = getGroupArray(volume.grid, props); return volume_1.Volume.Cell.Loci(volume, int_1.SortedArray.ofUnsortedArray(groupArray)); } else { // TODO: get exact groups return volume_1.Volume.Loci(volume); } } function getSliceLoci(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); } if (granularity === 'object') { return getLoci(volume, props); } else { return volume_1.Volume.Cell.Loci(volume, int_1.Interval.ofSingleton(groupId)); } } return loci_1.EmptyLoci; } function eachSlice(loci, volume, key, props, apply) { return (0, util_2.eachVolumeLoci)(loci, volume, undefined, apply); } // function SliceVisual(materialId) { return (0, representation_1.VolumeVisual)({ defaultProps: param_definition_1.ParamDefinition.getDefaultValues(exports.SliceParams), createGeometry: createImage, createLocationIterator: (volume) => (0, location_iterator_1.LocationIterator)(volume.grid.cells.data.length, 1, 1, () => location_1.NullLocation), getLoci: getSliceLoci, eachLocation: eachSlice, setUpdateState: (state, volume, newProps, currentProps, newTheme, currentTheme) => { state.createGeometry = (newProps.dimension.name !== currentProps.dimension.name || newProps.dimension.params !== currentProps.dimension.params || newProps.mode !== currentProps.mode || !vec3_1.Vec3.equals(newProps.rotation.axis, currentProps.rotation.axis) || newProps.rotation.angle !== currentProps.rotation.angle || newProps.offset !== currentProps.offset || newProps.axis !== currentProps.axis || !volume_1.Volume.IsoValue.areSame(newProps.isoValue, currentProps.isoValue, volume.grid.stats) || !color_2.ColorTheme.areEqual(newTheme.color, currentTheme.color)); }, geometryUtils: image_1.Image.Utils }, materialId); } function SliceRepresentation(ctx, getParams) { return (0, representation_1.VolumeRepresentation)('Slice', ctx, getParams, SliceVisual, getLoci); } exports.SliceRepresentationProvider = (0, representation_1.VolumeRepresentationProvider)({ name: 'slice', label: 'Slice', description: 'Slice of volume rendered as image with interpolation.', factory: SliceRepresentation, getParams: getSliceParams, defaultValues: param_definition_1.ParamDefinition.getDefaultValues(exports.SliceParams), defaultColorTheme: { name: 'uniform' }, defaultSizeTheme: { name: 'uniform' }, isApplicable: (volume) => !volume_1.Volume.isEmpty(volume) && !volume_1.Volume.Segmentation.get(volume) });