UNPKG

molstar

Version:

A comprehensive macromolecular library.

432 lines 25.9 kB
/** * Copyright (c) 2019-2020 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 { __assign, __awaiter, __extends, __generator } from "tslib"; import { ParamDefinition as PD } from '../../../../mol-util/param-definition'; import { PluginStateObject } from '../../../../mol-plugin-state/objects'; import { Volume } from '../../../../mol-model/volume'; import { Box3D } from '../../../../mol-math/geometry'; import { Mat4, Vec3 } from '../../../../mol-math/linear-algebra'; import { Color } from '../../../../mol-util/color'; import { PluginBehavior } from '../../behavior'; import { LRUCache } from '../../../../mol-util/lru-cache'; import { urlCombine } from '../../../../mol-util/url'; import { CIF } from '../../../../mol-io/reader/cif'; import { volumeFromDensityServerData } from '../../../../mol-model-formats/volume/density-server'; import { PluginCommands } from '../../../commands'; import { StateSelection } from '../../../../mol-state'; import { StructureElement } from '../../../../mol-model/structure'; import { EmptyLoci, Loci, isEmptyLoci } from '../../../../mol-model/loci'; import { Asset } from '../../../../mol-util/assets'; import { GlobalModelTransformInfo } from '../../../../mol-model/structure/model/properties/global-transform'; var VolumeStreaming = /** @class */ (function (_super) { __extends(VolumeStreaming, _super); function VolumeStreaming() { return _super !== null && _super.apply(this, arguments) || this; } return VolumeStreaming; }(PluginStateObject.CreateBehavior({ name: 'Volume Streaming' }))); export { VolumeStreaming }; (function (VolumeStreaming) { VolumeStreaming.RootTag = 'volume-streaming-info'; function channelParam(label, color, defaultValue, stats, defaults) { var _a, _b, _c, _d; if (defaults === void 0) { defaults = {}; } return PD.Group({ isoValue: Volume.createIsoValueParam((_a = defaults.isoValue) !== null && _a !== void 0 ? _a : defaultValue, stats), color: PD.Color((_b = defaults.color) !== null && _b !== void 0 ? _b : color), wireframe: PD.Boolean((_c = defaults.wireframe) !== null && _c !== void 0 ? _c : false), opacity: PD.Numeric((_d = defaults.opacity) !== null && _d !== void 0 ? _d : 0.3, { min: 0, max: 1, step: 0.01 }) }, { label: label, isExpanded: true }); } var fakeSampling = { byteOffset: 0, rate: 1, sampleCount: [1, 1, 1], valuesInfo: [{ mean: 0, min: -1, max: 1, sigma: 0.1 }, { mean: 0, min: -1, max: 1, sigma: 0.1 }] }; function createParams(options) { if (options === void 0) { options = {}; } var data = options.data, defaultView = options.defaultView, channelParams = options.channelParams; var map = new Map(); if (data) data.entries.forEach(function (d) { return map.set(d.dataId, d); }); var names = data ? data.entries.map(function (d) { return [d.dataId, d.dataId]; }) : []; var defaultKey = data ? data.entries[0].dataId : ''; return { entry: PD.Mapped(defaultKey, names, function (name) { return PD.Group(createEntryParams({ entryData: map.get(name), defaultView: defaultView, structure: data && data.structure, channelParams: channelParams })); }), }; } VolumeStreaming.createParams = createParams; function createEntryParams(options) { var entryData = options.entryData, defaultView = options.defaultView, structure = options.structure, _a = options.channelParams, channelParams = _a === void 0 ? {} : _a; // fake the info var info = entryData || { kind: 'em', header: { sampling: [fakeSampling], availablePrecisions: [{ precision: 0, maxVoxels: 0 }] }, emDefaultContourLevel: Volume.IsoValue.relative(0) }; var box = (structure && structure.boundary.box) || Box3D(); return { view: PD.MappedStatic(defaultView || (info.kind === 'em' ? 'cell' : 'selection-box'), { 'off': PD.Group({}), 'box': PD.Group({ bottomLeft: PD.Vec3(box.min), topRight: PD.Vec3(box.max), }, { description: 'Static box defined by cartesian coords.', isFlat: true }), 'selection-box': PD.Group({ radius: PD.Numeric(5, { min: 0, max: 50, step: 0.5 }, { description: 'Radius in \u212B within which the volume is shown.' }), bottomLeft: PD.Vec3(Vec3.create(0, 0, 0), {}, { isHidden: true }), topRight: PD.Vec3(Vec3.create(0, 0, 0), {}, { isHidden: true }), }, { description: 'Box around focused element.', isFlat: true }), 'cell': PD.Group({}), // Show selection-box if available and cell otherwise. 'auto': PD.Group({ radius: PD.Numeric(5, { min: 0, max: 50, step: 0.5 }, { description: 'Radius in \u212B within which the volume is shown.' }), selectionDetailLevel: PD.Select(Math.min(6, info.header.availablePrecisions.length - 1), info.header.availablePrecisions.map(function (p, i) { return [i, i + 1 + " [ " + (Math.pow(p.maxVoxels, 1 / 3) | 0) + "^3 cells ]"]; }), { label: 'Selection Detail', description: 'Determines the maximum number of voxels. Depending on the size of the volume options are in the range from 0 (0.52M voxels) to 6 (25.17M voxels).' }), isSelection: PD.Boolean(false, { isHidden: true }), bottomLeft: PD.Vec3(box.min, {}, { isHidden: true }), topRight: PD.Vec3(box.max, {}, { isHidden: true }), }, { description: 'Box around focused element.', isFlat: true }) }, { options: VolumeStreaming.ViewTypeOptions, description: 'Controls what of the volume is displayed. "Off" hides the volume alltogether. "Bounded box" shows the volume inside the given box. "Around Interaction" shows the volume around the focused element/atom. "Whole Structure" shows the volume for the whole structure.' }), detailLevel: PD.Select(Math.min(3, info.header.availablePrecisions.length - 1), info.header.availablePrecisions.map(function (p, i) { return [i, i + 1 + " [ " + (Math.pow(p.maxVoxels, 1 / 3) | 0) + "^3 cells ]"]; }), { description: 'Determines the maximum number of voxels. Depending on the size of the volume options are in the range from 0 (0.52M voxels) to 6 (25.17M voxels).' }), channels: info.kind === 'em' ? PD.Group({ 'em': channelParam('EM', Color(0x638F8F), info.emDefaultContourLevel || Volume.IsoValue.relative(1), info.header.sampling[0].valuesInfo[0], channelParams['em']) }, { isFlat: true }) : PD.Group({ '2fo-fc': channelParam('2Fo-Fc', Color(0x3362B2), Volume.IsoValue.relative(1.5), info.header.sampling[0].valuesInfo[0], channelParams['2fo-fc']), 'fo-fc(+ve)': channelParam('Fo-Fc(+ve)', Color(0x33BB33), Volume.IsoValue.relative(3), info.header.sampling[0].valuesInfo[1], channelParams['fo-fc(+ve)']), 'fo-fc(-ve)': channelParam('Fo-Fc(-ve)', Color(0xBB3333), Volume.IsoValue.relative(-3), info.header.sampling[0].valuesInfo[1], channelParams['fo-fc(-ve)']), }, { isFlat: true }), }; } VolumeStreaming.createEntryParams = createEntryParams; VolumeStreaming.ViewTypeOptions = [['off', 'Off'], ['box', 'Bounded Box'], ['selection-box', 'Around Focus'], ['cell', 'Whole Structure'], ['auto', 'Auto']]; VolumeStreaming.ChannelTypeOptions = [['em', 'em'], ['2fo-fc', '2fo-fc'], ['fo-fc(+ve)', 'fo-fc(+ve)'], ['fo-fc(-ve)', 'fo-fc(-ve)']]; var Behavior = /** @class */ (function (_super) { __extends(Behavior, _super); function Behavior(plugin, data) { var _this = _super.call(this, plugin, {}) || this; _this.plugin = plugin; _this.data = data; _this.cache = LRUCache.create(25); _this.params = {}; _this.lastLoci = EmptyLoci; _this.ref = ''; _this.channels = {}; _this._invTransform = Mat4(); _this.infoMap = new Map(); _this.data.entries.forEach(function (info) { return _this.infoMap.set(info.dataId, info); }); return _this; } Object.defineProperty(Behavior.prototype, "info", { get: function () { return this.infoMap.get(this.params.entry.name); }, enumerable: false, configurable: true }); Behavior.prototype.queryData = function (box) { return __awaiter(this, void 0, void 0, function () { var url, a, b, detail, entry, urlAsset, asset, data, removed; return __generator(this, function (_a) { switch (_a.label) { case 0: url = urlCombine(this.data.serverUrl, this.info.kind + "/" + this.info.dataId.toLowerCase()); if (box) { a = box.min, b = box.max; url += "/box" + ("/" + a.map(function (v) { return Math.round(1000 * v) / 1000; }).join(',')) + ("/" + b.map(function (v) { return Math.round(1000 * v) / 1000; }).join(',')); } else { url += "/cell"; } detail = this.params.entry.params.detailLevel; if (this.params.entry.params.view.name === 'auto' && this.params.entry.params.view.params.isSelection) { detail = this.params.entry.params.view.params.selectionDetailLevel; } url += "?detail=" + detail; entry = LRUCache.get(this.cache, url); if (entry) return [2 /*return*/, entry.data]; urlAsset = Asset.getUrlAsset(this.plugin.managers.asset, url); return [4 /*yield*/, this.plugin.runTask(this.plugin.managers.asset.resolve(urlAsset, 'binary'))]; case 1: asset = _a.sent(); return [4 /*yield*/, this.parseCif(asset.data)]; case 2: data = _a.sent(); if (!data) return [2 /*return*/]; removed = LRUCache.set(this.cache, url, { data: data, asset: asset }); if (removed) removed.asset.dispose(); return [2 /*return*/, data]; } }); }); }; Behavior.prototype.parseCif = function (data) { return __awaiter(this, void 0, void 0, function () { var parsed, ret, i, block, densityServerCif, volume; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, this.plugin.runTask(CIF.parseBinary(data))]; case 1: parsed = _a.sent(); if (parsed.isError) { this.plugin.log.error('VolumeStreaming, parsing CIF: ' + parsed.toString()); return [2 /*return*/]; } if (parsed.result.blocks.length < 2) { this.plugin.log.error('VolumeStreaming: Invalid data.'); return [2 /*return*/]; } ret = {}; i = 1; _a.label = 2; case 2: if (!(i < parsed.result.blocks.length)) return [3 /*break*/, 5]; block = parsed.result.blocks[i]; densityServerCif = CIF.schema.densityServer(block); return [4 /*yield*/, this.plugin.runTask(volumeFromDensityServerData(densityServerCif))]; case 3: volume = _a.sent(); ret[block.header] = volume; _a.label = 4; case 4: i++; return [3 /*break*/, 2]; case 5: return [2 /*return*/, ret]; } }); }); }; Behavior.prototype.updateSelectionBoxParams = function (box) { if (this.params.entry.params.view.name !== 'selection-box') return; var state = this.plugin.state.data; var newParams = __assign(__assign({}, this.params), { entry: { name: this.params.entry.name, params: __assign(__assign({}, this.params.entry.params), { view: { name: 'selection-box', params: { radius: this.params.entry.params.view.params.radius, bottomLeft: box.min, topRight: box.max } } }) } }); var update = state.build().to(this.ref).update(newParams); PluginCommands.State.Update(this.plugin, { state: state, tree: update, options: { doNotUpdateCurrent: true } }); }; Behavior.prototype.updateAutoParams = function (box, isSelection) { if (this.params.entry.params.view.name !== 'auto') return; var state = this.plugin.state.data; var newParams = __assign(__assign({}, this.params), { entry: { name: this.params.entry.name, params: __assign(__assign({}, this.params.entry.params), { view: { name: 'auto', params: { radius: this.params.entry.params.view.params.radius, selectionDetailLevel: this.params.entry.params.view.params.selectionDetailLevel, isSelection: isSelection, bottomLeft: (box === null || box === void 0 ? void 0 : box.min) || Vec3.zero(), topRight: (box === null || box === void 0 ? void 0 : box.max) || Vec3.zero() } } }) } }); var update = state.build().to(this.ref).update(newParams); PluginCommands.State.Update(this.plugin, { state: state, tree: update, options: { doNotUpdateCurrent: true } }); }; Behavior.prototype.getStructureRoot = function () { return this.plugin.state.data.select(StateSelection.Generators.byRef(this.ref).rootOfType(PluginStateObject.Molecule.Structure))[0]; }; Behavior.prototype.register = function (ref) { var _this = this; this.ref = ref; this.subscribeObservable(this.plugin.state.events.object.removed, function (o) { if (!PluginStateObject.Molecule.Structure.is(o.obj) || !StructureElement.Loci.is(_this.lastLoci)) return; if (_this.lastLoci.structure === o.obj.data) { _this.lastLoci = EmptyLoci; } }); this.subscribeObservable(this.plugin.state.events.object.updated, function (o) { if (!PluginStateObject.Molecule.Structure.is(o.oldObj) || !StructureElement.Loci.is(_this.lastLoci)) return; if (_this.lastLoci.structure === o.oldObj.data) { _this.lastLoci = EmptyLoci; } }); this.subscribeObservable(this.plugin.managers.structure.focus.behaviors.current, function (entry) { if (!_this.plugin.state.data.tree.children.has(_this.ref)) return; var loci = entry ? entry.loci : EmptyLoci; switch (_this.params.entry.params.view.name) { case 'auto': _this.updateAuto(loci); break; case 'selection-box': _this.updateSelectionBox(loci); break; default: _this.lastLoci = loci; break; } }); }; Behavior.prototype.unregister = function () { var entry = this.cache.entries.first; while (entry) { entry.value.data.asset.dispose(); entry = entry.next; } }; Behavior.prototype.getBoxFromLoci = function (loci) { var _a, _b, _c; if (Loci.isEmpty(loci) || isEmptyLoci(loci)) { return Box3D(); } var parent = this.plugin.helpers.substructureParent.get(loci.structure, true); if (!parent) return Box3D(); var root = this.getStructureRoot(); if (!root || ((_a = root.obj) === null || _a === void 0 ? void 0 : _a.data) !== ((_b = parent.obj) === null || _b === void 0 ? void 0 : _b.data)) return Box3D(); var transform = GlobalModelTransformInfo.get((_c = root.obj) === null || _c === void 0 ? void 0 : _c.data.models[0]); if (transform) Mat4.invert(this._invTransform, transform); var extendedLoci = StructureElement.Loci.extendToWholeResidues(loci); var box = StructureElement.Loci.getBoundary(extendedLoci, transform && !Number.isNaN(this._invTransform[0]) ? this._invTransform : void 0).box; if (StructureElement.Loci.size(extendedLoci) === 1) { Box3D.expand(box, box, Vec3.create(1, 1, 1)); } return box; }; Behavior.prototype.updateAuto = function (loci) { // if (Loci.areEqual(this.lastLoci, loci)) { // this.lastLoci = EmptyLoci; // this.updateSelectionBoxParams(Box3D.empty()); // return; // } this.lastLoci = loci; if (isEmptyLoci(loci)) { this.updateAutoParams(this.info.kind === 'x-ray' ? this.data.structure.boundary.box : void 0, false); return; } var box = this.getBoxFromLoci(loci); this.updateAutoParams(box, true); }; Behavior.prototype.updateSelectionBox = function (loci) { if (Loci.areEqual(this.lastLoci, loci)) { this.lastLoci = EmptyLoci; this.updateSelectionBoxParams(Box3D()); return; } this.lastLoci = loci; if (isEmptyLoci(loci)) { this.updateSelectionBoxParams(Box3D()); return; } var box = this.getBoxFromLoci(loci); this.updateSelectionBoxParams(box); }; Behavior.prototype.update = function (params) { return __awaiter(this, void 0, void 0, function () { var switchedToSelection, box, emptyData, r, r, data, _a, info; return __generator(this, function (_b) { switch (_b.label) { case 0: switchedToSelection = params.entry.params.view.name === 'selection-box' && this.params && this.params.entry && this.params.entry.params && this.params.entry.params.view && this.params.entry.params.view.name !== 'selection-box'; this.params = params; box = void 0, emptyData = false; switch (params.entry.params.view.name) { case 'off': emptyData = true; break; case 'box': box = Box3D.create(params.entry.params.view.params.bottomLeft, params.entry.params.view.params.topRight); emptyData = Box3D.volume(box) < 0.0001; break; case 'selection-box': { if (switchedToSelection) { box = this.getBoxFromLoci(this.lastLoci) || Box3D(); } else { box = Box3D.create(Vec3.clone(params.entry.params.view.params.bottomLeft), Vec3.clone(params.entry.params.view.params.topRight)); } r = params.entry.params.view.params.radius; emptyData = Box3D.volume(box) < 0.0001; Box3D.expand(box, box, Vec3.create(r, r, r)); break; } case 'cell': box = this.info.kind === 'x-ray' ? this.data.structure.boundary.box : void 0; break; case 'auto': box = params.entry.params.view.params.isSelection || this.info.kind === 'x-ray' ? Box3D.create(Vec3.clone(params.entry.params.view.params.bottomLeft), Vec3.clone(params.entry.params.view.params.topRight)) : void 0; if (box) { emptyData = Box3D.volume(box) < 0.0001; if (params.entry.params.view.params.isSelection) { r = params.entry.params.view.params.radius; Box3D.expand(box, box, Vec3.create(r, r, r)); } } break; } if (!emptyData) return [3 /*break*/, 1]; _a = {}; return [3 /*break*/, 3]; case 1: return [4 /*yield*/, this.queryData(box)]; case 2: _a = _b.sent(); _b.label = 3; case 3: data = _a; if (!data) return [2 /*return*/, false]; info = params.entry.params.channels; if (this.info.kind === 'x-ray') { this.channels['2fo-fc'] = this.createChannel(data['2FO-FC'] || Volume.One, info['2fo-fc'], this.info.header.sampling[0].valuesInfo[0]); this.channels['fo-fc(+ve)'] = this.createChannel(data['FO-FC'] || Volume.One, info['fo-fc(+ve)'], this.info.header.sampling[0].valuesInfo[1]); this.channels['fo-fc(-ve)'] = this.createChannel(data['FO-FC'] || Volume.One, info['fo-fc(-ve)'], this.info.header.sampling[0].valuesInfo[1]); } else { this.channels['em'] = this.createChannel(data['EM'] || Volume.One, info['em'], this.info.header.sampling[0].valuesInfo[0]); } return [2 /*return*/, true]; } }); }); }; Behavior.prototype.createChannel = function (data, info, stats) { var i = info; return { data: data, color: i.color, wireframe: i.wireframe, opacity: i.opacity, isoValue: i.isoValue.kind === 'relative' ? i.isoValue : Volume.IsoValue.toRelative(i.isoValue, stats) }; }; Behavior.prototype.getDescription = function () { if (this.params.entry.params.view.name === 'selection-box') return 'Selection'; if (this.params.entry.params.view.name === 'box') return 'Static Box'; if (this.params.entry.params.view.name === 'cell') return 'Cell'; return ''; }; return Behavior; }(PluginBehavior.WithSubscribers)); VolumeStreaming.Behavior = Behavior; })(VolumeStreaming || (VolumeStreaming = {})); //# sourceMappingURL=behavior.js.map