From 8e2b3ebb0dd783d353b7ed5d198127ca3daa9120 Mon Sep 17 00:00:00 2001 From: David Sehnal <david.sehnal@gmail.com> Date: Tue, 2 Oct 2018 13:57:14 +0200 Subject: [PATCH] wip refactoring custom props --- src/mol-model-props/common/wrapper.ts | 12 +- .../pdbe/structure-quality-report.ts | 152 ++++++++---------- src/mol-model/structure/export/mmcif.ts | 13 +- .../model/properties/custom/collection.ts | 2 +- .../model/properties/custom/descriptor.ts | 17 +- .../model/properties/custom/indexed.ts | 12 +- src/mol-script/runtime/query/compiler.ts | 2 +- src/servers/model/test.ts | 28 ++-- 8 files changed, 124 insertions(+), 114 deletions(-) diff --git a/src/mol-model-props/common/wrapper.ts b/src/mol-model-props/common/wrapper.ts index 6265ca850..2f22e742f 100644 --- a/src/mol-model-props/common/wrapper.ts +++ b/src/mol-model-props/common/wrapper.ts @@ -6,7 +6,6 @@ import { CifWriter } from 'mol-io/writer/cif'; import { Model } from 'mol-model/structure'; -import { CifExportContext } from 'mol-model/structure'; import { dateToUtcString } from 'mol-util/date'; interface PropertyWrapper<Data> { @@ -23,22 +22,21 @@ namespace PropertyWrapper { return { timestamp_utc: dateToUtcString(new Date()) }; } - export function defaultInfoCategory(name: string, getter: (model: Model) => PropertyWrapper<unknown> | undefined): CifWriter.Category<CifExportContext> { + export function defaultInfoCategory<Ctx>(name: string, getter: (ctx: Ctx) => Info | undefined): CifWriter.Category<Ctx> { return { name, instance(ctx) { - const prop = getter(ctx.firstModel); - if (!prop) return CifWriter.Category.Empty; + const info = getter(ctx); return { fields: _info_fields, - source: [{ data: prop.info.timestamp_utc, rowCount: 1 }] + source: [{ data: info, rowCount: 1 }] }; } } } - const _info_fields: CifWriter.Field<number, string>[] = [ - CifWriter.Field.str('updated_datetime_utc', (_, date) => date) + const _info_fields: CifWriter.Field<number, Info>[] = [ + CifWriter.Field.str('updated_datetime_utc', (_, date) => date.timestamp_utc) ]; export function tryGetInfoFromCif(categoryName: string, model: Model): Info | undefined { diff --git a/src/mol-model-props/pdbe/structure-quality-report.ts b/src/mol-model-props/pdbe/structure-quality-report.ts index 0cbf120d9..c4e8de133 100644 --- a/src/mol-model-props/pdbe/structure-quality-report.ts +++ b/src/mol-model-props/pdbe/structure-quality-report.ts @@ -8,14 +8,13 @@ import { Column, Table } from 'mol-data/db'; import { toTable } from 'mol-io/reader/cif/schema'; import { mmCIF_residueId_schema } from 'mol-io/reader/cif/schema/mmcif-extras'; import { CifWriter } from 'mol-io/writer/cif'; -import { Model, ModelPropertyDescriptor, ResidueIndex, StructureProperties as P, Unit, IndexedCustomProperty } from 'mol-model/structure'; +import { Model, ModelPropertyDescriptor, ResidueIndex, Unit, IndexedCustomProperty } from 'mol-model/structure'; import { residueIdFields } from 'mol-model/structure/export/categories/atom_site'; -import { StructureElement } from 'mol-model/structure/structure'; +import { StructureElement, CifExportContext } from 'mol-model/structure/structure'; import { CustomPropSymbol } from 'mol-script/language/symbol'; import Type from 'mol-script/language/type'; import { QuerySymbolRuntime } from 'mol-script/runtime/query/compiler'; import { PropertyWrapper } from '../common/wrapper'; -import CifField = CifWriter.Field; export namespace StructureQualityReport { export type IssueMap = IndexedCustomProperty.Residue<string[]> @@ -26,60 +25,45 @@ export namespace StructureQualityReport { return model._dynamicPropertyData.__StructureQualityReport__; } + 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, + pdbx_PDB_model_num: Column.Schema.int, + issue_group_id: Column.Schema.int + }, + pdbe_structure_quality_report_issue_types: { + group_id: Column.Schema.int, + issue_type: Column.Schema.str + } + }; + export type Schema = typeof Schema + export const Descriptor = ModelPropertyDescriptor({ isStatic: false, name: 'structure_quality_report', cifExport: { prefix: 'pdbe', + context(ctx) { + return createExportContext(ctx); + }, categories: [ - PropertyWrapper.defaultInfoCategory('pdbe_structure_quality_report', StructureQualityReport.get), + PropertyWrapper.defaultInfoCategory<ReportExportContext>('pdbe_structure_quality_report', ctx => ctx.info), { name: 'pdbe_structure_quality_report_issues', - instance(ctx) { - const prop = get(ctx.firstModel); - if (!prop) return CifWriter.Category.Empty; - - let groupCtx: ReportExportContext; - if (ctx.cache.pdbe_structure_quality_report_issues) groupCtx = ctx.cache.pdbe_structure_quality_report_issues; - else { - const exportCtx = prop.data!.getExportContext(ctx.structures[0]); - groupCtx = createExportContext(exportCtx); - ctx.cache.pdbe_structure_quality_report_issues = groupCtx; - } - + instance(ctx: ReportExportContext) { return { fields: _structure_quality_report_issues_fields, - source: [{ data: groupCtx, rowCount: groupCtx.elements.length }] + source: ctx.models.map(data => ({ data, rowCount: data.elements.length })) } - - // return { - // fields: _structure_quality_report_issues_fields, - // source: ctx.structures.map(s => IndexedCustomProperty.getCifDataSource(s, StructureQualityReport.getIssueMap(s.model), ctx.cache)) - // }; } }, { name: 'pdbe_structure_quality_report_issue_types', - instance(ctx) { - const prop = get(ctx.firstModel); - if (!prop) return CifWriter.Category.Empty; - - let groupCtx: ReportExportContext; - if (ctx.cache.pdbe_structure_quality_report_issues) groupCtx = ctx.cache.pdbe_structure_quality_report_issues; - else { - const exportCtx = prop.data!.getExportContext(ctx.structures[0]); - groupCtx = createExportContext(exportCtx); - ctx.cache.pdbe_structure_quality_report_issues = groupCtx; - } - - return { - fields: _structure_quality_report_issue_types_fields, - source: [{ data: groupCtx, rowCount: groupCtx.rows.length }] - } - - // return { - // fields: _structure_quality_report_issues_fields, - // source: ctx.structures.map(s => IndexedCustomProperty.getCifDataSource(s, StructureQualityReport.getIssueMap(s.model), ctx.cache)) - // }; + instance(ctx: ReportExportContext) { + return CifWriter.Category.ofTable(ctx.issueTypes); } }] }, @@ -90,22 +74,6 @@ export namespace StructureQualityReport { } }); - 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, - pdbx_PDB_model_num: Column.Schema.int, - issue_group_id: Column.Schema.int - }, - pdbe_structure_quality_report_issue_types: { - group_id: Column.Schema.int, - issue_type: Column.Schema.str - } - } - function getCifData(model: Model) { if (model.sourceData.kind !== 'mmCIF') throw new Error('Data format must be mmCIF.'); return { @@ -115,7 +83,7 @@ export namespace StructureQualityReport { } export async function attachFromCifOrApi(model: Model, params: { - // provide JSON from api + // optional JSON source PDBe_apiSourceJson?: (model: Model) => Promise<any> }) { if (get(model)) return true; @@ -158,39 +126,57 @@ export namespace StructureQualityReport { } } -type ExportCtx = IndexedCustomProperty.ExportCtx<string[]> -const _structure_quality_report_issues_fields: CifField<number, ReportExportContext>[] = CifWriter.fields<number, ReportExportContext>() +const _structure_quality_report_issues_fields = CifWriter.fields<number, ReportExportContext['models'][0]>() .index('id') .many(residueIdFields((i, d) => d.elements[i], { includeModelNum: true })) .int('group_id', (i, d) => d.groupId[i]) .getFields(); -interface ReportExportContext extends ExportCtx { - groupId: number[], - rows: [number, string][] +interface ReportExportContext { + models: { + elements: StructureElement[], + groupId: number[] + }[], + info: PropertyWrapper.Info, + issueTypes: Table<StructureQualityReport.Schema['pdbe_structure_quality_report_issue_types']>, } -const _structure_quality_report_issue_types_fields: CifField<number, ReportExportContext>[] = CifWriter.fields<number, ReportExportContext>() - .int('group_id', (i, d) => d.rows[i][0]) - .str('issue_type', (i, d) => d.rows[i][1]) - .getFields(); -function createExportContext(ctx: ExportCtx): ReportExportContext { +function createExportContext(ctx: CifExportContext): ReportExportContext { const groupMap = new Map<string, number>(); - const groupId: number[] = []; - const rows: ReportExportContext['rows'] = []; - for (let i = 0; i < ctx.elements.length; i++) { - const issues = ctx.property(i); - const key = issues.join(','); - if (!groupMap.has(key)) { - const idx = groupMap.size + 1; - groupMap.set(key, idx); - for (const issue of issues) { - rows.push([idx, issue]); + const models: ReportExportContext['models'] = []; + const group_id: number[] = [], issue_type: string[] = []; + let info: PropertyWrapper.Info = PropertyWrapper.createInfo(); + + for (const s of ctx.structures) { + const prop = StructureQualityReport.get(s.model); + if (prop) info = prop.info; + if (!prop || !prop.data) continue; + + const { elements, property } = prop.data.getElements(s); + if (elements.length === 0) continue; + + const elementGroupId: number[] = []; + for (let i = 0; i < elements.length; i++) { + const issues = property(i); + const key = issues.join(','); + if (!groupMap.has(key)) { + const idx = groupMap.size + 1; + groupMap.set(key, idx); + for (const issue of issues) { + group_id.push(idx); + issue_type.push(issue); + } } + elementGroupId[i] = groupMap.get(key)!; } - groupId[i] = groupMap.get(key)!; + models.push({ elements, groupId: elementGroupId }); + } + + return { + info, + models, + issueTypes: Table.ofArrays(StructureQualityReport.Schema.pdbe_structure_quality_report_issue_types, { group_id, issue_type }) } - return { ...ctx, groupId, rows }; } function createIssueMapFromJson(modelData: Model, data: any): StructureQualityReport.IssueMap | undefined { @@ -232,6 +218,8 @@ function createIssueMapFromCif(modelData: Model, ret.set(idx, groups.get(issue_group_id.value(i))!); } + console.log(ret); + return IndexedCustomProperty.fromResidueMap(ret); } diff --git a/src/mol-model/structure/export/mmcif.ts b/src/mol-model/structure/export/mmcif.ts index 7ce1fcf7b..40dd6985c 100644 --- a/src/mol-model/structure/export/mmcif.ts +++ b/src/mol-model/structure/export/mmcif.ts @@ -16,6 +16,7 @@ import { _chem_comp, _pdbx_chem_comp_identifier } from './categories/misc'; import { Model } from '../model'; import { getUniqueEntityIndicesFromStructures } from './categories/utils'; import { _struct_asym, _entity_poly, _entity_poly_seq } from './categories/sequence'; +import { ModelPropertyDescriptor } from '../model/properties/custom'; export interface CifExportContext { structures: Structure[], @@ -126,10 +127,20 @@ export function encode_mmCIF_categories(encoder: CifWriter.Encoder, structures: const prefix = customProp.cifExport.prefix; const cats = customProp.cifExport.categories; + + let propCtx = ctx; + if (customProp.cifExport.context) { + const propId = ModelPropertyDescriptor.getUUID(customProp); + if (ctx.cache[propId + '__ctx']) propCtx = ctx.cache[propId + '__ctx']; + else { + propCtx = customProp.cifExport.context(ctx) || ctx; + ctx.cache[propId + '__ctx'] = propCtx; + } + } for (const cat of cats) { if (_params.skipCategoryNames && _params.skipCategoryNames.has(cat.name)) continue; if (cat.name.indexOf(prefix) !== 0) throw new Error(`Custom category '${cat.name}' name must start with prefix '${prefix}.'`); - encoder.writeCategory(cat, ctx); + encoder.writeCategory(cat, propCtx); } } } diff --git a/src/mol-model/structure/model/properties/custom/collection.ts b/src/mol-model/structure/model/properties/custom/collection.ts index 053131e17..d216e089b 100644 --- a/src/mol-model/structure/model/properties/custom/collection.ts +++ b/src/mol-model/structure/model/properties/custom/collection.ts @@ -14,7 +14,7 @@ export class CustomProperties { return this._list; } - add(desc: ModelPropertyDescriptor) { + add(desc: ModelPropertyDescriptor<any>) { if (this._set.has(desc)) return; this._list.push(desc); diff --git a/src/mol-model/structure/model/properties/custom/descriptor.ts b/src/mol-model/structure/model/properties/custom/descriptor.ts index 061f376bc..3dcb5d9f3 100644 --- a/src/mol-model/structure/model/properties/custom/descriptor.ts +++ b/src/mol-model/structure/model/properties/custom/descriptor.ts @@ -7,23 +7,34 @@ import { CifWriter } from 'mol-io/writer/cif' import { CifExportContext } from '../../../export/mmcif'; import { QuerySymbolRuntime } from 'mol-script/runtime/query/compiler'; +import { UUID } from 'mol-util'; -interface ModelPropertyDescriptor<Symbols extends { [name: string]: QuerySymbolRuntime } = { }> { +interface ModelPropertyDescriptor<ExportCtx = CifExportContext, Symbols extends { [name: string]: QuerySymbolRuntime } = { }> { readonly isStatic: boolean, readonly name: string, cifExport?: { // Prefix enforced during export. prefix: string, - categories: CifWriter.Category<CifExportContext>[] + context?: (ctx: CifExportContext) => ExportCtx | undefined, + categories: CifWriter.Category<ExportCtx>[] }, // TODO: add aliases when lisp-like mol-script is done symbols?: Symbols } -function ModelPropertyDescriptor<Desc extends ModelPropertyDescriptor>(desc: Desc) { +function ModelPropertyDescriptor<Ctx, Desc extends ModelPropertyDescriptor<Ctx>>(desc: Desc) { return desc; } +namespace ModelPropertyDescriptor { + export function getUUID(prop: ModelPropertyDescriptor): UUID { + if (!(prop as any).__key) { + (prop as any).__key = UUID.create(); + } + return (prop as any).__key; + } +} + export { ModelPropertyDescriptor } \ No newline at end of file diff --git a/src/mol-model/structure/model/properties/custom/indexed.ts b/src/mol-model/structure/model/properties/custom/indexed.ts index 36fb91cb7..7fbe42869 100644 --- a/src/mol-model/structure/model/properties/custom/indexed.ts +++ b/src/mol-model/structure/model/properties/custom/indexed.ts @@ -17,14 +17,14 @@ export interface IndexedCustomProperty<Idx extends IndexedCustomProperty.Index, readonly level: IndexedCustomProperty.Level, has(idx: Idx): boolean, get(idx: Idx): T | undefined, - getExportContext(structure: Structure): IndexedCustomProperty.ExportCtx<T> + getElements(structure: Structure): IndexedCustomProperty.Elements<T> } export namespace IndexedCustomProperty { export type Index = ElementIndex | ResidueIndex | ChainIndex | EntityIndex export type Level = 'atom' | 'residue' | 'chain' | 'entity' - export interface ExportCtx<T> { + export interface Elements<T> { elements: StructureElement[], property(index: number): T } @@ -32,7 +32,7 @@ export namespace IndexedCustomProperty { export function getCifDataSource<Idx extends Index, T>(structure: Structure, prop: IndexedCustomProperty<Idx, T> | undefined, cache: any): CifWriter.Category.Instance['source'][0] { if (!prop) return { rowCount: 0 }; if (cache && cache[prop.id]) return cache[prop.id]; - const data = prop.getExportContext(structure); + const data = prop.getElements(structure); const ret = { data, rowCount: data.elements.length }; if (cache) cache[prop.id] = ret; return ret; @@ -117,7 +117,7 @@ class SegmentedMappedIndexedCustomProperty<Idx extends IndexedCustomProperty.Ind return loci; } - getExportContext(structure: Structure): IndexedCustomProperty.ExportCtx<T> { + getElements(structure: Structure): IndexedCustomProperty.Elements<T> { const index = this.segmentGetter(structure.model).index; const elements = this.getStructureElements(structure); return { elements, property: i => this.get(index[elements[i].element])! }; @@ -162,7 +162,7 @@ class ElementMappedCustomProperty<T = any> implements IndexedCustomProperty<Elem return loci; } - getExportContext(structure: Structure): IndexedCustomProperty.ExportCtx<T> { + getElements(structure: Structure): IndexedCustomProperty.Elements<T> { const elements = this.getStructureElements(structure); return { elements, property: i => this.get(elements[i].element)! }; } @@ -210,7 +210,7 @@ class EntityMappedCustomProperty<T = any> implements IndexedCustomProperty<Entit return loci; } - getExportContext(structure: Structure): IndexedCustomProperty.ExportCtx<T> { + getElements(structure: Structure): IndexedCustomProperty.Elements<T> { const elements = this.getStructureElements(structure); const chainIndex = structure.model.atomicHierarchy.chainAtomSegments.index; const index = structure.model.atomicHierarchy.index; diff --git a/src/mol-script/runtime/query/compiler.ts b/src/mol-script/runtime/query/compiler.ts index 70b805d03..ce49a29c3 100644 --- a/src/mol-script/runtime/query/compiler.ts +++ b/src/mol-script/runtime/query/compiler.ts @@ -18,7 +18,7 @@ export class QueryRuntimeTable { this.map.set(runtime.symbol.id, runtime); } - addCustomProp(desc: ModelPropertyDescriptor) { + addCustomProp(desc: ModelPropertyDescriptor<any>) { if (!desc.symbols) return; for (const k of Object.keys(desc.symbols)) { diff --git a/src/servers/model/test.ts b/src/servers/model/test.ts index 7ce94b2ae..ffb947e97 100644 --- a/src/servers/model/test.ts +++ b/src/servers/model/test.ts @@ -42,27 +42,29 @@ if (!fs.existsSync(outPath)) fs.mkdirSync(outPath); async function run() { try { // const testFile = '1crn.cif' - const testFile = '1cbs_updated.cif' + // const testFile = '1grm_updated.cif' + const testFile = 'C:/Projects/mol-star/molstar-proto/build/test/1grm_updated.cif' + const request = createJob({ + entryId: testFile, //path.join(examplesPath, testFile), + queryName: 'full', + queryParams: { }, + options: { modelNums: [ 2, 3 ] } + }); // const request = createJob({ // entryId: path.join(examplesPath, testFile), - // queryName: 'full', - // queryParams: { } + // queryName: 'atoms', + // queryParams: { + // atom_site: { label_comp_id: 'ALA' } + // } // }); // const request = createJob({ // entryId: path.join(examplesPath, testFile), - // queryName: 'atoms', + // queryName: 'residueInteraction', // queryParams: { - // atom_site: { label_comp_id: 'ALA' } + // atom_site: { label_comp_id: 'REA' }, + // radius: 5 // } // }); - const request = createJob({ - entryId: path.join(examplesPath, testFile), - queryName: 'residueInteraction', - queryParams: { - atom_site: { label_comp_id: 'REA' }, - radius: 5 - } - }); const encoder = await resolveJob(request); const writer = wrapFile(path.join(outPath, testFile)); encoder.writeTo(writer); -- GitLab