Skip to content
Snippets Groups Projects
Commit 3e6f3c3c authored by David Sehnal's avatar David Sehnal
Browse files

Refactoring custom model props

parent d2a17b80
No related branches found
No related tags found
No related merge requests found
...@@ -95,6 +95,11 @@ export namespace Field { ...@@ -95,6 +95,11 @@ export namespace Field {
return this; 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; } getFields() { return this.fields; }
} }
......
...@@ -8,7 +8,7 @@ import { Column, Table } from 'mol-data/db'; ...@@ -8,7 +8,7 @@ import { Column, Table } from 'mol-data/db';
import { toTable } from 'mol-io/reader/cif/schema'; import { toTable } from 'mol-io/reader/cif/schema';
import { mmCIF_residueId_schema } from 'mol-io/reader/cif/schema/mmcif-extras'; import { mmCIF_residueId_schema } from 'mol-io/reader/cif/schema/mmcif-extras';
import { CifWriter } from 'mol-io/writer/cif'; 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 { residueIdFields } from 'mol-model/structure/export/categories/atom_site';
import { StructureElement } from 'mol-model/structure/structure'; import { StructureElement } from 'mol-model/structure/structure';
import { CustomPropSymbol } from 'mol-script/language/symbol'; import { CustomPropSymbol } from 'mol-script/language/symbol';
...@@ -18,7 +18,7 @@ import { PropertyWrapper } from '../common/wrapper'; ...@@ -18,7 +18,7 @@ import { PropertyWrapper } from '../common/wrapper';
import CifField = CifWriter.Field; import CifField = CifWriter.Field;
export namespace StructureQualityReport { export namespace StructureQualityReport {
export type IssueMap = ResidueCustomProperty<string[]> export type IssueMap = IndexedCustomProperty.Residue<string[]>
export type Property = PropertyWrapper<IssueMap | undefined> export type Property = PropertyWrapper<IssueMap | undefined>
export function get(model: Model): Property | undefined { export function get(model: Model): Property | undefined {
...@@ -38,7 +38,7 @@ export namespace StructureQualityReport { ...@@ -38,7 +38,7 @@ export namespace StructureQualityReport {
instance(ctx) { instance(ctx) {
return { return {
fields: _structure_quality_report_issues_fields, 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 { ...@@ -111,13 +111,13 @@ export namespace StructureQualityReport {
} }
} }
type ExportCtx = ResidueCustomProperty.ExportCtx<string[]> type ExportCtx = IndexedCustomProperty.ExportCtx<string[]>
const _structure_quality_report_issues_fields: CifField<number, ExportCtx>[] = [ const _structure_quality_report_issues_fields: CifField<number, ExportCtx>[] = CifWriter.fields()
CifField.index('id'), .index('id')
...residueIdFields<number, ExportCtx>((i, d) => d.elements[i]), .many(residueIdFields((i, d) => d.elements[i]))
CifField.int<number, ExportCtx>('pdbx_PDB_model_num', (i, d) => P.unit.model_num(d.elements[i])), .int('pdbx_PDB_model_num', (i, d) => P.unit.model_num(d.elements[i]))
CifField.str<number, ExportCtx>('issues', (i, d) => d.property(i).join(',')) .str('issues', (i, d) => d.property(i).join(','))
]; .getFields()
function createIssueMapFromJson(modelData: Model, data: any): StructureQualityReport.IssueMap | undefined { function createIssueMapFromJson(modelData: Model, data: any): StructureQualityReport.IssueMap | undefined {
const ret = new Map<ResidueIndex, string[]>(); const ret = new Map<ResidueIndex, string[]>();
...@@ -140,7 +140,7 @@ function createIssueMapFromJson(modelData: Model, data: any): StructureQualityRe ...@@ -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 { 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 ...@@ -153,5 +153,5 @@ function createIssueMapFromCif(modelData: Model, data: Table<typeof StructureQua
ret.set(idx, issues.value(i)); 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
...@@ -178,6 +178,9 @@ export namespace AssemblySymmetry { ...@@ -178,6 +178,9 @@ export namespace AssemblySymmetry {
let db: Database 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')) { 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) const rcsb_assembly_symmetry_feature = toTable(Schema.rcsb_assembly_symmetry_feature, model.sourceData.frame.categories.rcsb_assembly_symmetry_feature)
......
...@@ -6,4 +6,4 @@ ...@@ -6,4 +6,4 @@
export * from './custom/descriptor' export * from './custom/descriptor'
export * from './custom/collection' export * from './custom/collection'
export * from './custom/residue' export * from './custom/indexed'
\ No newline at end of file \ 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 { 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
/**
* 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
/** // /**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. // * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
* // *
* @author David Sehnal <david.sehnal@gmail.com> // * @author David Sehnal <david.sehnal@gmail.com>
*/ // */
import { ResidueIndex } from '../../indexing'; // import { ResidueIndex } from '../../indexing';
import { Unit, Structure, StructureElement } from '../../../structure'; // import { Unit, Structure, StructureElement } from '../../../structure';
import { Segmentation } from 'mol-data/int'; // import { Segmentation } from 'mol-data/int';
import { UUID } from 'mol-util'; // import { UUID } from 'mol-util';
import { CifWriter } from 'mol-io/writer/cif'; // import { CifWriter } from 'mol-io/writer/cif';
export interface ResidueCustomProperty<T = any> { // export interface ResidueCustomProperty<T = any> {
readonly id: UUID, // readonly id: UUID,
readonly kind: Unit.Kind, // readonly kind: Unit.Kind,
has(idx: ResidueIndex): boolean // has(idx: ResidueIndex): boolean
get(idx: ResidueIndex): T | undefined // get(idx: ResidueIndex): T | undefined
} // }
export namespace ResidueCustomProperty { // export namespace ResidueCustomProperty {
export interface ExportCtx<T> { // export interface ExportCtx<T> {
elements: StructureElement[], // elements: StructureElement[],
property(index: number): T // property(index: number): T
}; // };
function getExportCtx<T>(prop: ResidueCustomProperty<T>, structure: Structure): ExportCtx<T> { // function getExportCtx<T>(prop: ResidueCustomProperty<T>, structure: Structure): ExportCtx<T> {
const residueIndex = structure.model.atomicHierarchy.residueAtomSegments.index; // const residueIndex = structure.model.atomicHierarchy.residueAtomSegments.index;
const elements = getStructureElements(structure, prop); // const elements = getStructureElements(structure, prop);
return { elements, property: i => prop.get(residueIndex[elements[i].element])! }; // 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] { // export function getCifDataSource<T>(structure: Structure, prop: ResidueCustomProperty<T> | undefined, cache: any): CifWriter.Category.Instance['source'][0] {
if (!prop) return { rowCount: 0 }; // if (!prop) return { rowCount: 0 };
if (cache && cache[prop.id]) return cache[prop.id]; // if (cache && cache[prop.id]) return cache[prop.id];
const data = getExportCtx(prop, structure); // const data = getExportCtx(prop, structure);
const ret = { data, rowCount: data.elements.length }; // const ret = { data, rowCount: data.elements.length };
if (cache) cache[prop.id] = ret; // if (cache) cache[prop.id] = ret;
return ret; // return ret;
} // }
class FromMap<T> implements ResidueCustomProperty<T> { // class FromMap<T> implements ResidueCustomProperty<T> {
readonly id = UUID.create(); // readonly id = UUID.create();
has(idx: ResidueIndex): boolean { // has(idx: ResidueIndex): boolean {
return this.map.has(idx); // return this.map.has(idx);
} // }
get(idx: ResidueIndex) { // get(idx: ResidueIndex) {
return this.map.get(idx); // return this.map.get(idx);
} // }
constructor(private map: Map<ResidueIndex, T>, public kind: Unit.Kind) { // constructor(private map: Map<ResidueIndex, T>, public kind: Unit.Kind) {
} // }
} // }
export function fromMap<T>(map: Map<ResidueIndex, T>, kind: Unit.Kind) { // export function fromMap<T>(map: Map<ResidueIndex, T>, kind: Unit.Kind) {
return new FromMap(map, kind); // return new FromMap(map, kind);
} // }
/** // /**
* Gets all StructureElements that correspond to 1st atoms of residues that have an property assigned. // * Gets all StructureElements that correspond to 1st atoms of residues that have an property assigned.
* Only works correctly for structures with a single model. // * Only works correctly for structures with a single model.
*/ // */
export function getStructureElements(structure: Structure, property: ResidueCustomProperty) { // export function getStructureElements(structure: Structure, property: ResidueCustomProperty) {
const models = structure.models; // const models = structure.models;
if (models.length !== 1) throw new Error(`Only works on structures with a single model.`); // if (models.length !== 1) throw new Error(`Only works on structures with a single model.`);
const seenResidues = new Set<ResidueIndex>(); // const seenResidues = new Set<ResidueIndex>();
const unitGroups = structure.unitSymmetryGroups; // const unitGroups = structure.unitSymmetryGroups;
const loci: StructureElement[] = []; // const loci: StructureElement[] = [];
for (const unitGroup of unitGroups) { // for (const unitGroup of unitGroups) {
const unit = unitGroup.units[0]; // const unit = unitGroup.units[0];
if (unit.kind !== property.kind) { // if (unit.kind !== property.kind) {
continue; // continue;
} // }
const residues = Segmentation.transientSegments(unit.model.atomicHierarchy.residueAtomSegments, unit.elements); // const residues = Segmentation.transientSegments(unit.model.atomicHierarchy.residueAtomSegments, unit.elements);
while (residues.hasNext) { // while (residues.hasNext) {
const seg = residues.move(); // const seg = residues.move();
if (!property.has(seg.index) || seenResidues.has(seg.index)) continue; // if (!property.has(seg.index) || seenResidues.has(seg.index)) continue;
seenResidues.add(seg.index); // seenResidues.add(seg.index);
loci[loci.length] = StructureElement.create(unit, unit.elements[seg.start]); // loci[loci.length] = StructureElement.create(unit, unit.elements[seg.start]);
} // }
} // }
loci.sort((x, y) => x.element - y.element); // loci.sort((x, y) => x.element - y.element);
return loci; // return loci;
} // }
} // }
\ No newline at end of file \ No newline at end of file
...@@ -55,6 +55,14 @@ async function run() { ...@@ -55,6 +55,14 @@ async function run() {
// atom_site: { label_comp_id: 'ALA' } // 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 encoder = await resolveJob(request);
const writer = wrapFile(path.join(outPath, testFile)); const writer = wrapFile(path.join(outPath, testFile));
encoder.writeTo(writer); encoder.writeTo(writer);
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment