AlignmentRepresentationPresetProvider.ts 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  1. /*
  2. * Copyright (c) 2021 RCSB PDB and contributors, licensed under MIT, See LICENSE file for more info.
  3. * @author Joan Segura Mora <joan.segura@rcsb.org>
  4. */
  5. import {
  6. StructureRepresentationPresetProvider
  7. } from "molstar/lib/mol-plugin-state/builder/structure/representation-preset";
  8. import {PluginContext} from "molstar/lib/mol-plugin/context";
  9. import {PluginStateObject} from "molstar/lib/mol-plugin-state/objects";
  10. import {StateObjectRef} from "molstar/lib/mol-state";
  11. import {
  12. Model,
  13. QueryContext, ResidueIndex,
  14. Structure,
  15. StructureElement,
  16. StructureProperties as SP,
  17. StructureSelection,
  18. Unit
  19. } from "molstar/lib/mol-model/structure";
  20. import {MolScriptBuilder as MS} from "molstar/lib/mol-script/language/builder";
  21. import uniqid from "uniqid";
  22. import {PLDDTConfidenceColorThemeProvider} from "molstar/lib/extensions/model-archive/quality-assessment/color/plddt";
  23. import {ColorTheme} from "molstar/lib/mol-theme/color";
  24. import {createSelectionExpressions} from "@rcsb/rcsb-molstar/build/src/viewer/helpers/selection";
  25. import {ParamDefinition as PD} from "molstar/lib/mol-util/param-definition";
  26. import {Loci} from "molstar/lib/mol-model/loci";
  27. import {superpose} from "molstar/lib/mol-model/structure/structure/util/superposition";
  28. import {Mat4} from "molstar/lib/mol-math/linear-algebra";
  29. import {SymmetryOperator} from "molstar/lib/mol-math/geometry/symmetry-operator";
  30. import {StateTransforms} from "molstar/lib/mol-plugin-state/transforms";
  31. import {TagDelimiter} from "@rcsb/rcsb-saguaro-app";
  32. import {AlignedRegion, TargetAlignment} from "@rcsb/rcsb-api-tools/build/RcsbGraphQL/Types/Borrego/GqlTypes";
  33. import {AlignmentMapper as AM} from "../../../../Utils/AlignmentMapper";
  34. import {compile} from 'molstar/lib/mol-script/runtime/query/compiler';
  35. import reprBuilder = StructureRepresentationPresetProvider.reprBuilder;
  36. import {
  37. QualityAssessment,
  38. } from "molstar/lib/extensions/model-archive/quality-assessment/prop";
  39. import {MmcifFormat} from "molstar/lib/mol-model-formats/structure/mmcif";
  40. import {CustomProperty} from "molstar/lib/mol-model-props/common/custom-property";
  41. let refData: Structure|undefined = undefined;
  42. let refParams: StructureAlignmentParamsType|undefined = undefined;
  43. export const AlignmentRepresentationPresetProvider = StructureRepresentationPresetProvider<{pdb?:{entryId:string;entityId:string;};targetAlignment?:TargetAlignment;},any>({
  44. id: 'alignment-to-reference',
  45. display: {
  46. name: 'Alignemnt to Reference'
  47. },
  48. isApplicable: (structureRef: PluginStateObject.Molecule.Structure, plugin: PluginContext): boolean => true,
  49. params: (structureRef: PluginStateObject.Molecule.Structure | undefined, plugin: PluginContext) => ({
  50. pdb: PD.Value<{entryId:string;entityId:string;}|undefined>(undefined),
  51. targetAlignment: PD.Value<TargetAlignment|undefined>(undefined)
  52. }),
  53. apply: async (structureRef: StateObjectRef<PluginStateObject.Molecule.Structure>, params: {pdb?:{entryId:string;entityId:string;};targetAlignment?: TargetAlignment;}, plugin: PluginContext) => {
  54. const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, structureRef);
  55. if(!structureCell) return;
  56. const structure = structureCell.obj!.data;
  57. const entryId = params.pdb?.entryId!;
  58. const entityId = params.pdb?.entityId!;
  59. const l = StructureElement.Location.create(structure);
  60. let alignedAsymId;
  61. let alignedOperatorName;
  62. let alignedType;
  63. for(const unit of structure.units) {
  64. StructureElement.Location.set(l, structure, unit, unit.elements[0]);
  65. const alignedEntityId = SP.chain.label_entity_id(l);
  66. if(alignedEntityId == params.pdb?.entityId){
  67. alignedAsymId = SP.chain.label_asym_id(l);
  68. alignedOperatorName = SP.unit.operator_name(l);
  69. alignedType = SP.entity.type(l);
  70. const alignedOperators = SP.unit.pdbx_struct_oper_list_ids(l);
  71. if(alignedType != "polymer")
  72. return;
  73. if(plugin.managers.structure.hierarchy.current.structures.length == 1){
  74. refParams = {
  75. entryId: entryId,
  76. labelAsymId: alignedAsymId,
  77. operatorName:alignedOperatorName,
  78. targetAlignment:params.targetAlignment!
  79. };
  80. }
  81. if(refParams && params.pdb){
  82. await structuralAlignment(plugin, refParams, {
  83. entryId: entryId,
  84. labelAsymId: alignedAsymId,
  85. operatorName:alignedOperatorName,
  86. targetAlignment:params.targetAlignment!
  87. }, structure);
  88. }
  89. const comp = await plugin.builders.structure.tryCreateComponentFromExpression(
  90. structureCell,
  91. MS.struct.generator.atomGroups({
  92. 'chain-test': MS.core.logic.and([
  93. MS.core.rel.eq([MS.ammp('label_asym_id'), alignedAsymId]),
  94. MS.core.rel.eq([MS.acp('operatorName'), alignedOperatorName])
  95. ])
  96. }),
  97. uniqid(`${entryId}${TagDelimiter.entity}${entityId}${TagDelimiter.instance}${alignedAsymId}${TagDelimiter.entity}${alignedOperators.join(",")}`),
  98. {
  99. label: `${entryId}${TagDelimiter.entity}${entityId}${TagDelimiter.instance}${alignedAsymId}${TagDelimiter.assembly}${alignedOperators.join(",")}${TagDelimiter.assembly}${alignedType}`
  100. }
  101. );
  102. //TODO This needs to be called after tryCreateComponentFromExpression
  103. const {update, builder} = reprBuilder(plugin, {
  104. ignoreHydrogens: true,
  105. ignoreLight: false,
  106. quality: "auto"
  107. });
  108. builder.buildRepresentation(update, comp, {
  109. color: PLDDTConfidenceColorThemeProvider.isApplicable({ structure }) ? PLDDTConfidenceColorThemeProvider.name as ColorTheme.BuiltIn : "chain-id",
  110. type: "cartoon"
  111. });
  112. await update.commit({ revertOnError: false });
  113. break;
  114. }
  115. }
  116. const expressions = []
  117. const asymObserved: {[key:string]:boolean} = {};
  118. for(const unit of structure.units){
  119. StructureElement.Location.set(l, structure, unit, unit.elements[0]);
  120. const asymId = SP.chain.label_asym_id(l);
  121. const operatorName = SP.unit.operator_name(l);
  122. if(asymId == alignedAsymId && operatorName == alignedOperatorName)
  123. continue;
  124. if(asymObserved[`${asymId}${TagDelimiter.assembly}${operatorName}`])
  125. continue;
  126. asymObserved[`${asymId}${TagDelimiter.assembly}${operatorName}`] = true;
  127. const type = SP.entity.type(l);
  128. if (type == "polymer") {
  129. expressions.push(MS.core.logic.and([
  130. MS.core.rel.eq([MS.ammp('label_asym_id'), asymId]),
  131. MS.core.rel.eq([MS.acp('operatorName'), operatorName])
  132. ]))
  133. }
  134. }
  135. const comp = await plugin.builders.structure.tryCreateComponentFromExpression(
  136. structureCell,
  137. MS.struct.generator.atomGroups({
  138. 'chain-test': MS.core.logic.or(expressions)
  139. }),
  140. uniqid(`${entryId}${TagDelimiter.entity}${entityId}${TagDelimiter.assembly}${alignedType}`),
  141. {
  142. label: `${entryId}${TagDelimiter.entity}${entityId}${TagDelimiter.assembly}${alignedType}`
  143. }
  144. );
  145. const {update, builder} = reprBuilder(plugin, {
  146. ignoreHydrogens: true,
  147. ignoreLight: false,
  148. quality: "auto"
  149. });
  150. builder.buildRepresentation(update, comp, {
  151. color: PLDDTConfidenceColorThemeProvider.isApplicable({ structure }) ? PLDDTConfidenceColorThemeProvider.name as ColorTheme.BuiltIn : "chain-id",
  152. type: "cartoon"
  153. }, {
  154. initialState:{
  155. isHidden:true
  156. }
  157. });
  158. await update.commit({ revertOnError: false });
  159. for(const expression of createSelectionExpressions(entryId)){
  160. if(expression.tag == "polymer")
  161. continue;
  162. const comp = await plugin.builders.structure.tryCreateComponentFromExpression(
  163. structureCell,
  164. expression.expression,
  165. uniqid(`${entryId}${TagDelimiter.entity}${entityId}${TagDelimiter.assembly}${expression.tag}`),
  166. {
  167. label: `${entryId}${TagDelimiter.entity}${entityId}${TagDelimiter.assembly}${expression.tag}`
  168. });
  169. //TODO This needs to be called after tryCreateComponentFromExpression
  170. const { update, builder } = reprBuilder(plugin, {
  171. ignoreHydrogens: true,
  172. ignoreLight: false,
  173. quality: "auto"
  174. });
  175. builder.buildRepresentation(update, comp, {
  176. type: expression.type
  177. },{
  178. initialState:{
  179. isHidden:true
  180. }
  181. });
  182. await update.commit({ revertOnError: false });
  183. }
  184. for (const c of plugin.managers.structure.hierarchy.currentComponentGroups){
  185. for (const comp of c) {
  186. if(typeof comp.cell.state.isHidden === "undefined" && comp.representations[0].cell.state.isHidden)
  187. plugin.managers.structure.component.toggleVisibility(c);
  188. }
  189. }
  190. }
  191. });
  192. type StructureAlignmentParamsType = {
  193. entryId:string;
  194. labelAsymId:string;
  195. operatorName:string;
  196. targetAlignment:TargetAlignment;
  197. };
  198. async function structuralAlignment(plugin: PluginContext, ref:StructureAlignmentParamsType, pdb:StructureAlignmentParamsType, structure: Structure): Promise<void> {
  199. if(ref.entryId == pdb.entryId){
  200. refData = structure;
  201. }else{
  202. const pdbResIndexes: number[] = [];
  203. const refResIndexes: number[] = [];
  204. const pdbData: Structure = structure;
  205. const pdbUnit:{unit: Unit; q:CustomProperty.Data<QualityAssessment>}|undefined = await findFirstInstanceUnit(pdbData,pdb.labelAsymId);
  206. const refUnit:{unit: Unit; q:CustomProperty.Data<QualityAssessment>}|undefined = refData ? await findFirstInstanceUnit(refData, ref.labelAsymId) : undefined;
  207. if( pdbUnit && refUnit && ref.targetAlignment?.aligned_regions && pdb.targetAlignment?.aligned_regions){
  208. const alignmentList = AM.getAllTargetIntersections( ref.targetAlignment.aligned_regions as AlignedRegion[], pdb.targetAlignment.aligned_regions as AlignedRegion[])
  209. alignmentList.forEach(alignment=>{
  210. const refRange = AM.range(alignment[0].target_begin, alignment[0].target_end);
  211. const pdbRange = AM.range(alignment[1].target_begin, alignment[1].target_end);
  212. refRange.forEach((refIndex,n)=>{
  213. const pdbIndex = pdbRange[n];
  214. const pdbLoci = residueToLoci(pdb, pdbIndex, pdbData);
  215. const refLoci = residueToLoci(refParams!, refIndex, refData!);
  216. if(!Loci.isEmpty(pdbLoci) && !Loci.isEmpty(refLoci) && checkLocalScore(pdbUnit.q.value.pLDDT, pdbIndex) && checkLocalScore(refUnit.q.value.pLDDT, refIndex)){
  217. pdbResIndexes.push(pdbIndex)
  218. refResIndexes.push(refIndex)
  219. }
  220. });
  221. })
  222. }
  223. if(pdbData && pdbUnit && refData && refUnit){
  224. const refLoci: Loci = residueListToLoci(refParams!, refResIndexes, refData);
  225. const pdbLoci: Loci = residueListToLoci(pdb, pdbResIndexes, pdbData);
  226. if(StructureElement.Loci.is(refLoci) && StructureElement.Loci.is(pdbLoci)) {
  227. const pivot = plugin.managers.structure.hierarchy.findStructure(refLoci.structure);
  228. const coordinateSystem = pivot?.transform?.cell.obj?.data.coordinateSystem;
  229. const transforms = superpose([refLoci, pdbLoci]);
  230. const { bTransform } = transforms[0];
  231. await transform(plugin, plugin.helpers.substructureParent.get(pdbData)!, bTransform, coordinateSystem);
  232. }
  233. }
  234. }
  235. }
  236. async function findFirstInstanceUnit(structure: Structure, labelAsymId: string): Promise<{unit: Unit; q:CustomProperty.Data<QualityAssessment>}|undefined> {
  237. const l = StructureElement.Location.create(structure);
  238. for(const unit of structure.units) {
  239. StructureElement.Location.set(l, structure, unit, unit.elements[0]);
  240. if (SP.chain.label_asym_id(l) == labelAsymId) {
  241. const q:CustomProperty.Data<QualityAssessment> = await obtain(unit.model);
  242. return {unit,q};
  243. }
  244. }
  245. }
  246. function checkLocalScore(scoreMap: Map<ResidueIndex, number>|undefined, index: number): boolean{
  247. if(typeof scoreMap == "undefined")
  248. return true;
  249. return !!(scoreMap.get(index as ResidueIndex) && scoreMap.get(index as ResidueIndex)! >= 70);
  250. }
  251. const Empty = {
  252. value: {
  253. localMetrics: new Map()
  254. }
  255. };
  256. async function obtain(model: Model): Promise<CustomProperty.Data<QualityAssessment>> {
  257. if (!model || !MmcifFormat.is(model.sourceData)) return Empty;
  258. const { ma_qa_metric, ma_qa_metric_local } = model.sourceData.data.db;
  259. const { model_id, label_asym_id, label_seq_id, metric_id, metric_value } = ma_qa_metric_local;
  260. const { index } = model.atomicHierarchy;
  261. // for simplicity we assume names in ma_qa_metric for mode 'local' are unique
  262. const localMetrics = new Map<string, Map<ResidueIndex, number>>();
  263. const localNames = new Map<number, string>();
  264. for (let i = 0, il = ma_qa_metric._rowCount; i < il; i++) {
  265. if (ma_qa_metric.mode.value(i) !== 'local') continue;
  266. const name = ma_qa_metric.name.value(i);
  267. if (localMetrics.has(name)) {
  268. console.warn(`local ma_qa_metric with name '${name}' already added`);
  269. continue;
  270. }
  271. localMetrics.set(name, new Map());
  272. localNames.set(ma_qa_metric.id.value(i), name);
  273. }
  274. for (let i = 0, il = ma_qa_metric_local._rowCount; i < il; i++) {
  275. if (model_id.value(i) !== model.modelNum)
  276. continue;
  277. const labelAsymId = label_asym_id.value(i);
  278. const entityIndex = index.findEntity(labelAsymId);
  279. const rI = index.findResidue(model.entities.data.id.value(entityIndex), labelAsymId, label_seq_id.value(i));
  280. const name = localNames.get(metric_id.value(i))!;
  281. localMetrics.get(name)!.set(rI, metric_value.value(i));
  282. }
  283. return {
  284. value: {
  285. localMetrics,
  286. pLDDT: localMetrics.get('pLDDT'),
  287. qmean: localMetrics.get('qmean'),
  288. }
  289. };
  290. }
  291. const SuperpositionTag = 'SuperpositionTransform';
  292. async function transform(plugin:PluginContext, s: StateObjectRef<PluginStateObject.Molecule.Structure>, matrix: Mat4, coordinateSystem?: SymmetryOperator): Promise<void>{
  293. const r = StateObjectRef.resolveAndCheck(plugin.state.data, s);
  294. if (!r) return;
  295. const o = plugin.state.data.selectQ(q => q.byRef(r.transform.ref).subtree().withTransformer(StateTransforms.Model.TransformStructureConformation))[0];
  296. const transform = coordinateSystem && !Mat4.isIdentity(coordinateSystem.matrix)
  297. ? Mat4.mul(Mat4(), coordinateSystem.matrix, matrix)
  298. : matrix;
  299. const params = {
  300. transform: {
  301. name: 'matrix' as const,
  302. params: { data: transform, transpose: false }
  303. }
  304. };
  305. const b = o
  306. ? plugin.state.data.build().to(o).update(params)
  307. : plugin.state.data.build().to(s)
  308. .insert(StateTransforms.Model.TransformStructureConformation, params, { tags: SuperpositionTag });
  309. await plugin.runTask(plugin.state.data.updateTree(b));
  310. }
  311. function residueToLoci(pdb:StructureAlignmentParamsType, pdbIndex:number, structure: Structure): Loci {
  312. const expression = MS.struct.generator.atomGroups({
  313. 'chain-test': MS.core.logic.and([
  314. MS.core.rel.eq([MS.ammp('label_asym_id'), pdb.labelAsymId]),
  315. MS.core.rel.eq([MS.acp('operatorName'), pdb.operatorName]),
  316. MS.core.rel.eq([MS.acp('modelIndex'),1])
  317. ]),
  318. 'residue-test':MS.core.rel.eq([MS.ammp('label_seq_id'), pdbIndex]),
  319. 'atom-test':MS.core.logic.and([
  320. MS.core.rel.eq([MS.ammp("label_atom_id"),"CA"]),
  321. MS.core.logic.or([MS.core.rel.eq([MS.ammp("label_alt_id"),""]), MS.core.rel.eq([MS.ammp("label_alt_id"),"A"])])
  322. ])
  323. });
  324. const query = compile<StructureSelection>(expression);
  325. const selection = query(new QueryContext(structure));
  326. return StructureSelection.toLociWithSourceUnits(selection);
  327. }
  328. function residueListToLoci(pdb:StructureAlignmentParamsType, indexList:number[], structure: Structure): Loci {
  329. const expression = MS.struct.generator.atomGroups({
  330. 'chain-test': MS.core.logic.and([
  331. MS.core.rel.eq([MS.ammp('label_asym_id'), pdb.labelAsymId]),
  332. MS.core.rel.eq([MS.acp('operatorName'), pdb.operatorName]),
  333. MS.core.rel.eq([MS.acp('modelIndex'),1])
  334. ]),
  335. 'residue-test':MS.core.logic.or(
  336. indexList.map(index=>MS.core.rel.eq([MS.ammp('label_seq_id'), index]))
  337. ),
  338. 'atom-test':MS.core.logic.and([
  339. MS.core.rel.eq([MS.ammp("label_atom_id"),"CA"]),
  340. MS.core.logic.or([MS.core.rel.eq([MS.ammp("label_alt_id"),""]), MS.core.rel.eq([MS.ammp("label_alt_id"),"A"])])
  341. ])
  342. });
  343. const query = compile<StructureSelection>(expression);
  344. const selection = query(new QueryContext(structure));
  345. return StructureSelection.toLociWithSourceUnits(selection);
  346. }