Selaa lähdekoodia

Refactoring custom model props

David Sehnal 6 vuotta sitten
vanhempi
commit
3e6f3c3c67

+ 5 - 0
src/mol-io/writer/cif/encoder.ts

@@ -95,6 +95,11 @@ export namespace Field {
             return this;
         }
 
+        many(fields: ArrayLike<Field<K, D>>) {
+            for (let i = 0; i < fields.length; i++) this.fields.push(fields[i]);
+            return this;
+        }
+
         getFields() { return this.fields; }
     }
 

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

@@ -8,7 +8,7 @@ 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, ResidueCustomProperty, ResidueIndex, StructureProperties as P, Unit } from 'mol-model/structure';
+import { Model, ModelPropertyDescriptor, ResidueIndex, StructureProperties as P, Unit, IndexedCustomProperty } from 'mol-model/structure';
 import { residueIdFields } from 'mol-model/structure/export/categories/atom_site';
 import { StructureElement } from 'mol-model/structure/structure';
 import { CustomPropSymbol } from 'mol-script/language/symbol';
@@ -18,7 +18,7 @@ import { PropertyWrapper } from '../common/wrapper';
 import CifField = CifWriter.Field;
 
 export namespace StructureQualityReport {
-    export type IssueMap = ResidueCustomProperty<string[]>
+    export type IssueMap = IndexedCustomProperty.Residue<string[]>
     export type Property = PropertyWrapper<IssueMap | undefined>
 
     export function get(model: Model): Property | undefined {
@@ -38,7 +38,7 @@ export namespace StructureQualityReport {
                     instance(ctx) {
                         return {
                             fields: _structure_quality_report_issues_fields,
-                            source: ctx.structures.map(s => ResidueCustomProperty.getCifDataSource(s, StructureQualityReport.getIssueMap(s.model), ctx.cache))
+                            source: ctx.structures.map(s => IndexedCustomProperty.getCifDataSource(s, StructureQualityReport.getIssueMap(s.model), ctx.cache))
                         };
                     }
                 }]
@@ -111,13 +111,13 @@ export namespace StructureQualityReport {
     }
 }
 
-type ExportCtx = ResidueCustomProperty.ExportCtx<string[]>
-const _structure_quality_report_issues_fields: CifField<number, ExportCtx>[] = [
-    CifField.index('id'),
-    ...residueIdFields<number, ExportCtx>((i, d) => d.elements[i]),
-    CifField.int<number, ExportCtx>('pdbx_PDB_model_num', (i, d) => P.unit.model_num(d.elements[i])),
-    CifField.str<number, ExportCtx>('issues', (i, d) => d.property(i).join(','))
-];
+type ExportCtx = IndexedCustomProperty.ExportCtx<string[]>
+const _structure_quality_report_issues_fields: CifField<number, ExportCtx>[] = CifWriter.fields()
+    .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()
 
 function createIssueMapFromJson(modelData: Model, data: any): StructureQualityReport.IssueMap | undefined {
     const ret = new Map<ResidueIndex, string[]>();
@@ -140,7 +140,7 @@ function createIssueMapFromJson(modelData: Model, data: any): StructureQualityRe
         }
     }
 
-    return ResidueCustomProperty.fromMap(ret, Unit.Kind.Atomic);
+    return IndexedCustomProperty.fromResidueMap(ret, Unit.Kind.Atomic);
 }
 
 function createIssueMapFromCif(modelData: Model, data: Table<typeof StructureQualityReport.Schema.pdbe_structure_quality_report_issues>): StructureQualityReport.IssueMap | undefined {
@@ -153,5 +153,5 @@ function createIssueMapFromCif(modelData: Model, data: Table<typeof StructureQua
         ret.set(idx, issues.value(i));
     }
 
-    return ResidueCustomProperty.fromMap(ret, Unit.Kind.Atomic);
+    return IndexedCustomProperty.fromResidueMap(ret, Unit.Kind.Atomic);
 }

+ 3 - 0
src/mol-model-props/rcsb/symmetry.ts

@@ -178,6 +178,9 @@ export namespace AssemblySymmetry {
 
         let db: Database
 
+        // TODO: there should be a "meta field" that indicates the property was added (see for example PDBe structure report)
+        // the reason for this is that the feature might not be present and therefore the "default" categories would
+        // not be created. This would result in an unnecessarily failed web request.
         if (model.sourceData.kind === 'mmCIF' && model.sourceData.frame.categoryNames.includes('rcsb_assembly_symmetry_feature')) {
             const rcsb_assembly_symmetry_feature = toTable(Schema.rcsb_assembly_symmetry_feature, model.sourceData.frame.categories.rcsb_assembly_symmetry_feature)
 

+ 1 - 1
src/mol-model/structure/model/properties/custom.ts

@@ -6,4 +6,4 @@
 
 export * from './custom/descriptor'
 export * from './custom/collection'
-export * from './custom/residue'
+export * from './custom/indexed'

+ 91 - 0
src/mol-model/structure/model/properties/custom/chain.ts

@@ -0,0 +1,91 @@
+// /**
+//  * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+//  *
+//  * @author David Sehnal <david.sehnal@gmail.com>
+//  */
+
+// import { ChainIndex } from '../../indexing';
+// import { Unit, Structure, StructureElement } from '../../../structure';
+// import { Segmentation } from 'mol-data/int';
+// import { UUID } from 'mol-util';
+// import { CifWriter } from 'mol-io/writer/cif';
+
+// export interface ChainCustomProperty<T = any> {
+//     readonly id: UUID,
+//     readonly kind: Unit.Kind,
+//     has(idx: ChainIndex): boolean
+//     get(idx: ChainIndex): T | undefined
+// }
+
+// export namespace ChainCustomProperty {
+//     export interface ExportCtx<T> {
+//         elements: StructureElement[],
+//         property(index: number): T
+//     };
+
+//     function getExportCtx<T>(prop: ChainCustomProperty<T>, structure: Structure): ExportCtx<T> {
+//         const chainIndex = structure.model.atomicHierarchy.chainAtomSegments.index;
+//         const elements = getStructureElements(structure, prop);
+//         return { elements, property: i => prop.get(chainIndex[elements[i].element])! };
+//     }
+
+//     export function getCifDataSource<T>(structure: Structure, prop: ChainCustomProperty<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 = getExportCtx(prop, structure);
+//         const ret = { data, rowCount: data.elements.length };
+//         if (cache) cache[prop.id] = ret;
+//         return ret;
+//     }
+
+//     class FromMap<T> implements ChainCustomProperty<T> {
+//         readonly id = UUID.create();
+
+//         has(idx: ChainIndex): boolean {
+//             return this.map.has(idx);
+//         }
+
+//         get(idx: ChainIndex) {
+//             return this.map.get(idx);
+//         }
+
+//         constructor(private map: Map<ChainIndex, T>, public kind: Unit.Kind) {
+//         }
+//     }
+
+//     export function fromMap<T>(map: Map<ChainIndex, T>, kind: Unit.Kind) {
+//         return new FromMap(map, kind);
+//     }
+
+//     /**
+//      * Gets all StructureElements that correspond to 1st atoms of residues that have an property assigned.
+//      * Only works correctly for structures with a single model.
+//      */
+//     export function getStructureElements(structure: Structure, property: ChainCustomProperty) {
+//         const models = structure.models;
+//         if (models.length !== 1) throw new Error(`Only works on structures with a single model.`);
+
+//         const seenChains = new Set<ChainIndex>();
+//         const unitGroups = structure.unitSymmetryGroups;
+//         const loci: StructureElement[] = [];
+
+//         for (const unitGroup of unitGroups) {
+//             const unit = unitGroup.units[0];
+//             if (unit.kind !== property.kind) {
+//                 continue;
+//             }
+
+//             const chains = Segmentation.transientSegments(unit.model.atomicHierarchy.chainAtomSegments, unit.elements);
+//             while (chains.hasNext) {
+//                 const seg = chains.move();
+//                 if (!property.has(seg.index) || seenChains.has(seg.index)) continue;
+
+//                 seenChains.add(seg.index);
+//                 loci[loci.length] = StructureElement.create(unit, unit.elements[seg.start]);
+//             }
+//         }
+
+//         loci.sort((x, y) => x.element - y.element);
+//         return loci;
+//     }
+// }

+ 88 - 0
src/mol-model/structure/model/properties/custom/indexed.ts

@@ -0,0 +1,88 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import { ResidueIndex, ChainIndex, ElementIndex } from '../../indexing';
+import { Unit, Structure, StructureElement } from '../../../structure';
+import { Segmentation } from 'mol-data/int';
+import { UUID } from 'mol-util';
+import { CifWriter } from 'mol-io/writer/cif';
+import { Model } from '../../model';
+
+export class IndexedCustomProperty<Idx extends IndexedCustomProperty.Index, T = any> {
+    readonly id: UUID = UUID.create();
+    readonly kind: Unit.Kind;
+    has(idx: Idx): boolean { return this.map.has(idx); }
+    get(idx: Idx) { return this.map.get(idx); }
+
+    private getStructureElements(structure: Structure) {
+        const models = structure.models;
+        if (models.length !== 1) throw new Error(`Only works on structures with a single model.`);
+
+        const seenIndices = new Set<Idx>();
+        const unitGroups = structure.unitSymmetryGroups;
+        const loci: StructureElement[] = [];
+
+        const segments = this.segmentGetter(models[0])
+
+        for (const unitGroup of unitGroups) {
+            const unit = unitGroup.units[0];
+            if (unit.kind !== this.kind) {
+                continue;
+            }
+
+            const chains = Segmentation.transientSegments(segments, unit.elements);
+            while (chains.hasNext) {
+                const seg = chains.move();
+                if (!this.has(seg.index) || seenIndices.has(seg.index)) continue;
+                seenIndices.add(seg.index);
+                loci[loci.length] = StructureElement.create(unit, unit.elements[seg.start]);
+            }
+        }
+
+        loci.sort((x, y) => x.element - y.element);
+        return loci;
+    }
+
+    getExportContext(structure: Structure): IndexedCustomProperty.ExportCtx<T> {
+        const index = this.segmentGetter(structure.model).index;
+        const elements = this.getStructureElements(structure);
+        return { elements, property: i => this.get(index[elements[i].element])! };
+    }
+
+    constructor(private map: Map<Idx, T>, private segmentGetter: (model: Model) => Segmentation<ElementIndex, Idx>, kind: Unit.Kind) {
+        this.kind = kind;
+    }
+}
+
+export namespace IndexedCustomProperty {
+    export type Index = ResidueIndex | ChainIndex
+
+    export interface ExportCtx<T> {
+        elements: StructureElement[],
+        property(index: number): T
+    }
+
+    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 ret = { data, rowCount: data.elements.length };
+        if (cache) cache[prop.id] = ret;
+        return ret;
+    }
+
+    export type Residue<T> = IndexedCustomProperty<ResidueIndex, T>
+    const getResidueSegments = (model: Model) => model.atomicHierarchy.residueAtomSegments;
+    export function fromResidueMap<T>(map: Map<ResidueIndex, T>, kind: Unit.Kind): Residue<T> {
+        return new IndexedCustomProperty(map, getResidueSegments, kind);
+    }
+
+    export type Chain<T> = IndexedCustomProperty<ChainIndex, T>
+    const getChainSegments = (model: Model) => model.atomicHierarchy.chainAtomSegments;
+    export function fromChainMap<T>(map: Map<ChainIndex, T>, kind: Unit.Kind): Chain<T> {
+        return new IndexedCustomProperty(map, getChainSegments, kind);
+    }
+}

+ 91 - 91
src/mol-model/structure/model/properties/custom/residue.ts

@@ -1,91 +1,91 @@
-/**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author David Sehnal <david.sehnal@gmail.com>
- */
-
-import { ResidueIndex } from '../../indexing';
-import { Unit, Structure, StructureElement } from '../../../structure';
-import { Segmentation } from 'mol-data/int';
-import { UUID } from 'mol-util';
-import { CifWriter } from 'mol-io/writer/cif';
-
-export interface ResidueCustomProperty<T = any> {
-    readonly id: UUID,
-    readonly kind: Unit.Kind,
-    has(idx: ResidueIndex): boolean
-    get(idx: ResidueIndex): T | undefined
-}
-
-export namespace ResidueCustomProperty {
-    export interface ExportCtx<T> {
-        elements: StructureElement[],
-        property(index: number): T
-    };
-
-    function getExportCtx<T>(prop: ResidueCustomProperty<T>, structure: Structure): ExportCtx<T> {
-        const residueIndex = structure.model.atomicHierarchy.residueAtomSegments.index;
-        const elements = getStructureElements(structure, prop);
-        return { elements, property: i => prop.get(residueIndex[elements[i].element])! };
-    }
-
-    export function getCifDataSource<T>(structure: Structure, prop: ResidueCustomProperty<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 = getExportCtx(prop, structure);
-        const ret = { data, rowCount: data.elements.length };
-        if (cache) cache[prop.id] = ret;
-        return ret;
-    }
-
-    class FromMap<T> implements ResidueCustomProperty<T> {
-        readonly id = UUID.create();
-
-        has(idx: ResidueIndex): boolean {
-            return this.map.has(idx);
-        }
-
-        get(idx: ResidueIndex) {
-            return this.map.get(idx);
-        }
-
-        constructor(private map: Map<ResidueIndex, T>, public kind: Unit.Kind) {
-        }
-    }
-
-    export function fromMap<T>(map: Map<ResidueIndex, T>, kind: Unit.Kind) {
-        return new FromMap(map, kind);
-    }
-
-    /**
-     * Gets all StructureElements that correspond to 1st atoms of residues that have an property assigned.
-     * Only works correctly for structures with a single model.
-     */
-    export function getStructureElements(structure: Structure, property: ResidueCustomProperty) {
-        const models = structure.models;
-        if (models.length !== 1) throw new Error(`Only works on structures with a single model.`);
-
-        const seenResidues = new Set<ResidueIndex>();
-        const unitGroups = structure.unitSymmetryGroups;
-        const loci: StructureElement[] = [];
-
-        for (const unitGroup of unitGroups) {
-            const unit = unitGroup.units[0];
-            if (unit.kind !== property.kind) {
-                continue;
-            }
-
-            const residues = Segmentation.transientSegments(unit.model.atomicHierarchy.residueAtomSegments, unit.elements);
-            while (residues.hasNext) {
-                const seg = residues.move();
-                if (!property.has(seg.index) || seenResidues.has(seg.index)) continue;
-
-                seenResidues.add(seg.index);
-                loci[loci.length] = StructureElement.create(unit, unit.elements[seg.start]);
-            }
-        }
-
-        loci.sort((x, y) => x.element - y.element);
-        return loci;
-    }
-}
+// /**
+//  * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+//  *
+//  * @author David Sehnal <david.sehnal@gmail.com>
+//  */
+
+// import { ResidueIndex } from '../../indexing';
+// import { Unit, Structure, StructureElement } from '../../../structure';
+// import { Segmentation } from 'mol-data/int';
+// import { UUID } from 'mol-util';
+// import { CifWriter } from 'mol-io/writer/cif';
+
+// export interface ResidueCustomProperty<T = any> {
+//     readonly id: UUID,
+//     readonly kind: Unit.Kind,
+//     has(idx: ResidueIndex): boolean
+//     get(idx: ResidueIndex): T | undefined
+// }
+
+// export namespace ResidueCustomProperty {
+//     export interface ExportCtx<T> {
+//         elements: StructureElement[],
+//         property(index: number): T
+//     };
+
+//     function getExportCtx<T>(prop: ResidueCustomProperty<T>, structure: Structure): ExportCtx<T> {
+//         const residueIndex = structure.model.atomicHierarchy.residueAtomSegments.index;
+//         const elements = getStructureElements(structure, prop);
+//         return { elements, property: i => prop.get(residueIndex[elements[i].element])! };
+//     }
+
+//     export function getCifDataSource<T>(structure: Structure, prop: ResidueCustomProperty<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 = getExportCtx(prop, structure);
+//         const ret = { data, rowCount: data.elements.length };
+//         if (cache) cache[prop.id] = ret;
+//         return ret;
+//     }
+
+//     class FromMap<T> implements ResidueCustomProperty<T> {
+//         readonly id = UUID.create();
+
+//         has(idx: ResidueIndex): boolean {
+//             return this.map.has(idx);
+//         }
+
+//         get(idx: ResidueIndex) {
+//             return this.map.get(idx);
+//         }
+
+//         constructor(private map: Map<ResidueIndex, T>, public kind: Unit.Kind) {
+//         }
+//     }
+
+//     export function fromMap<T>(map: Map<ResidueIndex, T>, kind: Unit.Kind) {
+//         return new FromMap(map, kind);
+//     }
+
+//     /**
+//      * Gets all StructureElements that correspond to 1st atoms of residues that have an property assigned.
+//      * Only works correctly for structures with a single model.
+//      */
+//     export function getStructureElements(structure: Structure, property: ResidueCustomProperty) {
+//         const models = structure.models;
+//         if (models.length !== 1) throw new Error(`Only works on structures with a single model.`);
+
+//         const seenResidues = new Set<ResidueIndex>();
+//         const unitGroups = structure.unitSymmetryGroups;
+//         const loci: StructureElement[] = [];
+
+//         for (const unitGroup of unitGroups) {
+//             const unit = unitGroup.units[0];
+//             if (unit.kind !== property.kind) {
+//                 continue;
+//             }
+
+//             const residues = Segmentation.transientSegments(unit.model.atomicHierarchy.residueAtomSegments, unit.elements);
+//             while (residues.hasNext) {
+//                 const seg = residues.move();
+//                 if (!property.has(seg.index) || seenResidues.has(seg.index)) continue;
+
+//                 seenResidues.add(seg.index);
+//                 loci[loci.length] = StructureElement.create(unit, unit.elements[seg.start]);
+//             }
+//         }
+
+//         loci.sort((x, y) => x.element - y.element);
+//         return loci;
+//     }
+// }

+ 8 - 0
src/servers/model/test.ts

@@ -55,6 +55,14 @@ async function run() {
         //         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);