@rcsb/rcsb-saguaro-3d
Version:
RCSB Molstar/Saguaro Web App
357 lines (356 loc) • 21.6 kB
JavaScript
"use strict";
/*
* Copyright (c) 2021 RCSB PDB and contributors, licensed under MIT, See LICENSE file for more info.
* @author Joan Segura Mora <joan.segura@rcsb.org>
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.AlignmentRepresentationPresetProvider = void 0;
const tslib_1 = require("tslib");
const representation_preset_1 = require("molstar/lib/mol-plugin-state/builder/structure/representation-preset");
const mol_state_1 = require("molstar/lib/mol-state");
const structure_1 = require("molstar/lib/mol-model/structure");
const builder_1 = require("molstar/lib/mol-script/language/builder");
const uniqid_1 = tslib_1.__importDefault(require("uniqid"));
const plddt_1 = require("molstar/lib/extensions/model-archive/quality-assessment/color/plddt");
const selection_1 = require("@rcsb/rcsb-molstar/build/src/viewer/helpers/selection");
const param_definition_1 = require("molstar/lib/mol-util/param-definition");
const loci_1 = require("molstar/lib/mol-model/loci");
const superposition_1 = require("molstar/lib/mol-model/structure/structure/util/superposition");
const linear_algebra_1 = require("molstar/lib/mol-math/linear-algebra");
const TagDelimiter_1 = require("@rcsb/rcsb-api-tools/lib/RcsbUtils/TagDelimiter");
const AlignmentMapper_1 = require("../../../../Utils/AlignmentMapper");
const compiler_1 = require("molstar/lib/mol-script/runtime/query/compiler");
var reprBuilder = representation_preset_1.StructureRepresentationPresetProvider.reprBuilder;
const mmcif_1 = require("molstar/lib/mol-model-formats/structure/mmcif");
const transform_1 = require("molstar/lib/mol-state/transform");
const model_1 = require("molstar/lib/mol-plugin-state/transforms/model");
var updateFocusRepr = representation_preset_1.StructureRepresentationPresetProvider.updateFocusRepr;
const FocusColoring_1 = require("./FocusTheme/FocusColoring");
const prop_1 = require("molstar/lib/extensions/model-archive/quality-assessment/prop");
let refData = undefined;
let refParams = undefined;
exports.AlignmentRepresentationPresetProvider = (0, representation_preset_1.StructureRepresentationPresetProvider)({
id: 'alignment-to-reference',
display: {
name: 'Alignment to Reference'
},
isApplicable: (structureRef, plugin) => true,
params: (structureRef, plugin) => ({
pdb: param_definition_1.ParamDefinition.Value(undefined),
targetAlignment: param_definition_1.ParamDefinition.Value(undefined),
transform: param_definition_1.ParamDefinition.Value(undefined)
}),
apply: (structureRef, params, plugin) => tslib_1.__awaiter(void 0, void 0, void 0, function* () {
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
const structureCell = mol_state_1.StateObjectRef.resolveAndCheck(plugin.state.data, structureRef);
if (!structureCell)
return {};
const structure = structureCell.obj.data;
const entryId = (_a = params.pdb) === null || _a === void 0 ? void 0 : _a.entryId;
const entityId = params.pdb && "entityId" in params.pdb ? (_b = params.pdb) === null || _b === void 0 ? void 0 : _b.entityId : undefined;
const instanceId = params.pdb && "instanceId" in params.pdb ? (_c = params.pdb) === null || _c === void 0 ? void 0 : _c.instanceId : undefined;
const l = structure_1.StructureElement.Location.create(structure);
let alignedEntityId;
let alignedAsymId;
let alignedOperatorName;
let alignedType;
const componentMap = {};
const representationMap = {};
for (const unit of structure.units) {
structure_1.StructureElement.Location.set(l, structure, unit, unit.elements[0]);
if (structure_1.StructureProperties.chain.label_entity_id(l) == entityId || structure_1.StructureProperties.chain.label_asym_id(l) == instanceId) {
alignedEntityId = structure_1.StructureProperties.chain.label_entity_id(l);
alignedAsymId = structure_1.StructureProperties.chain.label_asym_id(l);
alignedOperatorName = structure_1.StructureProperties.unit.operator_name(l);
alignedType = structure_1.StructureProperties.entity.type(l);
const alignedOperators = structure_1.StructureProperties.unit.pdbx_struct_oper_list_ids(l);
if (alignedOperators.length == 0)
alignedOperators.push("0");
if (alignedType != "polymer")
return {};
if (plugin.managers.structure.hierarchy.current.structures.length == 1) {
refParams = {
entryId: entryId,
labelAsymId: alignedAsymId,
operatorName: alignedOperatorName,
targetAlignment: params.targetAlignment
};
}
if (refParams && params.pdb && !params.transform) {
yield structuralAlignment(plugin, refParams, {
entryId: entryId,
labelAsymId: alignedAsymId,
operatorName: alignedOperatorName,
targetAlignment: params.targetAlignment
}, structure);
}
else if ((_d = params.transform) === null || _d === void 0 ? void 0 : _d[0].transform) {
yield matrixAlign(plugin, structureRef, (_e = params.transform) === null || _e === void 0 ? void 0 : _e[0].transform);
}
const comp = yield plugin.builders.structure.tryCreateComponentFromExpression(structureCell, builder_1.MolScriptBuilder.struct.generator.atomGroups({
'chain-test': builder_1.MolScriptBuilder.core.logic.and([
builder_1.MolScriptBuilder.core.rel.eq([builder_1.MolScriptBuilder.ammp('label_asym_id'), alignedAsymId]),
builder_1.MolScriptBuilder.core.rel.eq([builder_1.MolScriptBuilder.acp('operatorName'), alignedOperatorName])
])
}), (0, uniqid_1.default)(`${entryId}${TagDelimiter_1.TagDelimiter.entity}${alignedEntityId}${TagDelimiter_1.TagDelimiter.instance}${alignedAsymId}${TagDelimiter_1.TagDelimiter.entity}${alignedOperators.join(",")}`), {
label: `${entryId}${TagDelimiter_1.TagDelimiter.entity}${alignedEntityId}${TagDelimiter_1.TagDelimiter.instance}${alignedAsymId}${TagDelimiter_1.TagDelimiter.assembly}${alignedOperators.join(",")}${TagDelimiter_1.TagDelimiter.assembly}${alignedType}`
});
componentMap["aligned"] = comp;
//TODO This needs to be called after tryCreateComponentFromExpression
const { update, builder } = reprBuilder(plugin, {
ignoreHydrogens: true,
ignoreLight: false,
quality: "auto"
});
representationMap["aligned"] = builder.buildRepresentation(update, comp, {
color: structure.models.some(m => prop_1.QualityAssessment.isApplicable(m, 'pLDDT')) ? plddt_1.PLDDTConfidenceColorThemeProvider.name : "chain-id",
type: "cartoon"
});
yield update.commit({ revertOnError: false });
break;
}
}
const expressions = [];
const asymObserved = {};
for (const unit of structure.units) {
structure_1.StructureElement.Location.set(l, structure, unit, unit.elements[0]);
const asymId = structure_1.StructureProperties.chain.label_asym_id(l);
const operatorName = structure_1.StructureProperties.unit.operator_name(l);
if (asymId == alignedAsymId && operatorName == alignedOperatorName)
continue;
if (asymObserved[`${asymId}${TagDelimiter_1.TagDelimiter.assembly}${operatorName}`])
continue;
asymObserved[`${asymId}${TagDelimiter_1.TagDelimiter.assembly}${operatorName}`] = true;
const type = structure_1.StructureProperties.entity.type(l);
if (type == "polymer") {
expressions.push(builder_1.MolScriptBuilder.core.logic.and([
builder_1.MolScriptBuilder.core.rel.eq([builder_1.MolScriptBuilder.ammp('label_asym_id'), asymId]),
builder_1.MolScriptBuilder.core.rel.eq([builder_1.MolScriptBuilder.acp('operatorName'), operatorName])
]));
}
}
const compId = `${entryId}${TagDelimiter_1.TagDelimiter.entity}${alignedEntityId}${TagDelimiter_1.TagDelimiter.assembly}${alignedType}`;
const comp = yield plugin.builders.structure.tryCreateComponentFromExpression(structureCell, builder_1.MolScriptBuilder.struct.generator.atomGroups({
'chain-test': builder_1.MolScriptBuilder.core.logic.or(expressions)
}), (0, uniqid_1.default)(compId), {
label: compId
});
componentMap["polymer"] = comp;
const { update, builder } = reprBuilder(plugin, {
ignoreHydrogens: true,
ignoreLight: false,
quality: "auto"
});
representationMap["polymer"] = builder.buildRepresentation(update, comp, {
color: structure.models.some(m => prop_1.QualityAssessment.isApplicable(m, 'pLDDT')) ? plddt_1.PLDDTConfidenceColorThemeProvider.name : "chain-id",
type: "cartoon"
}, {
initialState: {
isHidden: true
}
});
if ((_f = comp === null || comp === void 0 ? void 0 : comp.cell) === null || _f === void 0 ? void 0 : _f.state) {
transform_1.StateTransform.assignState((_g = comp === null || comp === void 0 ? void 0 : comp.cell) === null || _g === void 0 ? void 0 : _g.state, { isHidden: true });
}
yield update.commit({ revertOnError: false });
for (const expression of (0, selection_1.createSelectionExpressions)(entryId)) {
if (expression.tag == "polymer")
continue;
const comp = yield plugin.builders.structure.tryCreateComponentFromExpression(structureCell, expression.expression, (0, uniqid_1.default)(`${entryId}${TagDelimiter_1.TagDelimiter.entity}${alignedEntityId}${TagDelimiter_1.TagDelimiter.assembly}${expression.tag}`), {
label: `${entryId}${TagDelimiter_1.TagDelimiter.entity}${alignedEntityId}${TagDelimiter_1.TagDelimiter.assembly}${expression.tag}`
});
componentMap[expression.tag] = comp;
//TODO This needs to be called after tryCreateComponentFromExpression
const { update, builder } = reprBuilder(plugin, {
ignoreHydrogens: true,
ignoreLight: false,
quality: "auto"
});
representationMap[expression.tag] = builder.buildRepresentation(update, comp, {
type: expression.type
}, {
initialState: {
isHidden: true
}
});
if (expression.type !== "ball-and-stick")
representationMap[expression.tag + "#ball-and-stick"] = builder.buildRepresentation(update, comp, {
type: "ball-and-stick"
}, {
initialState: {
isHidden: true
}
});
if ((_h = comp === null || comp === void 0 ? void 0 : comp.cell) === null || _h === void 0 ? void 0 : _h.state) {
transform_1.StateTransform.assignState((_j = comp === null || comp === void 0 ? void 0 : comp.cell) === null || _j === void 0 ? void 0 : _j.state, { isHidden: true });
}
yield update.commit({ revertOnError: false });
}
structure.inheritedPropertyData.reprList = Object.values(representationMap).filter(repr => typeof repr != "undefined");
yield updateFocusRepr(plugin, structure, FocusColoring_1.FOCUS_RESIDUE_COLOR, {});
return {
components: componentMap,
representations: representationMap
};
})
});
function matrixAlign(plugin, structureRef, matrix) {
return tslib_1.__awaiter(this, void 0, void 0, function* () {
const trans = {
transform: {
name: 'matrix',
params: { data: matrix, transpose: false }
}
};
const b = plugin.state.data.build().to(structureRef)
.insert(model_1.TransformStructureConformation, trans);
yield plugin.runTask(plugin.state.data.updateTree(b));
});
}
function structuralAlignment(plugin, ref, pdb, structure) {
return tslib_1.__awaiter(this, void 0, void 0, function* () {
var _a, _b, _c, _d;
if (ref.entryId == pdb.entryId) {
refData = structure;
}
else {
const pdbResIndexes = [];
const refResIndexes = [];
const pdbData = structure;
const pdbUnit = yield findFirstInstanceUnit(pdbData, pdb.labelAsymId);
const refUnit = refData ? yield findFirstInstanceUnit(refData, ref.labelAsymId) : undefined;
if (pdbUnit && refUnit && ((_a = ref.targetAlignment) === null || _a === void 0 ? void 0 : _a.aligned_regions) && ((_b = pdb.targetAlignment) === null || _b === void 0 ? void 0 : _b.aligned_regions)) {
const alignmentList = AlignmentMapper_1.AlignmentMapper.getAllTargetIntersections(ref.targetAlignment.aligned_regions, pdb.targetAlignment.aligned_regions);
alignmentList.forEach(alignment => {
const refRange = AlignmentMapper_1.AlignmentMapper.range(alignment[0].target_begin, alignment[0].target_end);
const pdbRange = AlignmentMapper_1.AlignmentMapper.range(alignment[1].target_begin, alignment[1].target_end);
refRange.forEach((refIndex, n) => {
const pdbIndex = pdbRange[n];
const pdbLoci = residueToLoci(pdb, pdbIndex, pdbData);
const refLoci = residueToLoci(refParams, refIndex, refData);
if (!loci_1.Loci.isEmpty(pdbLoci) && !loci_1.Loci.isEmpty(refLoci) && checkLocalScore(pdbUnit.localScore, pdbIndex) && checkLocalScore(refUnit.localScore, refIndex)) {
pdbResIndexes.push(pdbIndex);
refResIndexes.push(refIndex);
}
});
});
}
if (pdbData && pdbUnit && refData && refUnit) {
const refLoci = residueListToLoci(refParams, refResIndexes, refData);
const pdbLoci = residueListToLoci(pdb, pdbResIndexes, pdbData);
if (structure_1.StructureElement.Loci.is(refLoci) && structure_1.StructureElement.Loci.is(pdbLoci)) {
const pivot = plugin.managers.structure.hierarchy.findStructure(refLoci.structure);
const 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;
const transforms = (0, superposition_1.superpose)([refLoci, pdbLoci]);
const { bTransform } = transforms[0];
yield transform(plugin, plugin.helpers.substructureParent.get(pdbData), bTransform, coordinateSystem);
}
}
}
});
}
function findFirstInstanceUnit(structure, labelAsymId) {
return tslib_1.__awaiter(this, void 0, void 0, function* () {
const l = structure_1.StructureElement.Location.create(structure);
for (const unit of structure.units) {
structure_1.StructureElement.Location.set(l, structure, unit, unit.elements[0]);
if (structure_1.StructureProperties.chain.label_asym_id(l) == labelAsymId) {
const q = yield obtainQualityAssessment(unit.model);
return { unit, localScore: q };
}
}
});
}
function checkLocalScore(scoreMap, index) {
if (scoreMap.size == 0)
return true;
return !!(scoreMap.get(index) && scoreMap.get(index) >= 70);
}
function obtainQualityAssessment(model) {
return tslib_1.__awaiter(this, void 0, void 0, function* () {
var _a;
if (!model || !mmcif_1.MmcifFormat.is(model.sourceData))
return new Map();
const { ma_qa_metric, ma_qa_metric_local } = model.sourceData.data.db;
const { model_id, label_seq_id, metric_id, metric_value } = ma_qa_metric_local;
// for simplicity we assume names in ma_qa_metric for mode 'local' are unique
const localMetrics = new Map();
const localNames = new Map();
for (let i = 0, il = ma_qa_metric._rowCount; i < il; i++) {
if (ma_qa_metric.mode.value(i) !== 'local')
continue;
const name = ma_qa_metric.name.value(i);
if (localMetrics.has(name)) {
console.warn(`local ma_qa_metric with name '${name}' already added`);
continue;
}
localMetrics.set(name, new Map());
localNames.set(ma_qa_metric.id.value(i), name);
}
for (let i = 0, il = ma_qa_metric_local._rowCount; i < il; i++) {
if (model_id.value(i) !== model.modelNum)
continue;
const rI = label_seq_id.value(i);
const name = localNames.get(metric_id.value(i));
localMetrics.get(name).set(rI, metric_value.value(i));
}
return (_a = localMetrics.get('pLDDT')) !== null && _a !== void 0 ? _a : new Map();
});
}
const SuperpositionTag = 'SuperpositionTransform';
function transform(plugin, s, matrix, coordinateSystem) {
return tslib_1.__awaiter(this, void 0, void 0, function* () {
const r = mol_state_1.StateObjectRef.resolveAndCheck(plugin.state.data, s);
if (!r)
return;
const o = plugin.state.data.selectQ(q => q.byRef(r.transform.ref).subtree().withTransformer(model_1.TransformStructureConformation))[0];
const transform = coordinateSystem && !linear_algebra_1.Mat4.isIdentity(coordinateSystem.matrix)
? linear_algebra_1.Mat4.mul((0, linear_algebra_1.Mat4)(), coordinateSystem.matrix, matrix)
: matrix;
const params = {
transform: {
name: 'matrix',
params: { data: transform, transpose: false }
}
};
const b = o
? plugin.state.data.build().to(o).update(params)
: plugin.state.data.build().to(s)
.insert(model_1.TransformStructureConformation, params, { tags: SuperpositionTag });
yield plugin.runTask(plugin.state.data.updateTree(b));
});
}
function residueToLoci(pdb, pdbIndex, structure) {
const expression = builder_1.MolScriptBuilder.struct.generator.atomGroups({
'chain-test': builder_1.MolScriptBuilder.core.logic.and([
builder_1.MolScriptBuilder.core.rel.eq([builder_1.MolScriptBuilder.ammp('label_asym_id'), pdb.labelAsymId]),
builder_1.MolScriptBuilder.core.rel.eq([builder_1.MolScriptBuilder.acp('operatorName'), pdb.operatorName]),
builder_1.MolScriptBuilder.core.rel.eq([builder_1.MolScriptBuilder.acp('modelIndex'), 1])
]),
'residue-test': builder_1.MolScriptBuilder.struct.atomProperty.ihm.hasSeqId([pdbIndex]),
'atom-test': builder_1.MolScriptBuilder.core.logic.and([
builder_1.MolScriptBuilder.core.rel.eq([builder_1.MolScriptBuilder.ammp("label_atom_id"), "CA"]),
builder_1.MolScriptBuilder.core.logic.or([builder_1.MolScriptBuilder.core.rel.eq([builder_1.MolScriptBuilder.ammp("label_alt_id"), ""]), builder_1.MolScriptBuilder.core.rel.eq([builder_1.MolScriptBuilder.ammp("label_alt_id"), "A"])])
])
});
const query = (0, compiler_1.compile)(expression);
const selection = query(new structure_1.QueryContext(structure));
return structure_1.StructureSelection.toLociWithSourceUnits(selection);
}
function residueListToLoci(pdb, indexList, structure) {
const expression = builder_1.MolScriptBuilder.struct.generator.atomGroups({
'chain-test': builder_1.MolScriptBuilder.core.logic.and([
builder_1.MolScriptBuilder.core.rel.eq([builder_1.MolScriptBuilder.ammp('label_asym_id'), pdb.labelAsymId]),
builder_1.MolScriptBuilder.core.rel.eq([builder_1.MolScriptBuilder.acp('operatorName'), pdb.operatorName]),
builder_1.MolScriptBuilder.core.rel.eq([builder_1.MolScriptBuilder.acp('modelIndex'), 1])
]),
'residue-test': builder_1.MolScriptBuilder.core.logic.or(indexList.map(index => builder_1.MolScriptBuilder.struct.atomProperty.ihm.hasSeqId([index]))),
'atom-test': builder_1.MolScriptBuilder.core.logic.and([
builder_1.MolScriptBuilder.core.rel.eq([builder_1.MolScriptBuilder.ammp("label_atom_id"), "CA"]),
builder_1.MolScriptBuilder.core.logic.or([builder_1.MolScriptBuilder.core.rel.eq([builder_1.MolScriptBuilder.ammp("label_alt_id"), ""]), builder_1.MolScriptBuilder.core.rel.eq([builder_1.MolScriptBuilder.ammp("label_alt_id"), "A"])])
])
});
const query = (0, compiler_1.compile)(expression);
const selection = query(new structure_1.QueryContext(structure));
return structure_1.StructureSelection.toLociWithSourceUnits(selection);
}