molstar
Version:
A comprehensive macromolecular library.
376 lines (375 loc) • 27.9 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SuperpositionControls = exports.StructureSuperpositionParams = exports.StructureSuperpositionControls = void 0;
var tslib_1 = require("tslib");
var jsx_runtime_1 = require("react/jsx-runtime");
var linear_algebra_1 = require("../../mol-math/linear-algebra");
var sifts_mapping_1 = require("../../mol-model-props/sequence/sifts-mapping");
var structure_1 = require("../../mol-model/structure");
var superposition_1 = require("../../mol-model/structure/structure/util/superposition");
var superposition_sifts_mapping_1 = require("../../mol-model/structure/structure/util/superposition-sifts-mapping");
var structure_selection_query_1 = require("../../mol-plugin-state/helpers/structure-selection-query");
var objects_1 = require("../../mol-plugin-state/objects");
var transforms_1 = require("../../mol-plugin-state/transforms");
var commands_1 = require("../../mol-plugin/commands");
var mol_state_1 = require("../../mol-state");
var label_1 = require("../../mol-theme/label");
var param_definition_1 = require("../../mol-util/param-definition");
var string_1 = require("../../mol-util/string");
var base_1 = require("../base");
var common_1 = require("../controls/common");
var icons_1 = require("../controls/icons");
var parameters_1 = require("../controls/parameters");
var selection_1 = require("./selection");
var StructureSuperpositionControls = /** @class */ (function (_super) {
tslib_1.__extends(StructureSuperpositionControls, _super);
function StructureSuperpositionControls() {
return _super !== null && _super.apply(this, arguments) || this;
}
StructureSuperpositionControls.prototype.defaultState = function () {
return {
isCollapsed: false,
header: 'Superposition',
brand: { accent: 'gray', svg: icons_1.SuperpositionSvg },
isHidden: true
};
};
StructureSuperpositionControls.prototype.componentDidMount = function () {
var _this = this;
this.subscribe(this.plugin.managers.structure.hierarchy.behaviors.selection, function (sel) {
_this.setState({ isHidden: sel.structures.length < 2 });
});
};
StructureSuperpositionControls.prototype.renderControls = function () {
return (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: (0, jsx_runtime_1.jsx)(SuperpositionControls, {}) });
};
return StructureSuperpositionControls;
}(base_1.CollapsableControls));
exports.StructureSuperpositionControls = StructureSuperpositionControls;
exports.StructureSuperpositionParams = {
alignSequences: param_definition_1.ParamDefinition.Boolean(true, { isEssential: true, description: 'For Chain-based 3D superposition, perform a sequence alignment and use the aligned residue pairs to guide the 3D superposition.' }),
traceOnly: param_definition_1.ParamDefinition.Boolean(true, { description: 'For Chain- and Uniprot-based 3D superposition, base superposition only on CA (and equivalent) atoms.' })
};
var DefaultStructureSuperpositionOptions = param_definition_1.ParamDefinition.getDefaultValues(exports.StructureSuperpositionParams);
var SuperpositionTag = 'SuperpositionTransform';
;
;
var SuperpositionControls = /** @class */ (function (_super) {
tslib_1.__extends(SuperpositionControls, _super);
function SuperpositionControls() {
var _this = _super !== null && _super.apply(this, arguments) || this;
_this.state = {
isBusy: false,
canUseDb: false,
action: undefined,
options: DefaultStructureSuperpositionOptions
};
_this.superposeChains = function () { return tslib_1.__awaiter(_this, void 0, void 0, function () {
var query, entries, locis, pivot, coordinateSystem, transforms, eA, i, il, eB, _a, bTransform, rmsd, labelA, labelB;
var _this = this;
var _b, _c, _d;
return tslib_1.__generator(this, function (_e) {
switch (_e.label) {
case 0:
query = (this.state.options.traceOnly ? structure_selection_query_1.StructureSelectionQueries.trace : structure_selection_query_1.StructureSelectionQueries.polymer).query;
entries = this.chainEntries;
locis = entries.map(function (e) {
var s = structure_1.StructureElement.Loci.toStructure(e.loci);
var loci = structure_1.StructureSelection.toLociWithSourceUnits(query(new structure_1.QueryContext(s)));
return structure_1.StructureElement.Loci.remap(loci, _this.getRootStructure(e.loci.structure));
});
pivot = this.plugin.managers.structure.hierarchy.findStructure((_b = locis[0]) === null || _b === void 0 ? void 0 : _b.structure);
coordinateSystem = (_d = (_c = pivot === null || pivot === void 0 ? void 0 : pivot.transform) === null || _c === void 0 ? void 0 : _c.cell.obj) === null || _d === void 0 ? void 0 : _d.data.coordinateSystem;
transforms = this.state.options.alignSequences
? (0, superposition_1.alignAndSuperpose)(locis)
: (0, superposition_1.superpose)(locis);
eA = entries[0];
i = 1, il = locis.length;
_e.label = 1;
case 1:
if (!(i < il)) return [3 /*break*/, 4];
eB = entries[i];
_a = transforms[i - 1], bTransform = _a.bTransform, rmsd = _a.rmsd;
return [4 /*yield*/, this.transform(eB.cell, bTransform, coordinateSystem)];
case 2:
_e.sent();
labelA = (0, string_1.stripTags)(eA.label);
labelB = (0, string_1.stripTags)(eB.label);
this.plugin.log.info("Superposed [".concat(labelA, "] and [").concat(labelB, "] with RMSD ").concat(rmsd.toFixed(2), "."));
_e.label = 3;
case 3:
++i;
return [3 /*break*/, 1];
case 4: return [4 /*yield*/, this.cameraReset()];
case 5:
_e.sent();
return [2 /*return*/];
}
});
}); };
_this.superposeAtoms = function () { return tslib_1.__awaiter(_this, void 0, void 0, function () {
var entries, atomLocis, transforms, pivot, coordinateSystem, eA, i, il, eB, _a, bTransform, rmsd, labelA, labelB, count;
var _this = this;
var _b, _c, _d;
return tslib_1.__generator(this, function (_e) {
switch (_e.label) {
case 0:
entries = this.atomEntries;
atomLocis = entries.map(function (e) {
return structure_1.StructureElement.Loci.remap(e.loci, _this.getRootStructure(e.loci.structure));
});
transforms = (0, superposition_1.superpose)(atomLocis);
pivot = this.plugin.managers.structure.hierarchy.findStructure((_b = atomLocis[0]) === null || _b === void 0 ? void 0 : _b.structure);
coordinateSystem = (_d = (_c = pivot === null || pivot === void 0 ? void 0 : pivot.transform) === null || _c === void 0 ? void 0 : _c.cell.obj) === null || _d === void 0 ? void 0 : _d.data.coordinateSystem;
eA = entries[0];
i = 1, il = atomLocis.length;
_e.label = 1;
case 1:
if (!(i < il)) return [3 /*break*/, 4];
eB = entries[i];
_a = transforms[i - 1], bTransform = _a.bTransform, rmsd = _a.rmsd;
return [4 /*yield*/, this.transform(eB.cell, bTransform, coordinateSystem)];
case 2:
_e.sent();
labelA = (0, string_1.stripTags)(eA.label);
labelB = (0, string_1.stripTags)(eB.label);
count = entries[i].atoms.length;
this.plugin.log.info("Superposed ".concat(count, " ").concat(count === 1 ? 'atom' : 'atoms', " of [").concat(labelA, "] and [").concat(labelB, "] with RMSD ").concat(rmsd.toFixed(2), "."));
_e.label = 3;
case 3:
++i;
return [3 /*break*/, 1];
case 4: return [4 /*yield*/, this.cameraReset()];
case 5:
_e.sent();
return [2 /*return*/];
}
});
}); };
_this.superposeDb = function () { return tslib_1.__awaiter(_this, void 0, void 0, function () {
var input, traceOnly, structures, _a, entries, failedPairs, zeroOverlapPairs, coordinateSystem, rmsd, _i, entries_1, xform, formatPairs;
var _b, _c, _d;
return tslib_1.__generator(this, function (_e) {
switch (_e.label) {
case 0:
input = this.plugin.managers.structure.hierarchy.behaviors.selection.value.structures;
traceOnly = this.state.options.traceOnly;
structures = input.map(function (s) { var _a; return (_a = s.cell.obj) === null || _a === void 0 ? void 0 : _a.data; });
_a = (0, superposition_sifts_mapping_1.alignAndSuperposeWithSIFTSMapping)(structures, { traceOnly: traceOnly }), entries = _a.entries, failedPairs = _a.failedPairs, zeroOverlapPairs = _a.zeroOverlapPairs;
coordinateSystem = (_d = (_c = (_b = input[0]) === null || _b === void 0 ? void 0 : _b.transform) === null || _c === void 0 ? void 0 : _c.cell.obj) === null || _d === void 0 ? void 0 : _d.data.coordinateSystem;
rmsd = 0;
_i = 0, entries_1 = entries;
_e.label = 1;
case 1:
if (!(_i < entries_1.length)) return [3 /*break*/, 4];
xform = entries_1[_i];
return [4 /*yield*/, this.transform(input[xform.other].cell, xform.transform.bTransform, coordinateSystem)];
case 2:
_e.sent();
rmsd += xform.transform.rmsd;
_e.label = 3;
case 3:
_i++;
return [3 /*break*/, 1];
case 4:
rmsd /= Math.max(entries.length - 1, 1);
formatPairs = function (pairs) {
return "[".concat(pairs.map(function (_a) {
var i = _a[0], j = _a[1];
return "(".concat(structures[i].models[0].entryId, ", ").concat(structures[j].models[0].entryId, ")");
}).join(', '), "]");
};
if (zeroOverlapPairs.length) {
this.plugin.log.warn("Superposition: No UNIPROT mapping overlap between structures ".concat(formatPairs(zeroOverlapPairs), "."));
}
if (failedPairs.length) {
this.plugin.log.error("Superposition: Failed to superpose structures ".concat(formatPairs(failedPairs), "."));
}
if (!entries.length) return [3 /*break*/, 6];
this.plugin.log.info("Superposed ".concat(entries.length + 1, " structures with avg. RMSD ").concat(rmsd.toFixed(2), " \u00C5."));
return [4 /*yield*/, this.cameraReset()];
case 5:
_e.sent();
_e.label = 6;
case 6: return [2 /*return*/];
}
});
}); };
_this.toggleByChains = function () { return _this.setState({ action: _this.state.action === 'byChains' ? void 0 : 'byChains' }); };
_this.toggleByAtoms = function () { return _this.setState({ action: _this.state.action === 'byAtoms' ? void 0 : 'byAtoms' }); };
_this.toggleOptions = function () { return _this.setState({ action: _this.state.action === 'options' ? void 0 : 'options' }); };
_this.setOptions = function (values) {
_this.setState({ options: values });
};
return _this;
}
SuperpositionControls.prototype.componentDidMount = function () {
var _this = this;
this.subscribe(this.selection.events.changed, function () {
_this.forceUpdate();
});
this.subscribe(this.selection.events.additionsHistoryUpdated, function () {
_this.forceUpdate();
});
this.subscribe(this.plugin.behaviors.state.isBusy, function (v) {
_this.setState({ isBusy: v });
});
this.subscribe(this.plugin.managers.structure.hierarchy.behaviors.selection, function (sel) {
_this.setState({ canUseDb: sel.structures.every(function (s) { var _a; return !!((_a = s.cell.obj) === null || _a === void 0 ? void 0 : _a.data) && s.cell.obj.data.models.some(function (m) { return sifts_mapping_1.SIFTSMapping.Provider.isApplicable(m); }); }) });
});
};
Object.defineProperty(SuperpositionControls.prototype, "selection", {
get: function () {
return this.plugin.managers.structure.selection;
},
enumerable: false,
configurable: true
});
SuperpositionControls.prototype.transform = function (s, matrix, coordinateSystem) {
return tslib_1.__awaiter(this, void 0, void 0, function () {
var r, o, transform, params, b;
return tslib_1.__generator(this, function (_a) {
switch (_a.label) {
case 0:
r = mol_state_1.StateObjectRef.resolveAndCheck(this.plugin.state.data, s);
if (!r)
return [2 /*return*/];
o = this.plugin.state.data.selectQ(function (q) { return q.byRef(r.transform.ref).subtree().withTransformer(transforms_1.StateTransforms.Model.TransformStructureConformation); })[0];
transform = coordinateSystem && !linear_algebra_1.Mat4.isIdentity(coordinateSystem.matrix)
? linear_algebra_1.Mat4.mul((0, linear_algebra_1.Mat4)(), coordinateSystem.matrix, matrix)
: matrix;
params = {
transform: {
name: 'matrix',
params: { data: transform, transpose: false }
}
};
b = o
? this.plugin.state.data.build().to(o).update(params)
: this.plugin.state.data.build().to(s)
.insert(transforms_1.StateTransforms.Model.TransformStructureConformation, params, { tags: SuperpositionTag });
return [4 /*yield*/, this.plugin.runTask(this.plugin.state.data.updateTree(b))];
case 1:
_a.sent();
return [2 /*return*/];
}
});
});
};
SuperpositionControls.prototype.getRootStructure = function (s) {
var _a;
var parent = this.plugin.helpers.substructureParent.get(s);
return (_a = this.plugin.state.data.selectQ(function (q) { return q.byValue(parent).rootOfType(objects_1.PluginStateObject.Molecule.Structure); })[0].obj) === null || _a === void 0 ? void 0 : _a.data;
};
SuperpositionControls.prototype.cameraReset = function () {
return tslib_1.__awaiter(this, void 0, void 0, function () {
return tslib_1.__generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, new Promise(function (res) { return requestAnimationFrame(res); })];
case 1:
_a.sent();
commands_1.PluginCommands.Camera.Reset(this.plugin);
return [2 /*return*/];
}
});
});
};
SuperpositionControls.prototype.highlight = function (loci) {
this.plugin.managers.interactivity.lociHighlights.highlightOnly({ loci: loci }, false);
};
SuperpositionControls.prototype.moveHistory = function (e, direction) {
this.plugin.managers.structure.selection.modifyHistory(e, direction, void 0, true);
};
SuperpositionControls.prototype.focusLoci = function (loci) {
this.plugin.managers.camera.focusLoci(loci);
};
SuperpositionControls.prototype.lociEntry = function (e, idx) {
var _this = this;
return (0, jsx_runtime_1.jsx)("div", tslib_1.__assign({ className: 'msp-flex-row' }, { children: (0, jsx_runtime_1.jsx)(common_1.Button, tslib_1.__assign({ noOverflow: true, title: 'Click to focus. Hover to highlight.', onClick: function () { return _this.focusLoci(e.loci); }, style: { width: 'auto', textAlign: 'left' }, onMouseEnter: function () { return _this.highlight(e.loci); }, onMouseLeave: function () { return _this.plugin.managers.interactivity.lociHighlights.clearHighlights(); } }, { children: (0, jsx_runtime_1.jsx)("span", { dangerouslySetInnerHTML: { __html: e.label } }) })) }), idx);
};
SuperpositionControls.prototype.historyEntry = function (e, idx) {
var _this = this;
var history = this.plugin.managers.structure.selection.additionsHistory;
return (0, jsx_runtime_1.jsxs)("div", tslib_1.__assign({ className: 'msp-flex-row' }, { children: [(0, jsx_runtime_1.jsxs)(common_1.Button, tslib_1.__assign({ noOverflow: true, title: 'Click to focus. Hover to highlight.', onClick: function () { return _this.focusLoci(e.loci); }, style: { width: 'auto', textAlign: 'left' }, onMouseEnter: function () { return _this.highlight(e.loci); }, onMouseLeave: function () { return _this.plugin.managers.interactivity.lociHighlights.clearHighlights(); } }, { children: [idx, ". ", (0, jsx_runtime_1.jsx)("span", { dangerouslySetInnerHTML: { __html: e.label } })] })), history.length > 1 && (0, jsx_runtime_1.jsx)(common_1.IconButton, { svg: icons_1.ArrowUpwardSvg, small: true, className: 'msp-form-control', onClick: function () { return _this.moveHistory(e, 'up'); }, flex: '20px', title: 'Move up' }), history.length > 1 && (0, jsx_runtime_1.jsx)(common_1.IconButton, { svg: icons_1.ArrowDownwardSvg, small: true, className: 'msp-form-control', onClick: function () { return _this.moveHistory(e, 'down'); }, flex: '20px', title: 'Move down' }), (0, jsx_runtime_1.jsx)(common_1.IconButton, { svg: icons_1.DeleteOutlinedSvg, small: true, className: 'msp-form-control', onClick: function () { return _this.plugin.managers.structure.selection.modifyHistory(e, 'remove'); }, flex: true, title: 'Remove' })] }), e.id);
};
SuperpositionControls.prototype.atomsLociEntry = function (e, idx) {
var _this = this;
return (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("div", tslib_1.__assign({ className: 'msp-control-group-header' }, { children: (0, jsx_runtime_1.jsx)("div", tslib_1.__assign({ className: 'msp-no-overflow', title: e.label }, { children: e.label })) })), (0, jsx_runtime_1.jsx)("div", tslib_1.__assign({ className: 'msp-control-offset' }, { children: e.atoms.map(function (h, i) { return _this.historyEntry(h, i); }) }))] }, idx);
};
Object.defineProperty(SuperpositionControls.prototype, "chainEntries", {
get: function () {
var _this = this;
var location = structure_1.StructureElement.Location.create();
var entries = [];
this.plugin.managers.structure.selection.entries.forEach(function (_a, ref) {
var selection = _a.selection;
var cell = mol_state_1.StateObjectRef.resolveAndCheck(_this.plugin.state.data, ref);
if (!cell || structure_1.StructureElement.Loci.isEmpty(selection))
return;
// only single polymer chain selections
var l = structure_1.StructureElement.Loci.getFirstLocation(selection, location);
if (selection.elements.length > 1 || structure_1.StructureProperties.entity.type(l) !== 'polymer')
return;
var stats = structure_1.StructureElement.Stats.ofLoci(selection);
var counts = (0, label_1.structureElementStatsLabel)(stats, { countsOnly: true });
var chain = (0, label_1.elementLabel)(l, { reverse: true, granularity: 'chain' }).split('|');
var label = "".concat(counts, " | ").concat(chain[0], " | ").concat(chain[chain.length - 1]);
entries.push({ loci: selection, label: label, cell: cell });
});
return entries;
},
enumerable: false,
configurable: true
});
Object.defineProperty(SuperpositionControls.prototype, "atomEntries", {
get: function () {
var _this = this;
var structureEntries = new Map();
var history = this.plugin.managers.structure.selection.additionsHistory;
for (var i = 0, il = history.length; i < il; ++i) {
var e = history[i];
if (structure_1.StructureElement.Loci.size(e.loci) !== 1)
continue;
var k = e.loci.structure;
if (structureEntries.has(k))
structureEntries.get(k).push(e);
else
structureEntries.set(k, [e]);
}
var entries = [];
structureEntries.forEach(function (atoms, structure) {
var cell = _this.plugin.helpers.substructureParent.get(structure);
var elements = [];
for (var i = 0, il = atoms.length; i < il; ++i) {
// note, we don't do loci union here to keep order of selected atoms
// for atom pairing during superposition
elements.push(atoms[i].loci.elements[0]);
}
var loci = structure_1.StructureElement.Loci(atoms[0].loci.structure, elements);
var label = loci.structure.label.split(' | ')[0];
entries.push({ loci: loci, label: label, cell: cell, atoms: atoms });
});
return entries;
},
enumerable: false,
configurable: true
});
SuperpositionControls.prototype.addByChains = function () {
var _this = this;
var entries = this.chainEntries;
return (0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [entries.length > 0 && (0, jsx_runtime_1.jsx)("div", tslib_1.__assign({ className: 'msp-control-offset' }, { children: entries.map(function (e, i) { return _this.lociEntry(e, i); }) })), entries.length < 2 && (0, jsx_runtime_1.jsx)("div", tslib_1.__assign({ className: 'msp-control-offset msp-help-text' }, { children: (0, jsx_runtime_1.jsxs)("div", tslib_1.__assign({ className: 'msp-help-description' }, { children: [(0, jsx_runtime_1.jsx)(icons_1.Icon, { svg: icons_1.HelpOutlineSvg, inline: true }), "Add 2 or more selections (toggle ", (0, jsx_runtime_1.jsx)(selection_1.ToggleSelectionModeButton, { inline: true }), " mode) from separate structures. Selections must be limited to single polymer chains or residues therein."] })) })), entries.length > 1 && (0, jsx_runtime_1.jsx)(common_1.Button, tslib_1.__assign({ title: 'Superpose structures by selected chains.', className: 'msp-btn-commit msp-btn-commit-on', onClick: this.superposeChains, style: { marginTop: '1px' } }, { children: "Superpose" }))] });
};
SuperpositionControls.prototype.addByAtoms = function () {
var _this = this;
var entries = this.atomEntries;
return (0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [entries.length > 0 && (0, jsx_runtime_1.jsx)("div", tslib_1.__assign({ className: 'msp-control-offset' }, { children: entries.map(function (e, i) { return _this.atomsLociEntry(e, i); }) })), entries.length < 2 && (0, jsx_runtime_1.jsx)("div", tslib_1.__assign({ className: 'msp-control-offset msp-help-text' }, { children: (0, jsx_runtime_1.jsxs)("div", tslib_1.__assign({ className: 'msp-help-description' }, { children: [(0, jsx_runtime_1.jsx)(icons_1.Icon, { svg: icons_1.HelpOutlineSvg, inline: true }), "Add 1 or more selections (toggle ", (0, jsx_runtime_1.jsx)(selection_1.ToggleSelectionModeButton, { inline: true }), " mode) from separate structures. Selections must be limited to single atoms."] })) })), entries.length > 1 && (0, jsx_runtime_1.jsx)(common_1.Button, tslib_1.__assign({ title: 'Superpose structures by selected atoms.', className: 'msp-btn-commit msp-btn-commit-on', onClick: this.superposeAtoms, style: { marginTop: '1px' } }, { children: "Superpose" }))] });
};
SuperpositionControls.prototype.superposeByDbMapping = function () {
return (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: (0, jsx_runtime_1.jsx)(common_1.Button, tslib_1.__assign({ icon: icons_1.SuperposeChainsSvg, title: 'Superpose structures using intersection of residues from SIFTS UNIPROT mapping.', className: 'msp-btn msp-btn-block', onClick: this.superposeDb, style: { marginTop: '1px' }, disabled: this.state.isBusy }, { children: "Uniprot" })) });
};
SuperpositionControls.prototype.render = function () {
return (0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("div", tslib_1.__assign({ className: 'msp-flex-row' }, { children: [(0, jsx_runtime_1.jsx)(common_1.ToggleButton, { icon: icons_1.SuperposeChainsSvg, label: 'Chains', toggle: this.toggleByChains, isSelected: this.state.action === 'byChains', disabled: this.state.isBusy }), (0, jsx_runtime_1.jsx)(common_1.ToggleButton, { icon: icons_1.SuperposeAtomsSvg, label: 'Atoms', toggle: this.toggleByAtoms, isSelected: this.state.action === 'byAtoms', disabled: this.state.isBusy }), this.state.canUseDb && this.superposeByDbMapping(), (0, jsx_runtime_1.jsx)(common_1.ToggleButton, { icon: icons_1.TuneSvg, label: '', title: 'Options', toggle: this.toggleOptions, isSelected: this.state.action === 'options', disabled: this.state.isBusy, style: { flex: '0 0 40px', padding: 0 } })] })), this.state.action === 'byChains' && this.addByChains(), this.state.action === 'byAtoms' && this.addByAtoms(), this.state.action === 'options' && (0, jsx_runtime_1.jsx)("div", tslib_1.__assign({ className: 'msp-control-offset' }, { children: (0, jsx_runtime_1.jsx)(parameters_1.ParameterControls, { params: exports.StructureSuperpositionParams, values: this.state.options, onChangeValues: this.setOptions, isDisabled: this.state.isBusy }) }))] });
};
return SuperpositionControls;
}(base_1.PurePluginUIComponent));
exports.SuperpositionControls = SuperpositionControls;