Ver código fonte

wip refactoring custom props

David Sehnal 6 anos atrás
pai
commit
e2a7ca6da6

+ 108 - 13
src/mol-model-props/pdbe/structure-quality-report.ts

@@ -36,10 +36,50 @@ export namespace StructureQualityReport {
                 {
                     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;
+                        }
+
                         return {
                             fields: _structure_quality_report_issues_fields,
-                            source: ctx.structures.map(s => IndexedCustomProperty.getCifDataSource(s, StructureQualityReport.getIssueMap(s.model), ctx.cache))
-                        };
+                            source: [{ data: groupCtx, rowCount: groupCtx.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))
+                        // };
                     }
                 }]
         },
@@ -58,13 +98,20 @@ export namespace StructureQualityReport {
             id: Column.Schema.int,
             ...mmCIF_residueId_schema,
             pdbx_PDB_model_num: Column.Schema.int,
-            issues: Column.Schema.List(',', x => x)
+            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 toTable(Schema.pdbe_structure_quality_report_issues, model.sourceData.frame.categories.pdbe_structure_quality_report_issues);
+        return {
+            residues: toTable(Schema.pdbe_structure_quality_report_issues, model.sourceData.frame.categories.pdbe_structure_quality_report_issues),
+            groups: toTable(Schema.pdbe_structure_quality_report_issue_types, model.sourceData.frame.categories.pdbe_structure_quality_report_issue_types),
+        }
     }
 
     export async function attachFromCifOrApi(model: Model, params: {
@@ -77,7 +124,7 @@ export namespace StructureQualityReport {
         let info = PropertyWrapper.tryGetInfoFromCif('pdbe_structure_quality_report', model);
         if (info) {
             const data = getCifData(model);
-            issueMap = createIssueMapFromCif(model, data);
+            issueMap = createIssueMapFromCif(model, data.residues, data.groups);
         } else if (params.PDBe_apiSourceJson) {
             const data = await params.PDBe_apiSourceJson(model);
             if (!data) return false;
@@ -112,12 +159,39 @@ export namespace StructureQualityReport {
 }
 
 type ExportCtx = IndexedCustomProperty.ExportCtx<string[]>
-const _structure_quality_report_issues_fields: CifField<number, ExportCtx>[] = CifWriter.fields()
+const _structure_quality_report_issues_fields: CifField<number, ReportExportContext>[] = CifWriter.fields<number, ReportExportContext>()
     .index('id')
-    .many(residueIdFields((i, d) => d.elements[i]))
-    .int('pdbx_PDB_model_num', (i, d) => P.unit.model_num(d.elements[i]))
-    .str('issues', (i, d) => d.property(i).join(','))
-    .getFields()
+    .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][]
+}
+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 {
+    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]);
+            }
+        }
+        groupId[i] = groupMap.get(key)!;
+    }
+    return { ...ctx, groupId, rows };
+}
 
 function createIssueMapFromJson(modelData: Model, data: any): StructureQualityReport.IssueMap | undefined {
     const ret = new Map<ResidueIndex, string[]>();
@@ -143,15 +217,36 @@ function createIssueMapFromJson(modelData: Model, data: any): StructureQualityRe
     return IndexedCustomProperty.fromResidueMap(ret);
 }
 
-function createIssueMapFromCif(modelData: Model, data: Table<typeof StructureQualityReport.Schema.pdbe_structure_quality_report_issues>): StructureQualityReport.IssueMap | undefined {
+function createIssueMapFromCif(modelData: Model,
+    residueData: Table<typeof StructureQualityReport.Schema.pdbe_structure_quality_report_issues>,
+    groupData: Table<typeof StructureQualityReport.Schema.pdbe_structure_quality_report_issue_types>): StructureQualityReport.IssueMap | undefined {
+
     const ret = new Map<ResidueIndex, string[]>();
-    const { label_entity_id, label_asym_id, auth_seq_id, pdbx_PDB_ins_code, issues, pdbx_PDB_model_num, _rowCount } = data;
+    const { label_entity_id, label_asym_id, auth_seq_id, pdbx_PDB_ins_code, issue_group_id, pdbx_PDB_model_num, _rowCount } = residueData;
+
+    const groups = parseIssueTypes(groupData);
 
     for (let i = 0; i < _rowCount; i++) {
         if (pdbx_PDB_model_num.value(i) !== modelData.modelNum) continue;
         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));
-        ret.set(idx, issues.value(i));
+        ret.set(idx, groups.get(issue_group_id.value(i))!);
     }
 
     return IndexedCustomProperty.fromResidueMap(ret);
+}
+
+function parseIssueTypes(groupData: Table<typeof StructureQualityReport.Schema.pdbe_structure_quality_report_issue_types>): Map<number, string[]> {
+    const ret = new Map<number, string[]>();
+    const { group_id, issue_type } = groupData;
+    for (let i = 0; i < groupData._rowCount; i++) {
+        let group: string[];
+        const id = group_id.value(i);
+        if (ret.has(id)) group = ret.get(id)!;
+        else {
+            group = [];
+            ret.set(id, group);
+        }
+        group.push(issue_type.value(i));
+    }
+    return ret;
 }

+ 34 - 14
src/mol-model/structure/export/categories/atom_site.ts

@@ -74,9 +74,21 @@ function mappedProp<K, D>(loc: (key: K, data: D) => StructureElement, prop: (e:
     return (k: K, d: D) => prop(loc(k, d));
 }
 
-export function residueIdFields<K, D>(getLocation: (key: K, data: D) => StructureElement, options?: { prefix?: string, postfix?: string }): CifField<K, D>[] {
+function addModelNum<K, D>(fields: CifWriter.Field.Builder<K, D>, getLocation: (key: K, data: D) => StructureElement, options?: IdFieldsOptions) {
+    if (options && options.includeModelNum) {
+        fields.int('pdbx_PDB_model_num', mappedProp(getLocation, P.unit.model_num));
+    }
+}
+
+export interface IdFieldsOptions {
+    prefix?: string,
+    postfix?: string,
+    includeModelNum?: boolean
+}
+
+export function residueIdFields<K, D>(getLocation: (key: K, data: D) => StructureElement, options?: IdFieldsOptions): CifField<K, D>[] {
     const prefix = options && options.prefix, postfix = options && options.postfix;
-    return CifWriter.fields<K, D>()
+    const ret = CifWriter.fields<K, D>()
         .str(prepostfixed(prefix, postfix, `label_comp_id`), mappedProp(getLocation, P.residue.label_comp_id))
         .int(prepostfixed(prefix, postfix, `label_seq_id`), mappedProp(getLocation, P.residue.label_seq_id), {
             encoder: E.deltaRLE,
@@ -93,29 +105,35 @@ export function residueIdFields<K, D>(getLocation: (key: K, data: D) => Structur
 
         .str(prepostfixed(prefix, postfix, `auth_comp_id`), mappedProp(getLocation, P.residue.auth_comp_id))
         .int(prepostfixed(prefix, postfix, `auth_seq_id`), mappedProp(getLocation, P.residue.auth_seq_id), { encoder: E.deltaRLE })
-        .str(prepostfixed(prefix, postfix, `auth_asym_id`), mappedProp(getLocation, P.chain.auth_asym_id))
-        .getFields();
+        .str(prepostfixed(prefix, postfix, `auth_asym_id`), mappedProp(getLocation, P.chain.auth_asym_id));
+
+    addModelNum(ret, getLocation, options);
+    return ret.getFields();
 }
 
-export function chainIdFields<K, D>(getLocation: (key: K, data: D) => StructureElement, options?: { prefix?: string, postfix?: string }): CifField<K, D>[] {
+export function chainIdFields<K, D>(getLocation: (key: K, data: D) => StructureElement, options?: IdFieldsOptions): CifField<K, D>[] {
     const prefix = options && options.prefix, postfix = options && options.postfix;
-    return CifField.build<K, D>()
+    const ret = CifField.build<K, D>()
         .str(prepostfixed(prefix, postfix, `label_asym_id`), mappedProp(getLocation, P.chain.label_asym_id))
         .str(prepostfixed(prefix, postfix, `label_entity_id`), mappedProp(getLocation, P.chain.label_entity_id))
         .str(prepostfixed(prefix, postfix, `auth_asym_id`), mappedProp(getLocation, P.chain.auth_asym_id))
-        .getFields();
+
+    addModelNum(ret, getLocation, options);
+    return ret.getFields();
 }
 
-export function entityIdFields<K, D>(getLocation: (key: K, data: D) => StructureElement, options?: { prefix?: string, postfix?: string }): CifField<K, D>[] {
+export function entityIdFields<K, D>(getLocation: (key: K, data: D) => StructureElement, options?: IdFieldsOptions): CifField<K, D>[] {
     const prefix = options && options.prefix, postfix = options && options.postfix;
-    return CifField.build<K, D>()
+    const ret = CifField.build<K, D>()
         .str(prepostfixed(prefix, postfix, `label_entity_id`), mappedProp(getLocation, P.chain.label_entity_id))
-        .getFields();
+
+    addModelNum(ret, getLocation, options);
+    return ret.getFields();
 }
 
-export function atomIdFields<K, D>(getLocation: (key: K, data: D) => StructureElement, options?: { prefix?: string, postfix?: string }): CifField<K, D>[] {
+export function atomIdFields<K, D>(getLocation: (key: K, data: D) => StructureElement, options?: IdFieldsOptions): CifField<K, D>[] {
     const prefix = options && options.prefix, postfix = options && options.postfix;
-    return CifWriter.fields<K, D>()
+    const ret = CifWriter.fields<K, D>()
         .str(prepostfixed(prefix, postfix, `label_atom_id`), mappedProp(getLocation, P.atom.label_atom_id))
         .str(prepostfixed(prefix, postfix, `label_comp_id`), mappedProp(getLocation, P.residue.label_comp_id))
         .int(prepostfixed(prefix, postfix, `label_seq_id`), mappedProp(getLocation, P.residue.label_seq_id), {
@@ -135,6 +153,8 @@ export function atomIdFields<K, D>(getLocation: (key: K, data: D) => StructureEl
         .str(prepostfixed(prefix, postfix, `auth_atom_id`), mappedProp(getLocation, P.atom.auth_atom_id))
         .str(prepostfixed(prefix, postfix, `auth_comp_id`), mappedProp(getLocation, P.residue.auth_comp_id))
         .int(prepostfixed(prefix, postfix, `auth_seq_id`), mappedProp(getLocation, P.residue.auth_seq_id), { encoder: E.deltaRLE })
-        .str(prepostfixed(prefix, postfix, `auth_asym_id`), mappedProp(getLocation, P.chain.auth_asym_id))
-        .getFields();
+        .str(prepostfixed(prefix, postfix, `auth_asym_id`), mappedProp(getLocation, P.chain.auth_asym_id));
+
+    addModelNum(ret, getLocation, options);
+    return ret.getFields();
 }

+ 12 - 12
src/servers/model/test.ts

@@ -43,26 +43,26 @@ async function run() {
     try {
         // const testFile = '1crn.cif'
         const testFile = '1cbs_updated.cif'
-        const request = createJob({
-            entryId: path.join(examplesPath, testFile),
-            queryName: 'full',
-            queryParams: { }
-        });
         // const request = createJob({
         //     entryId: path.join(examplesPath, testFile),
-        //     queryName: 'atoms',
-        //     queryParams: {
-        //         atom_site: { label_comp_id: 'ALA' }
-        //     }
+        //     queryName: 'full',
+        //     queryParams: { }
         // });
         // const request = createJob({
         //     entryId: path.join(examplesPath, testFile),
-        //     queryName: 'residueInteraction',
+        //     queryName: 'atoms',
         //     queryParams: {
-        //         atom_site: { label_comp_id: 'REA' },
-        //         radius: 5
+        //         atom_site: { label_comp_id: 'ALA' }
         //     }
         // });
+        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);