structure-quality-report.ts 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. /**
  2. * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author David Sehnal <david.sehnal@gmail.com>
  5. */
  6. import { Segmentation } from 'mol-data/int';
  7. import { CifWriter } from 'mol-io/writer/cif';
  8. import { Model, ModelPropertyDescriptor, ResidueIndex, Structure, StructureElement, Unit } from 'mol-model/structure';
  9. import { residueIdFields } from 'mol-model/structure/export/categories/atom_site';
  10. import { fetchRetry } from '../utils/fetch-retry';
  11. import CifField = CifWriter.Field;
  12. type IssueMap = Map<ResidueIndex, string[]>
  13. const _Descriptor: ModelPropertyDescriptor = {
  14. isStatic: true,
  15. name: 'structure_quality_report',
  16. cifExport: {
  17. prefix: 'pdbe',
  18. categories: [{
  19. name: 'pdbe_structure_quality_report',
  20. instance(ctx) {
  21. const issues = StructureQualityReport.get(ctx.model);
  22. if (!issues) return CifWriter.Category.Empty;
  23. const residues = getResidueLoci(ctx.structure, issues);
  24. return {
  25. fields: _structure_quality_report_fields,
  26. data: <ExportCtx>{ model: ctx.model, residues, residueIndex: ctx.model.atomicHierarchy.residueAtomSegments.index, issues },
  27. rowCount: residues.length
  28. };
  29. }
  30. }]
  31. }
  32. }
  33. type ExportCtx = { model: Model, residueIndex: ArrayLike<ResidueIndex>, residues: StructureElement[], issues: IssueMap };
  34. const _structure_quality_report_fields: CifField<ResidueIndex, ExportCtx>[] = [
  35. CifField.index('id'),
  36. ...residueIdFields<ResidueIndex, ExportCtx>((k, d) => d.residues[k]),
  37. CifField.str<ResidueIndex, ExportCtx>('issues', (i, d) => d.issues.get(d.residueIndex[d.residues[i].element])!.join(','))
  38. ];
  39. function getResidueLoci(structure: Structure, issues: IssueMap) {
  40. const seenResidues = new Set<ResidueIndex>();
  41. const unitGroups = structure.unitSymmetryGroups;
  42. const loci: StructureElement[] = [];
  43. for (const unitGroup of unitGroups) {
  44. const unit = unitGroup.units[0];
  45. if (!Unit.isAtomic(unit)) {
  46. continue;
  47. }
  48. const residues = Segmentation.transientSegments(unit.model.atomicHierarchy.residueAtomSegments, unit.elements);
  49. while (residues.hasNext) {
  50. const seg = residues.move();
  51. if (!issues.has(seg.index) || seenResidues.has(seg.index)) continue;
  52. seenResidues.add(seg.index);
  53. loci[loci.length] = StructureElement.create(unit, unit.elements[seg.start]);
  54. }
  55. }
  56. loci.sort((x, y) => x.element - y.element);
  57. return loci;
  58. }
  59. function createIssueMap(modelData: Model, data: any): IssueMap | undefined {
  60. const ret = new Map<ResidueIndex, string[]>();
  61. if (!data.molecules) return;
  62. for (const entity of data.molecules) {
  63. const entity_id = entity.entity_id.toString();
  64. for (const chain of entity.chains) {
  65. const asym_id = chain.struct_asym_id.toString();
  66. for (const model of chain.models) {
  67. const model_id = model.model_id.toString();
  68. if (+model_id !== modelData.modelNum) continue;
  69. for (const residue of model.residues) {
  70. const auth_seq_id = residue.author_residue_number, ins_code = residue.author_insertion_code || '';
  71. const idx = modelData.atomicHierarchy.findResidueKey(entity_id, asym_id, '', auth_seq_id, ins_code);
  72. ret.set(idx, residue.outlier_types);
  73. }
  74. }
  75. }
  76. }
  77. return ret;
  78. }
  79. export namespace StructureQualityReport {
  80. export const Descriptor = _Descriptor;
  81. export async function attachFromPDBeApi(model: Model) {
  82. if (model.customProperties.has(Descriptor)) return true;
  83. const id = model.label.toLowerCase();
  84. const rawData = await fetchRetry(`https://www.ebi.ac.uk/pdbe/api/validation/residuewise_outlier_summary/entry/${model.label.toLowerCase()}`, 1500, 5);
  85. const json = await rawData.json();
  86. const data = json[id];
  87. if (!data) return false;
  88. const issueMap = createIssueMap(model, data);
  89. if (!issueMap || issueMap.size === 0) return false;
  90. model.customProperties.add(Descriptor);
  91. model._staticPropertyData.__StructureQualityReport__ = issueMap;
  92. return true;
  93. }
  94. export function get(model: Model): IssueMap | undefined {
  95. return model._staticPropertyData.__StructureQualityReport__;
  96. }
  97. }