Browse Source

wip refactoring custom props

David Sehnal 6 năm trước cách đây
mục cha
commit
8e2b3ebb0d

+ 5 - 7
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 {

+ 70 - 82
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);
 }
 

+ 12 - 1
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);
         }
     }
 }

+ 1 - 1
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);

+ 14 - 3
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 }

+ 6 - 6
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;

+ 1 - 1
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)) {

+ 15 - 13
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);