structure-quality-report.ts 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  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 { Column, Table } from 'mol-data/db';
  7. import { toTable } from 'mol-io/reader/cif/schema';
  8. import { mmCIF_residueId_schema } from 'mol-io/reader/cif/schema/mmcif-extras';
  9. import { CifWriter } from 'mol-io/writer/cif';
  10. import { Model, ModelPropertyDescriptor, ResidueIndex, StructureProperties as P, Unit, IndexedCustomProperty } from 'mol-model/structure';
  11. import { residueIdFields } from 'mol-model/structure/export/categories/atom_site';
  12. import { StructureElement } from 'mol-model/structure/structure';
  13. import { CustomPropSymbol } from 'mol-script/language/symbol';
  14. import Type from 'mol-script/language/type';
  15. import { QuerySymbolRuntime } from 'mol-script/runtime/query/compiler';
  16. import { PropertyWrapper } from '../common/wrapper';
  17. import CifField = CifWriter.Field;
  18. export namespace StructureQualityReport {
  19. export type IssueMap = IndexedCustomProperty.Residue<string[]>
  20. export type Property = PropertyWrapper<IssueMap | undefined>
  21. export function get(model: Model): Property | undefined {
  22. // must be defined before the descriptor so it's not undefined.
  23. return model._dynamicPropertyData.__StructureQualityReport__;
  24. }
  25. export const Descriptor = ModelPropertyDescriptor({
  26. isStatic: false,
  27. name: 'structure_quality_report',
  28. cifExport: {
  29. prefix: 'pdbe',
  30. categories: [
  31. PropertyWrapper.defaultInfoCategory('pdbe_structure_quality_report', StructureQualityReport.get),
  32. {
  33. name: 'pdbe_structure_quality_report_issues',
  34. instance(ctx) {
  35. return {
  36. fields: _structure_quality_report_issues_fields,
  37. source: ctx.structures.map(s => IndexedCustomProperty.getCifDataSource(s, StructureQualityReport.getIssueMap(s.model), ctx.cache))
  38. };
  39. }
  40. }]
  41. },
  42. symbols: {
  43. issueCount: QuerySymbolRuntime.Dynamic(CustomPropSymbol('pdbe', 'structure-quality.issue-count', Type.Num),
  44. ctx => StructureQualityReport.getIssues(ctx.element).length),
  45. // TODO: add (hasIssue :: IssueType(extends string) -> boolean) symbol
  46. }
  47. });
  48. export const Schema = {
  49. pdbe_structure_quality_report: {
  50. updated_datetime_utc: Column.Schema.str
  51. },
  52. pdbe_structure_quality_report_issues: {
  53. id: Column.Schema.int,
  54. ...mmCIF_residueId_schema,
  55. pdbx_PDB_model_num: Column.Schema.int,
  56. issues: Column.Schema.List(',', x => x)
  57. }
  58. }
  59. function getCifData(model: Model) {
  60. if (model.sourceData.kind !== 'mmCIF') throw new Error('Data format must be mmCIF.');
  61. return toTable(Schema.pdbe_structure_quality_report_issues, model.sourceData.frame.categories.pdbe_structure_quality_report_issues);
  62. }
  63. export async function attachFromCifOrApi(model: Model, params: {
  64. // provide JSON from api
  65. PDBe_apiSourceJson?: (model: Model) => Promise<any>
  66. }) {
  67. if (get(model)) return true;
  68. let issueMap: IssueMap | undefined;
  69. let info = PropertyWrapper.tryGetInfoFromCif('pdbe_structure_quality_report', model);
  70. if (info) {
  71. const data = getCifData(model);
  72. issueMap = createIssueMapFromCif(model, data);
  73. } else if (params.PDBe_apiSourceJson) {
  74. const data = await params.PDBe_apiSourceJson(model);
  75. if (!data) return false;
  76. info = PropertyWrapper.createInfo();
  77. issueMap = createIssueMapFromJson(model, data);
  78. } else {
  79. return false;
  80. }
  81. model.customProperties.add(Descriptor);
  82. set(model, { info, data: issueMap });
  83. return true;
  84. }
  85. function set(model: Model, prop: Property) {
  86. (model._dynamicPropertyData.__StructureQualityReport__ as Property) = prop;
  87. }
  88. export function getIssueMap(model: Model): IssueMap | undefined {
  89. const prop = get(model);
  90. return prop && prop.data;
  91. }
  92. const _emptyArray: string[] = [];
  93. export function getIssues(e: StructureElement) {
  94. if (!Unit.isAtomic(e.unit)) return _emptyArray;
  95. const prop = StructureQualityReport.get(e.unit.model);
  96. if (!prop || !prop.data) return _emptyArray;
  97. const rI = e.unit.residueIndex[e.element];
  98. return prop.data.has(rI) ? prop.data.get(rI)! : _emptyArray;
  99. }
  100. }
  101. type ExportCtx = IndexedCustomProperty.ExportCtx<string[]>
  102. const _structure_quality_report_issues_fields: CifField<number, ExportCtx>[] = CifWriter.fields()
  103. .index('id')
  104. .many(residueIdFields((i, d) => d.elements[i]))
  105. .int('pdbx_PDB_model_num', (i, d) => P.unit.model_num(d.elements[i]))
  106. .str('issues', (i, d) => d.property(i).join(','))
  107. .getFields()
  108. function createIssueMapFromJson(modelData: Model, data: any): StructureQualityReport.IssueMap | undefined {
  109. const ret = new Map<ResidueIndex, string[]>();
  110. if (!data.molecules) return;
  111. for (const entity of data.molecules) {
  112. const entity_id = entity.entity_id.toString();
  113. for (const chain of entity.chains) {
  114. const asym_id = chain.struct_asym_id.toString();
  115. for (const model of chain.models) {
  116. const model_id = model.model_id.toString();
  117. if (+model_id !== modelData.modelNum) continue;
  118. for (const residue of model.residues) {
  119. const auth_seq_id = residue.author_residue_number, ins_code = residue.author_insertion_code || '';
  120. const idx = modelData.atomicHierarchy.index.findResidue(entity_id, asym_id, auth_seq_id, ins_code);
  121. ret.set(idx, residue.outlier_types);
  122. }
  123. }
  124. }
  125. }
  126. return IndexedCustomProperty.fromResidueMap(ret);
  127. }
  128. function createIssueMapFromCif(modelData: Model, data: Table<typeof StructureQualityReport.Schema.pdbe_structure_quality_report_issues>): StructureQualityReport.IssueMap | undefined {
  129. const ret = new Map<ResidueIndex, string[]>();
  130. const { label_entity_id, label_asym_id, auth_seq_id, pdbx_PDB_ins_code, issues, pdbx_PDB_model_num, _rowCount } = data;
  131. for (let i = 0; i < _rowCount; i++) {
  132. if (pdbx_PDB_model_num.value(i) !== modelData.modelNum) continue;
  133. const idx = modelData.atomicHierarchy.index.findResidue(label_entity_id.value(i), label_asym_id.value(i), auth_seq_id.value(i), pdbx_PDB_ins_code.value(i));
  134. ret.set(idx, issues.value(i));
  135. }
  136. return IndexedCustomProperty.fromResidueMap(ret);
  137. }