molstar
Version:
A comprehensive macromolecular library.
436 lines • 27.9 kB
JavaScript
"use strict";
/**
* 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>
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.VolumeStreaming = void 0;
var tslib_1 = require("tslib");
var param_definition_1 = require("../../../../mol-util/param-definition");
var objects_1 = require("../../../../mol-plugin-state/objects");
var volume_1 = require("../../../../mol-model/volume");
var geometry_1 = require("../../../../mol-math/geometry");
var linear_algebra_1 = require("../../../../mol-math/linear-algebra");
var color_1 = require("../../../../mol-util/color");
var behavior_1 = require("../../behavior");
var lru_cache_1 = require("../../../../mol-util/lru-cache");
var url_1 = require("../../../../mol-util/url");
var cif_1 = require("../../../../mol-io/reader/cif");
var density_server_1 = require("../../../../mol-model-formats/volume/density-server");
var commands_1 = require("../../../commands");
var mol_state_1 = require("../../../../mol-state");
var structure_1 = require("../../../../mol-model/structure");
var loci_1 = require("../../../../mol-model/loci");
var assets_1 = require("../../../../mol-util/assets");
var global_transform_1 = require("../../../../mol-model/structure/model/properties/global-transform");
var VolumeStreaming = /** @class */ (function (_super) {
(0, tslib_1.__extends)(VolumeStreaming, _super);
function VolumeStreaming() {
return _super !== null && _super.apply(this, arguments) || this;
}
return VolumeStreaming;
}(objects_1.PluginStateObject.CreateBehavior({ name: 'Volume Streaming' })));
exports.VolumeStreaming = 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 param_definition_1.ParamDefinition.Group({
isoValue: volume_1.Volume.createIsoValueParam((_a = defaults.isoValue) !== null && _a !== void 0 ? _a : defaultValue, stats),
color: param_definition_1.ParamDefinition.Color((_b = defaults.color) !== null && _b !== void 0 ? _b : color),
wireframe: param_definition_1.ParamDefinition.Boolean((_c = defaults.wireframe) !== null && _c !== void 0 ? _c : false),
opacity: param_definition_1.ParamDefinition.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: param_definition_1.ParamDefinition.Mapped(defaultKey, names, function (name) { return param_definition_1.ParamDefinition.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_1.Volume.IsoValue.relative(0) };
var box = (structure && structure.boundary.box) || (0, geometry_1.Box3D)();
return {
view: param_definition_1.ParamDefinition.MappedStatic(defaultView || (info.kind === 'em' ? 'cell' : 'selection-box'), {
'off': param_definition_1.ParamDefinition.Group({}),
'box': param_definition_1.ParamDefinition.Group({
bottomLeft: param_definition_1.ParamDefinition.Vec3(box.min),
topRight: param_definition_1.ParamDefinition.Vec3(box.max),
}, { description: 'Static box defined by cartesian coords.', isFlat: true }),
'selection-box': param_definition_1.ParamDefinition.Group({
radius: param_definition_1.ParamDefinition.Numeric(5, { min: 0, max: 50, step: 0.5 }, { description: 'Radius in \u212B within which the volume is shown.' }),
bottomLeft: param_definition_1.ParamDefinition.Vec3(linear_algebra_1.Vec3.create(0, 0, 0), {}, { isHidden: true }),
topRight: param_definition_1.ParamDefinition.Vec3(linear_algebra_1.Vec3.create(0, 0, 0), {}, { isHidden: true }),
}, { description: 'Box around focused element.', isFlat: true }),
'cell': param_definition_1.ParamDefinition.Group({}),
// Show selection-box if available and cell otherwise.
'auto': param_definition_1.ParamDefinition.Group({
radius: param_definition_1.ParamDefinition.Numeric(5, { min: 0, max: 50, step: 0.5 }, { description: 'Radius in \u212B within which the volume is shown.' }),
selectionDetailLevel: param_definition_1.ParamDefinition.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: param_definition_1.ParamDefinition.Boolean(false, { isHidden: true }),
bottomLeft: param_definition_1.ParamDefinition.Vec3(box.min, {}, { isHidden: true }),
topRight: param_definition_1.ParamDefinition.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: param_definition_1.ParamDefinition.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'
? param_definition_1.ParamDefinition.Group({
'em': channelParam('EM', (0, color_1.Color)(0x638F8F), info.emDefaultContourLevel || volume_1.Volume.IsoValue.relative(1), info.header.sampling[0].valuesInfo[0], channelParams['em'])
}, { isFlat: true })
: param_definition_1.ParamDefinition.Group({
'2fo-fc': channelParam('2Fo-Fc', (0, color_1.Color)(0x3362B2), volume_1.Volume.IsoValue.relative(1.5), info.header.sampling[0].valuesInfo[0], channelParams['2fo-fc']),
'fo-fc(+ve)': channelParam('Fo-Fc(+ve)', (0, color_1.Color)(0x33BB33), volume_1.Volume.IsoValue.relative(3), info.header.sampling[0].valuesInfo[1], channelParams['fo-fc(+ve)']),
'fo-fc(-ve)': channelParam('Fo-Fc(-ve)', (0, color_1.Color)(0xBB3333), volume_1.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) {
(0, tslib_1.__extends)(Behavior, _super);
function Behavior(plugin, data) {
var _this = _super.call(this, plugin, {}) || this;
_this.plugin = plugin;
_this.data = data;
_this.cache = lru_cache_1.LRUCache.create(25);
_this.params = {};
_this.lastLoci = loci_1.EmptyLoci;
_this.ref = '';
_this.channels = {};
_this._invTransform = (0, linear_algebra_1.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 (0, tslib_1.__awaiter)(this, void 0, void 0, function () {
var url, a, b, detail, entry, urlAsset, asset, data, removed;
return (0, tslib_1.__generator)(this, function (_a) {
switch (_a.label) {
case 0:
url = (0, url_1.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 = lru_cache_1.LRUCache.get(this.cache, url);
if (entry)
return [2 /*return*/, entry.data];
urlAsset = assets_1.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 = lru_cache_1.LRUCache.set(this.cache, url, { data: data, asset: asset });
if (removed)
removed.asset.dispose();
return [2 /*return*/, data];
}
});
});
};
Behavior.prototype.parseCif = function (data) {
return (0, tslib_1.__awaiter)(this, void 0, void 0, function () {
var parsed, ret, i, block, densityServerCif, volume;
return (0, tslib_1.__generator)(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.plugin.runTask(cif_1.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_1.CIF.schema.densityServer(block);
return [4 /*yield*/, this.plugin.runTask((0, density_server_1.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 = (0, tslib_1.__assign)((0, tslib_1.__assign)({}, this.params), { entry: {
name: this.params.entry.name,
params: (0, tslib_1.__assign)((0, tslib_1.__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);
commands_1.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 = (0, tslib_1.__assign)((0, tslib_1.__assign)({}, this.params), { entry: {
name: this.params.entry.name,
params: (0, tslib_1.__assign)((0, tslib_1.__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) || linear_algebra_1.Vec3.zero(),
topRight: (box === null || box === void 0 ? void 0 : box.max) || linear_algebra_1.Vec3.zero()
}
} })
} });
var update = state.build().to(this.ref).update(newParams);
commands_1.PluginCommands.State.Update(this.plugin, { state: state, tree: update, options: { doNotUpdateCurrent: true } });
};
Behavior.prototype.getStructureRoot = function () {
return this.plugin.state.data.select(mol_state_1.StateSelection.Generators.byRef(this.ref).rootOfType(objects_1.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 (!objects_1.PluginStateObject.Molecule.Structure.is(o.obj) || !structure_1.StructureElement.Loci.is(_this.lastLoci))
return;
if (_this.lastLoci.structure === o.obj.data) {
_this.lastLoci = loci_1.EmptyLoci;
}
});
this.subscribeObservable(this.plugin.state.events.object.updated, function (o) {
if (!objects_1.PluginStateObject.Molecule.Structure.is(o.oldObj) || !structure_1.StructureElement.Loci.is(_this.lastLoci))
return;
if (_this.lastLoci.structure === o.oldObj.data) {
_this.lastLoci = loci_1.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 : loci_1.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_1.Loci.isEmpty(loci) || (0, loci_1.isEmptyLoci)(loci)) {
return (0, geometry_1.Box3D)();
}
var parent = this.plugin.helpers.substructureParent.get(loci.structure, true);
if (!parent)
return (0, geometry_1.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 (0, geometry_1.Box3D)();
var transform = global_transform_1.GlobalModelTransformInfo.get((_c = root.obj) === null || _c === void 0 ? void 0 : _c.data.models[0]);
if (transform)
linear_algebra_1.Mat4.invert(this._invTransform, transform);
var extendedLoci = structure_1.StructureElement.Loci.extendToWholeResidues(loci);
var box = structure_1.StructureElement.Loci.getBoundary(extendedLoci, transform && !Number.isNaN(this._invTransform[0]) ? this._invTransform : void 0).box;
if (structure_1.StructureElement.Loci.size(extendedLoci) === 1) {
geometry_1.Box3D.expand(box, box, linear_algebra_1.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 ((0, loci_1.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_1.Loci.areEqual(this.lastLoci, loci)) {
this.lastLoci = loci_1.EmptyLoci;
this.updateSelectionBoxParams((0, geometry_1.Box3D)());
return;
}
this.lastLoci = loci;
if ((0, loci_1.isEmptyLoci)(loci)) {
this.updateSelectionBoxParams((0, geometry_1.Box3D)());
return;
}
var box = this.getBoxFromLoci(loci);
this.updateSelectionBoxParams(box);
};
Behavior.prototype.update = function (params) {
return (0, tslib_1.__awaiter)(this, void 0, void 0, function () {
var switchedToSelection, box, emptyData, r, r, data, _a, info;
return (0, tslib_1.__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 = geometry_1.Box3D.create(params.entry.params.view.params.bottomLeft, params.entry.params.view.params.topRight);
emptyData = geometry_1.Box3D.volume(box) < 0.0001;
break;
case 'selection-box': {
if (switchedToSelection) {
box = this.getBoxFromLoci(this.lastLoci) || (0, geometry_1.Box3D)();
}
else {
box = geometry_1.Box3D.create(linear_algebra_1.Vec3.clone(params.entry.params.view.params.bottomLeft), linear_algebra_1.Vec3.clone(params.entry.params.view.params.topRight));
}
r = params.entry.params.view.params.radius;
emptyData = geometry_1.Box3D.volume(box) < 0.0001;
geometry_1.Box3D.expand(box, box, linear_algebra_1.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'
? geometry_1.Box3D.create(linear_algebra_1.Vec3.clone(params.entry.params.view.params.bottomLeft), linear_algebra_1.Vec3.clone(params.entry.params.view.params.topRight))
: void 0;
if (box) {
emptyData = geometry_1.Box3D.volume(box) < 0.0001;
if (params.entry.params.view.params.isSelection) {
r = params.entry.params.view.params.radius;
geometry_1.Box3D.expand(box, box, linear_algebra_1.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_1.Volume.One, info['2fo-fc'], this.info.header.sampling[0].valuesInfo[0]);
this.channels['fo-fc(+ve)'] = this.createChannel(data['FO-FC'] || volume_1.Volume.One, info['fo-fc(+ve)'], this.info.header.sampling[0].valuesInfo[1]);
this.channels['fo-fc(-ve)'] = this.createChannel(data['FO-FC'] || volume_1.Volume.One, info['fo-fc(-ve)'], this.info.header.sampling[0].valuesInfo[1]);
}
else {
this.channels['em'] = this.createChannel(data['EM'] || volume_1.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_1.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;
}(behavior_1.PluginBehavior.WithSubscribers));
VolumeStreaming.Behavior = Behavior;
})(VolumeStreaming = exports.VolumeStreaming || (exports.VolumeStreaming = {}));
exports.VolumeStreaming = VolumeStreaming;
//# sourceMappingURL=behavior.js.map