From 3e6f3c3c67b80c20fcbde025436cb82fdfff39ef Mon Sep 17 00:00:00 2001 From: David Sehnal <david.sehnal@gmail.com> Date: Mon, 1 Oct 2018 15:53:39 +0200 Subject: [PATCH] Refactoring custom model props --- src/mol-io/writer/cif/encoder.ts | 5 + .../pdbe/structure-quality-report.ts | 24 +-- src/mol-model-props/rcsb/symmetry.ts | 3 + .../structure/model/properties/custom.ts | 2 +- .../model/properties/custom/chain.ts | 91 +++++++++ .../model/properties/custom/indexed.ts | 88 +++++++++ .../model/properties/custom/residue.ts | 182 +++++++++--------- src/servers/model/test.ts | 8 + 8 files changed, 299 insertions(+), 104 deletions(-) create mode 100644 src/mol-model/structure/model/properties/custom/chain.ts create mode 100644 src/mol-model/structure/model/properties/custom/indexed.ts diff --git a/src/mol-io/writer/cif/encoder.ts b/src/mol-io/writer/cif/encoder.ts index 585a221bf..a26084fb3 100644 --- a/src/mol-io/writer/cif/encoder.ts +++ b/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; } } diff --git a/src/mol-model-props/pdbe/structure-quality-report.ts b/src/mol-model-props/pdbe/structure-quality-report.ts index d69524a6a..9974478df 100644 --- a/src/mol-model-props/pdbe/structure-quality-report.ts +++ b/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); } \ No newline at end of file diff --git a/src/mol-model-props/rcsb/symmetry.ts b/src/mol-model-props/rcsb/symmetry.ts index 36bdd5a8e..379310bde 100644 --- a/src/mol-model-props/rcsb/symmetry.ts +++ b/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) diff --git a/src/mol-model/structure/model/properties/custom.ts b/src/mol-model/structure/model/properties/custom.ts index 6c8764094..9b9ab7c3d 100644 --- a/src/mol-model/structure/model/properties/custom.ts +++ b/src/mol-model/structure/model/properties/custom.ts @@ -6,4 +6,4 @@ export * from './custom/descriptor' export * from './custom/collection' -export * from './custom/residue' \ No newline at end of file +export * from './custom/indexed' \ No newline at end of file diff --git a/src/mol-model/structure/model/properties/custom/chain.ts b/src/mol-model/structure/model/properties/custom/chain.ts new file mode 100644 index 000000000..4e711c32e --- /dev/null +++ b/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; +// } +// } \ 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 new file mode 100644 index 000000000..f20bf257b --- /dev/null +++ b/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); + } +} \ No newline at end of file diff --git a/src/mol-model/structure/model/properties/custom/residue.ts b/src/mol-model/structure/model/properties/custom/residue.ts index ba79b8d7b..4dd7cb130 100644 --- a/src/mol-model/structure/model/properties/custom/residue.ts +++ b/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; - } -} \ No newline at end of file +// /** +// * 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; +// } +// } \ No newline at end of file diff --git a/src/servers/model/test.ts b/src/servers/model/test.ts index 3e1cc6d9e..ca718fbd4 100644 --- a/src/servers/model/test.ts +++ b/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); -- GitLab