-
David Sehnal authoredDavid Sehnal authored
structure-quality-report.ts 4.48 KiB
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { Segmentation } from 'mol-data/int';
import { CifWriter } from 'mol-io/writer/cif';
import { Model, ModelPropertyDescriptor, ResidueIndex, Structure, StructureElement, Unit } from 'mol-model/structure';
import { residueIdFields } from 'mol-model/structure/export/categories/atom_site';
import CifField = CifWriter.Field;
type IssueMap = Map<ResidueIndex, string[]>
const _Descriptor: ModelPropertyDescriptor = {
isStatic: true,
name: 'structure_quality_report',
cifExport: {
prefix: 'pdbe',
categories: [{
name: 'pdbe_structure_quality_report',
instance(ctx) {
const issues = StructureQualityReport.get(ctx.model);
if (!issues) return CifWriter.Category.Empty;
const residues = getResidueLoci(ctx.structure, issues);
return {
fields: _structure_quality_report_fields,
data: <ExportCtx>{ model: ctx.model, residues, residueIndex: ctx.model.atomicHierarchy.residueAtomSegments.index, issues },
rowCount: residues.length
};
}
}]
}
}
type ExportCtx = { model: Model, residueIndex: ArrayLike<ResidueIndex>, residues: StructureElement[], issues: IssueMap };
const _structure_quality_report_fields: CifField<ResidueIndex, ExportCtx>[] = [
CifField.index('id'),
...residueIdFields<ResidueIndex, ExportCtx>((k, d) => d.residues[k]),
CifField.str<ResidueIndex, ExportCtx>('issues', (i, d) => d.issues.get(d.residueIndex[d.residues[i].element])!.join(','))
];
function getResidueLoci(structure: Structure, issues: IssueMap) {
const seenResidues = new Set<ResidueIndex>();
const unitGroups = structure.unitSymmetryGroups;
const loci: StructureElement[] = [];
for (const unitGroup of unitGroups) {
const unit = unitGroup.units[0];
if (!Unit.isAtomic(unit)) {
continue;
}
const residues = Segmentation.transientSegments(unit.model.atomicHierarchy.residueAtomSegments, unit.elements);
while (residues.hasNext) {
const seg = residues.move();
if (!issues.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;
}
function createIssueMap(modelData: Model, data: any): IssueMap | undefined {
const ret = new Map<ResidueIndex, string[]>();
if (!data.molecules) return;
for (const entity of data.molecules) {
const entity_id = entity.entity_id.toString();
for (const chain of entity.chains) {
const asym_id = chain.struct_asym_id.toString();
for (const model of chain.models) {
const model_id = model.model_id.toString();
if (+model_id !== modelData.modelNum) continue;
for (const residue of model.residues) {
const auth_seq_id = residue.author_residue_number, ins_code = residue.author_insertion_code || '';
const idx = modelData.atomicHierarchy.findResidueKey(entity_id, asym_id, '', auth_seq_id, ins_code);
ret.set(idx, residue.outlier_types);
}
}
}
}
return ret;
}
export namespace StructureQualityReport {
export const Descriptor = _Descriptor;
export async function attachFromCifOrApi(model: Model, params: {
// provide JSON from api
pdbEapiSourceJson?: (model: Model) => Promise<any>
}) {
// TODO: check if present in mmCIF on Model
if (!params.pdbEapiSourceJson) throw new Error('Data source not defined');
if (model.customProperties.has(Descriptor)) return true;
const id = model.label.toLowerCase();
const json = await params.pdbEapiSourceJson(model);
const data = json[id];
if (!data) return false;
const issueMap = createIssueMap(model, data);
if (!issueMap || issueMap.size === 0) return false;
model.customProperties.add(Descriptor);
model._staticPropertyData.__StructureQualityReport__ = issueMap;
return true;
}
export function get(model: Model): IssueMap | undefined {
return model._staticPropertyData.__StructureQualityReport__;
}
}