@rcsb/rcsb-saguaro-3d
Version:
RCSB Molstar/Saguaro Web App
354 lines • 19.2 kB
JavaScript
/*
* Copyright (c) 2021 RCSB PDB and contributors, licensed under MIT, See LICENSE file for more info.
* @author Joan Segura Mora <joan.segura@rcsb.org>
*/
import { __awaiter } from "tslib";
import { StructureRepresentationPresetProvider } from "molstar/lib/mol-plugin-state/builder/structure/representation-preset";
import { StateObjectRef } from "molstar/lib/mol-state";
import { QueryContext, StructureElement, StructureProperties as SP, StructureSelection } from "molstar/lib/mol-model/structure";
import { MolScriptBuilder as MS } from "molstar/lib/mol-script/language/builder";
import uniqid from "uniqid";
import { PLDDTConfidenceColorThemeProvider } from "molstar/lib/extensions/model-archive/quality-assessment/color/plddt";
import { createSelectionExpressions } from "@rcsb/rcsb-molstar/build/src/viewer/helpers/selection";
import { ParamDefinition as PD } from "molstar/lib/mol-util/param-definition";
import { Loci } from "molstar/lib/mol-model/loci";
import { superpose } from "molstar/lib/mol-model/structure/structure/util/superposition";
import { Mat4 } from "molstar/lib/mol-math/linear-algebra";
import { TagDelimiter } from "@rcsb/rcsb-api-tools/lib/RcsbUtils/TagDelimiter";
import { AlignmentMapper as AM } from "../../../../Utils/AlignmentMapper";
import { compile } from 'molstar/lib/mol-script/runtime/query/compiler';
var reprBuilder = StructureRepresentationPresetProvider.reprBuilder;
import { MmcifFormat } from "molstar/lib/mol-model-formats/structure/mmcif";
import { StateTransform } from "molstar/lib/mol-state/transform";
import { TransformStructureConformation } from "molstar/lib/mol-plugin-state/transforms/model";
var updateFocusRepr = StructureRepresentationPresetProvider.updateFocusRepr;
import { FOCUS_RESIDUE_COLOR } from "./FocusTheme/FocusColoring";
import { QualityAssessment } from "molstar/lib/extensions/model-archive/quality-assessment/prop";
let refData = undefined;
let refParams = undefined;
export const AlignmentRepresentationPresetProvider = StructureRepresentationPresetProvider({
id: 'alignment-to-reference',
display: {
name: 'Alignment to Reference'
},
isApplicable: (structureRef, plugin) => true,
params: (structureRef, plugin) => ({
pdb: PD.Value(undefined),
targetAlignment: PD.Value(undefined),
transform: PD.Value(undefined)
}),
apply: (structureRef, params, plugin) => __awaiter(void 0, void 0, void 0, function* () {
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
const structureCell = 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 = StructureElement.Location.create(structure);
let alignedEntityId;
let alignedAsymId;
let alignedOperatorName;
let alignedType;
const componentMap = {};
const representationMap = {};
for (const unit of structure.units) {
StructureElement.Location.set(l, structure, unit, unit.elements[0]);
if (SP.chain.label_entity_id(l) == entityId || SP.chain.label_asym_id(l) == instanceId) {
alignedEntityId = SP.chain.label_entity_id(l);
alignedAsymId = SP.chain.label_asym_id(l);
alignedOperatorName = SP.unit.operator_name(l);
alignedType = SP.entity.type(l);
const alignedOperators = SP.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, MS.struct.generator.atomGroups({
'chain-test': MS.core.logic.and([
MS.core.rel.eq([MS.ammp('label_asym_id'), alignedAsymId]),
MS.core.rel.eq([MS.acp('operatorName'), alignedOperatorName])
])
}), uniqid(`${entryId}${TagDelimiter.entity}${alignedEntityId}${TagDelimiter.instance}${alignedAsymId}${TagDelimiter.entity}${alignedOperators.join(",")}`), {
label: `${entryId}${TagDelimiter.entity}${alignedEntityId}${TagDelimiter.instance}${alignedAsymId}${TagDelimiter.assembly}${alignedOperators.join(",")}${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 => QualityAssessment.isApplicable(m, 'pLDDT')) ? PLDDTConfidenceColorThemeProvider.name : "chain-id",
type: "cartoon"
});
yield update.commit({ revertOnError: false });
break;
}
}
const expressions = [];
const asymObserved = {};
for (const unit of structure.units) {
StructureElement.Location.set(l, structure, unit, unit.elements[0]);
const asymId = SP.chain.label_asym_id(l);
const operatorName = SP.unit.operator_name(l);
if (asymId == alignedAsymId && operatorName == alignedOperatorName)
continue;
if (asymObserved[`${asymId}${TagDelimiter.assembly}${operatorName}`])
continue;
asymObserved[`${asymId}${TagDelimiter.assembly}${operatorName}`] = true;
const type = SP.entity.type(l);
if (type == "polymer") {
expressions.push(MS.core.logic.and([
MS.core.rel.eq([MS.ammp('label_asym_id'), asymId]),
MS.core.rel.eq([MS.acp('operatorName'), operatorName])
]));
}
}
const compId = `${entryId}${TagDelimiter.entity}${alignedEntityId}${TagDelimiter.assembly}${alignedType}`;
const comp = yield plugin.builders.structure.tryCreateComponentFromExpression(structureCell, MS.struct.generator.atomGroups({
'chain-test': MS.core.logic.or(expressions)
}), uniqid(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 => QualityAssessment.isApplicable(m, 'pLDDT')) ? 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) {
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 createSelectionExpressions(entryId)) {
if (expression.tag == "polymer")
continue;
const comp = yield plugin.builders.structure.tryCreateComponentFromExpression(structureCell, expression.expression, uniqid(`${entryId}${TagDelimiter.entity}${alignedEntityId}${TagDelimiter.assembly}${expression.tag}`), {
label: `${entryId}${TagDelimiter.entity}${alignedEntityId}${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) {
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, FOCUS_RESIDUE_COLOR, {});
return {
components: componentMap,
representations: representationMap
};
})
});
function matrixAlign(plugin, structureRef, matrix) {
return __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(TransformStructureConformation, trans);
yield plugin.runTask(plugin.state.data.updateTree(b));
});
}
function structuralAlignment(plugin, ref, pdb, structure) {
return __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 = AM.getAllTargetIntersections(ref.targetAlignment.aligned_regions, pdb.targetAlignment.aligned_regions);
alignmentList.forEach(alignment => {
const refRange = AM.range(alignment[0].target_begin, alignment[0].target_end);
const pdbRange = AM.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.isEmpty(pdbLoci) && !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 (StructureElement.Loci.is(refLoci) && 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 = superpose([refLoci, pdbLoci]);
const { bTransform } = transforms[0];
yield transform(plugin, plugin.helpers.substructureParent.get(pdbData), bTransform, coordinateSystem);
}
}
}
});
}
function findFirstInstanceUnit(structure, labelAsymId) {
return __awaiter(this, void 0, void 0, function* () {
const l = StructureElement.Location.create(structure);
for (const unit of structure.units) {
StructureElement.Location.set(l, structure, unit, unit.elements[0]);
if (SP.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 __awaiter(this, void 0, void 0, function* () {
var _a;
if (!model || !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 __awaiter(this, void 0, void 0, function* () {
const r = StateObjectRef.resolveAndCheck(plugin.state.data, s);
if (!r)
return;
const o = plugin.state.data.selectQ(q => q.byRef(r.transform.ref).subtree().withTransformer(TransformStructureConformation))[0];
const transform = coordinateSystem && !Mat4.isIdentity(coordinateSystem.matrix)
? Mat4.mul(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(TransformStructureConformation, params, { tags: SuperpositionTag });
yield plugin.runTask(plugin.state.data.updateTree(b));
});
}
function residueToLoci(pdb, pdbIndex, structure) {
const expression = MS.struct.generator.atomGroups({
'chain-test': MS.core.logic.and([
MS.core.rel.eq([MS.ammp('label_asym_id'), pdb.labelAsymId]),
MS.core.rel.eq([MS.acp('operatorName'), pdb.operatorName]),
MS.core.rel.eq([MS.acp('modelIndex'), 1])
]),
'residue-test': MS.struct.atomProperty.ihm.hasSeqId([pdbIndex]),
'atom-test': MS.core.logic.and([
MS.core.rel.eq([MS.ammp("label_atom_id"), "CA"]),
MS.core.logic.or([MS.core.rel.eq([MS.ammp("label_alt_id"), ""]), MS.core.rel.eq([MS.ammp("label_alt_id"), "A"])])
])
});
const query = compile(expression);
const selection = query(new QueryContext(structure));
return StructureSelection.toLociWithSourceUnits(selection);
}
function residueListToLoci(pdb, indexList, structure) {
const expression = MS.struct.generator.atomGroups({
'chain-test': MS.core.logic.and([
MS.core.rel.eq([MS.ammp('label_asym_id'), pdb.labelAsymId]),
MS.core.rel.eq([MS.acp('operatorName'), pdb.operatorName]),
MS.core.rel.eq([MS.acp('modelIndex'), 1])
]),
'residue-test': MS.core.logic.or(indexList.map(index => MS.struct.atomProperty.ihm.hasSeqId([index]))),
'atom-test': MS.core.logic.and([
MS.core.rel.eq([MS.ammp("label_atom_id"), "CA"]),
MS.core.logic.or([MS.core.rel.eq([MS.ammp("label_alt_id"), ""]), MS.core.rel.eq([MS.ammp("label_alt_id"), "A"])])
])
});
const query = compile(expression);
const selection = query(new QueryContext(structure));
return StructureSelection.toLociWithSourceUnits(selection);
}
//# sourceMappingURL=AlignmentRepresentationPresetProvider.js.map