diff --git a/src/mol-io/reader/cif/schema/mmcif-extras.ts b/src/mol-io/reader/cif/schema/mmcif-extras.ts new file mode 100644 index 0000000000000000000000000000000000000000..10b5e32f74df25b17eaf76414e89f7cd9d98b74b --- /dev/null +++ b/src/mol-io/reader/cif/schema/mmcif-extras.ts @@ -0,0 +1,18 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + + import { mmCIF_Schema } from './mmcif'; + +export const mmCIF_residueId_schema = { + label_comp_id: mmCIF_Schema.atom_site.label_comp_id, + label_seq_id: mmCIF_Schema.atom_site.label_seq_id, + pdbx_PDB_ins_code: mmCIF_Schema.atom_site.pdbx_PDB_ins_code, + label_asym_id: mmCIF_Schema.atom_site.label_asym_id, + label_entity_id: mmCIF_Schema.atom_site.label_entity_id, + auth_comp_id: mmCIF_Schema.atom_site.auth_atom_id, + auth_seq_id: mmCIF_Schema.atom_site.auth_seq_id, + auth_asym_id: mmCIF_Schema.atom_site.auth_asym_id +} \ No newline at end of file diff --git a/src/mol-model-props/pdbe/structure-quality-report.ts b/src/mol-model-props/pdbe/structure-quality-report.ts index 98d0f440f0f3ca976e5f16677a88d94cc08b7fe8..c2d89ba0defd71442121d774987be82aa8de168f 100644 --- a/src/mol-model-props/pdbe/structure-quality-report.ts +++ b/src/mol-model-props/pdbe/structure-quality-report.ts @@ -9,6 +9,9 @@ import { CifWriter } from 'mol-io/writer/cif'; import { Model, ModelPropertyDescriptor, ResidueIndex, Structure, StructureElement, Unit } from 'mol-model/structure'; import { residueIdFields } from 'mol-model/structure/export/categories/atom_site'; import CifField = CifWriter.Field; +import { mmCIF_residueId_schema } from 'mol-io/reader/cif/schema/mmcif-extras'; +import { Column, Table } from 'mol-data/db'; +import { toTable } from 'mol-io/reader/cif/schema'; type IssueMap = Map<ResidueIndex, string[]> @@ -19,13 +22,18 @@ const _Descriptor: ModelPropertyDescriptor = { prefix: 'pdbe', categories: [{ name: 'pdbe_structure_quality_report', + instance() { + return { fields: _structure_quality_report_fields, rowCount: 1 } + } + }, { + name: 'pdbe_structure_quality_report_issues', instance(ctx) { const issues = StructureQualityReport.get(ctx.model); if (!issues) return CifWriter.Category.Empty; const residues = getResidueLoci(ctx.structure, issues); return { - fields: _structure_quality_report_fields, + fields: _structure_quality_report_issues_fields, data: <ExportCtx>{ model: ctx.model, residues, residueIndex: ctx.model.atomicHierarchy.residueAtomSegments.index, issues }, rowCount: residues.length }; @@ -36,12 +44,16 @@ const _Descriptor: ModelPropertyDescriptor = { type ExportCtx = { model: Model, residueIndex: ArrayLike<ResidueIndex>, residues: StructureElement[], issues: IssueMap }; -const _structure_quality_report_fields: CifField<ResidueIndex, ExportCtx>[] = [ +const _structure_quality_report_issues_fields: CifField<ResidueIndex, ExportCtx>[] = [ CifField.index('id'), ...residueIdFields<ResidueIndex, ExportCtx>((k, d) => d.residues[k]), CifField.str<ResidueIndex, ExportCtx>('issues', (i, d) => d.issues.get(d.residueIndex[d.residues[i].element])!.join(',')) ]; +const _structure_quality_report_fields: CifField<ResidueIndex, ExportCtx>[] = [ + CifField.str('updated_datetime_utc', () => `${new Date().toISOString().replace(/T/, ' ').replace(/\..+/, '')}`) +]; + function getResidueLoci(structure: Structure, issues: IssueMap) { const seenResidues = new Set<ResidueIndex>(); const unitGroups = structure.unitSymmetryGroups; @@ -67,7 +79,7 @@ function getResidueLoci(structure: Structure, issues: IssueMap) { return loci; } -function createIssueMap(modelData: Model, data: any): IssueMap | undefined { +function createIssueMapFromJson(modelData: Model, data: any): IssueMap | undefined { const ret = new Map<ResidueIndex, string[]>(); if (!data.molecules) return; @@ -91,24 +103,52 @@ function createIssueMap(modelData: Model, data: any): IssueMap | undefined { return ret; } +function createIssueMapFromCif(modelData: Model, data: Table<typeof StructureQualityReport.Schema.pdbe_structure_quality_report_issues>): IssueMap | undefined { + const ret = new Map<ResidueIndex, string[]>(); + const { label_entity_id, label_asym_id, auth_seq_id, pdbx_PDB_ins_code, issues, _rowCount } = data; + + for (let i = 0; i < _rowCount; i++) { + const idx = modelData.atomicHierarchy.findResidueKey(label_entity_id.value(i), label_asym_id.value(i), '', auth_seq_id.value(i), pdbx_PDB_ins_code.value(i)); + ret.set(idx, issues.value(i)); + } + + return ret; +} + export namespace StructureQualityReport { export const Descriptor = _Descriptor; + export const Schema = { + pdbe_structure_quality_report: { + updated_datetime_utc: Column.Schema.str + }, + pdbe_structure_quality_report_issues: { + id: Column.Schema.int, + ...mmCIF_residueId_schema, + issues: Column.Schema.List(',', x => x) + } + } + export async function attachFromCifOrApi(model: Model, params: { // provide JSON from api - pdbEapiSourceJson?: (model: Model) => Promise<any> + PDBe_apiSourceJson?: (model: Model) => Promise<any> }) { - // TODO: check if present in mmCIF on Model - if (!params.pdbEapiSourceJson) throw new Error('Data source not defined'); - if (model.customProperties.has(Descriptor)) return true; - const id = model.label.toLowerCase(); - const json = await params.pdbEapiSourceJson(model); - const data = json[id]; - if (!data) return false; - const issueMap = createIssueMap(model, data); - if (!issueMap || issueMap.size === 0) return false; + let issueMap; + + if (model.sourceData.kind === 'mmCIF' && model.sourceData.frame.categoryNames.includes('pdbe_structure_quality_report')) { + const data = toTable(Schema.pdbe_structure_quality_report_issues, model.sourceData.frame.categories.pdbe_structure_quality_report); + issueMap = createIssueMapFromCif(model, data); + } else if (!params.PDBe_apiSourceJson) { + throw new Error('Data source not defined'); + } else { + const id = model.label.toLowerCase(); + const json = await params.PDBe_apiSourceJson(model); + const data = json[id]; + if (!data) return false; + issueMap = createIssueMapFromJson(model, data); + } model.customProperties.add(Descriptor); model._staticPropertyData.__StructureQualityReport__ = issueMap; diff --git a/src/servers/model/properties/pdbe.ts b/src/servers/model/properties/pdbe.ts index 5ae5d369f826c481ff9570562d600a5759cc008d..acda123fe369344a804c4edfb3f4a8d7abfe9200 100644 --- a/src/servers/model/properties/pdbe.ts +++ b/src/servers/model/properties/pdbe.ts @@ -10,7 +10,7 @@ import { fetchRetry } from '../utils/fetch-retry'; export function PDBe_structureQualityReport(model: Model) { return StructureQualityReport.attachFromCifOrApi(model, { - pdbEapiSourceJson: async model => { + PDBe_apiSourceJson: async model => { const rawData = await fetchRetry(`https://www.ebi.ac.uk/pdbe/api/validation/residuewise_outlier_summary/entry/${model.label.toLowerCase()}`, 1500, 5); return await rawData.json(); }