diff --git a/src/mol-model-props/pdbe/preferred-assembly.ts b/src/mol-model-props/pdbe/preferred-assembly.ts new file mode 100644 index 0000000000000000000000000000000000000000..fb5f5dc69d30b0ee3f12611aa9890086bed3fd8c --- /dev/null +++ b/src/mol-model-props/pdbe/preferred-assembly.ts @@ -0,0 +1,89 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import { Column, Table } from 'mol-data/db'; +import { toTable } from 'mol-io/reader/cif/schema'; +import { CifWriter } from 'mol-io/writer/cif'; +import { Model, ModelPropertyDescriptor } from 'mol-model/structure'; + +export namespace PDBePreferredAssembly { + export type Property = string + + export function getFirstFromModel(model: Model): Property { + const asm = model.symmetry.assemblies; + return asm.length ? asm[0].id : ''; + } + + export function get(model: Model): Property { + return model._staticPropertyData.__PDBePreferredAssebly__ || getFirstFromModel(model); + } + function set(model: Model, prop: Property) { + (model._staticPropertyData.__PDBePreferredAssebly__ as Property) = prop; + } + + export const Schema = { + pdbe_preferred_assembly: { + assembly_id: Column.Schema.str + } + }; + export type Schema = typeof Schema + + export const Descriptor = ModelPropertyDescriptor({ + isStatic: true, + name: 'pdbe_preferred_assembly', + cifExport: { + prefix: 'pdbe', + context(ctx): Property { return get(ctx.firstModel); }, + categories: [{ + name: 'pdbe_preferred_assembly', + instance(ctx: Property) { + return CifWriter.Category.ofTable(Table.ofArrays(Schema.pdbe_preferred_assembly, { assembly_id: [ctx] })); + } + }] + } + }); + + function fromCifData(model: Model): string | undefined { + if (model.sourceData.kind !== 'mmCIF') return void 0; + const cat = model.sourceData.frame.categories.pdbe_preferred_assembly; + if (!cat) return void 0; + return toTable(Schema.pdbe_preferred_assembly, cat).assembly_id.value(0) || getFirstFromModel(model); + } + + export async function attachFromCifOrApi(model: Model, params: { + // optional JSON source + PDBe_apiSourceJson?: (model: Model) => Promise<any> + }) { + if (model.customProperties.has(Descriptor)) return true; + + let asmName: string | undefined = fromCifData(model); + if (asmName === void 0 && params.PDBe_apiSourceJson) { + const data = await params.PDBe_apiSourceJson(model); + if (!data) return false; + asmName = asmNameFromJson(model, data); + } else { + return false; + } + + if (!asmName) return false; + + model.customProperties.add(Descriptor); + set(model, asmName); + return true; + } +} + +function asmNameFromJson(modelData: Model, data: any): string { + const assemblies = data[0] && data[0].assemblies; + if (!assemblies || !assemblies.length) return PDBePreferredAssembly.getFirstFromModel(modelData); + + for (const asm of assemblies) { + if (asm.preferred) { + return asm.assembly_id; + } + } + return assemblies[0].assembly_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 4dfc0465fbb061fb52298aacadcb01b8bfeedcfb..a770aade4011dfe3204304459b3465f2c8340b85 100644 --- a/src/mol-model-props/pdbe/structure-quality-report.ts +++ b/src/mol-model-props/pdbe/structure-quality-report.ts @@ -22,7 +22,7 @@ export namespace StructureQualityReport { export function get(model: Model): Property | undefined { // must be defined before the descriptor so it's not undefined. - return model._dynamicPropertyData.__StructureQualityReport__; + return model._dynamicPropertyData.__PDBeStructureQualityReport__; } export const Schema = { @@ -44,7 +44,7 @@ export namespace StructureQualityReport { export const Descriptor = ModelPropertyDescriptor({ isStatic: false, - name: 'structure_quality_report', + name: 'pdbe_structure_quality_report', cifExport: { prefix: 'pdbe', context(ctx) { @@ -108,7 +108,7 @@ export namespace StructureQualityReport { } function set(model: Model, prop: Property) { - (model._dynamicPropertyData.__StructureQualityReport__ as Property) = prop; + (model._dynamicPropertyData.__PDBeStructureQualityReport__ as Property) = prop; } export function getIssueMap(model: Model): IssueMap | undefined { diff --git a/src/mol-model/structure/model/properties/custom/collection.ts b/src/mol-model/structure/model/properties/custom/collection.ts index d216e089bfbb202ec0833ddbd8dd0f572c8db501..e6602fe192108b654abfaaf90667d51a4e8a8482 100644 --- a/src/mol-model/structure/model/properties/custom/collection.ts +++ b/src/mol-model/structure/model/properties/custom/collection.ts @@ -21,7 +21,7 @@ export class CustomProperties { this._set.add(desc); } - has(desc: ModelPropertyDescriptor): boolean { + has(desc: ModelPropertyDescriptor<any>): boolean { return this._set.has(desc); } } \ No newline at end of file diff --git a/src/servers/model/properties/pdbe.ts b/src/servers/model/properties/pdbe.ts index aafc6675c22ccd12fdd7e0ef9af03bb51f1ed228..ca26bad669735f7fc2e9fea5ec5045093b56342c 100644 --- a/src/servers/model/properties/pdbe.ts +++ b/src/servers/model/properties/pdbe.ts @@ -6,12 +6,13 @@ */ import { Model } from 'mol-model/structure'; -import { PDBe_structureQualityReport } from './providers/pdbe'; +import { PDBe_structureQualityReport, PDBe_preferredAssembly } from './providers/pdbe'; export function attachModelProperties(model: Model, cache: object): Promise<any>[] { // return a list of promises that start attaching the props in parallel // (if there are downloads etc.) return [ - PDBe_structureQualityReport(model, cache) + PDBe_structureQualityReport(model, cache), + PDBe_preferredAssembly(model, cache) ]; } \ No newline at end of file diff --git a/src/servers/model/properties/providers/pdbe.ts b/src/servers/model/properties/providers/pdbe.ts index bec1c19332f271950b78eb2086eca8d27fdff77b..d0d60dc0bc3963ad9e90995e38fd11569a6388fc 100644 --- a/src/servers/model/properties/providers/pdbe.ts +++ b/src/servers/model/properties/providers/pdbe.ts @@ -9,35 +9,53 @@ import { Model } from 'mol-model/structure'; import { StructureQualityReport } from 'mol-model-props/pdbe/structure-quality-report'; import { fetchRetry } from '../../utils/fetch-retry'; import { UUID } from 'mol-util'; +import { PDBePreferredAssembly } from 'mol-model-props/pdbe/preferred-assembly'; const USE_FILE_SOURCE = false; export function PDBe_structureQualityReport(model: Model, cache: any) { return StructureQualityReport.attachFromCifOrApi(model, { PDBe_apiSourceJson: USE_FILE_SOURCE - ? residuewise_outlier_summary.getDataFromFile + ? residuewise_outlier_summary.getDataFromAggregateFile : residuewise_outlier_summary.getDataFromApiProvider(cache) }); } +export function PDBe_preferredAssembly(model: Model, cache: any) { + return PDBePreferredAssembly.attachFromCifOrApi(model, { + PDBe_apiSourceJson: USE_FILE_SOURCE + ? void 0 + : preferred_assembly.getDataFromApiProvider(cache) + }); +} + +namespace preferred_assembly { + export const getDataFromApiProvider = apiQueryProvider('https://www.ebi.ac.uk/pdbe/api/pdb/entry/summary'); +} + namespace residuewise_outlier_summary { const json = new Map<string, any>(); - export async function getDataFromFile(model: Model) { + export async function getDataFromAggregateFile(model: Model) { const key = `${model.label[1]}${model.label[2]}`; if (!json.has(key)) { const fn = `e:/test/mol-star/model/props/${key}.json`; if (!fs.existsSync(fn)) json.set(key, { }); + // TODO: use async readFile? else json.set(key, JSON.parse(fs.readFileSync(fn, 'utf8'))); } return json.get(key)![model.label.toLowerCase()] || { }; } - export function getDataFromApiProvider(cache: any) { + export const getDataFromApiProvider = apiQueryProvider('https://www.ebi.ac.uk/pdbe/api/validation/residuewise_outlier_summary/entry'); +} + +function apiQueryProvider(urlPrefix: string) { + return (cache: any) => { const cacheKey = UUID.create(); return async (model: Model) => { if (cache[cacheKey]) return cache[cacheKey]; - const rawData = await fetchRetry(`https://www.ebi.ac.uk/pdbe/api/validation/residuewise_outlier_summary/entry/${model.label.toLowerCase()}`, 1500, 5); + const rawData = await fetchRetry(`${urlPrefix}/${model.label.toLowerCase()}`, 1500, 5); const json = (await rawData.json())[model.label.toLowerCase()] || { }; cache[cacheKey] = json; return json;