Переглянути джерело

better support for polymers with a coarse backbone, improved MoleculeTypeAtomRoleIds

Alexander Rose 6 роки тому
батько
коміт
7ef36dbe48

+ 1 - 1
src/mol-model-formats/structure/mmcif/atomic.ts

@@ -101,7 +101,7 @@ export function getAtomicHierarchyAndConformation(format: mmCIF_Format, atom_sit
 
     const index = getAtomicIndex(hierarchyData, entities, hierarchySegments);
     const derived = getAtomicDerivedData(hierarchyData, index, formatData.chemicalComponentMap);
-    const hierarchyRanges = getAtomicRanges(hierarchyData, hierarchySegments, conformation, derived.residue.moleculeType);
+    const hierarchyRanges = getAtomicRanges(hierarchyData, hierarchySegments, conformation, index, derived.residue.moleculeType);
     const hierarchy: AtomicHierarchy = { ...hierarchyData, ...hierarchySegments, ...hierarchyRanges, index, derived };
     return { sameAsPrevious: false, hierarchy, conformation };
 }

+ 9 - 4
src/mol-model/structure/model/properties/atomic/hierarchy.ts

@@ -145,7 +145,7 @@ export interface AtomicIndex {
     /**
      * Index of the 1st occurence of this residue.
      * auth_seq_id is used because label_seq_id is undefined for "ligands" in mmCIF.
-     * @param pdbx_PDB_ins_code Empty string for undefined
+     * @param key.pdbx_PDB_ins_code Empty string for undefined
      * @returns index or -1 if not present.
      */
     findResidue(key: AtomicIndex.ResidueKey): ResidueIndex,
@@ -153,7 +153,7 @@ export interface AtomicIndex {
 
     /**
      * Index of the 1st occurence of this residue.
-     * @param pdbx_PDB_ins_code Empty string for undefined
+     * @param key.pdbx_PDB_ins_code Empty string for undefined
      * @returns index or -1 if not present.
      */
     findResidueAuth(key: AtomicIndex.ResidueAuthKey): ResidueIndex,
@@ -161,7 +161,7 @@ export interface AtomicIndex {
     /**
      * Find the residue index where the spefied residue should be inserted to maintain the ordering (entity_id, asym_id, seq_id, ins_code).
      * Useful for determining ranges for sequence-level annotations.
-     * @param pdbx_PDB_ins_code Empty string for undefined
+     * @param key.pdbx_PDB_ins_code Use empty string for undefined
      */
     findResidueInsertion(key: AtomicIndex.ResidueLabelKey): ResidueIndex,
 
@@ -181,11 +181,16 @@ export interface AtomicIndex {
 
     /**
      * Find element index of an atom on a given residue.
-     * @param key
      * @returns index or -1 if the atom is not present.
      */
     findAtomOnResidue(residueIndex: ResidueIndex, label_atom_id: string, label_alt_id?: string): ElementIndex
 
+    /**
+     * Find element index of any given atom on a given residue.
+     * @returns first found index or -1 if none of the given atoms are present.
+     */
+    findAtomsOnResidue(residueIndex: ResidueIndex, label_atom_ids: Set<string>): ElementIndex
+
     // TODO: add indices that support comp_id?
 }
 

+ 7 - 2
src/mol-model/structure/model/properties/utils/atomic-derived.ts

@@ -37,10 +37,15 @@ export function getAtomicDerivedData(data: AtomicData, index: AtomicIndex, chemi
         moleculeType[i] = molType
 
         const traceAtomId = getAtomIdForAtomRole(molType, 'trace')
-        traceElementIndex[i] = index.findAtomOnResidue(i as ResidueIndex, traceAtomId)
+        let traceIndex = index.findAtomsOnResidue(i as ResidueIndex, traceAtomId)
+        if (traceIndex === -1) {
+            const coarseAtomId = getAtomIdForAtomRole(molType, 'coarseBackbone')
+            traceIndex = index.findAtomsOnResidue(i as ResidueIndex, coarseAtomId)
+        }
+        traceElementIndex[i] = traceIndex
 
         const directionAtomId = getAtomIdForAtomRole(molType, 'direction')
-        directionElementIndex[i] = index.findAtomOnResidue(i as ResidueIndex, directionAtomId)
+        directionElementIndex[i] = index.findAtomsOnResidue(i as ResidueIndex, directionAtomId)
     }
 
     return {

+ 11 - 0
src/mol-model/structure/model/properties/utils/atomic-index.ts

@@ -158,6 +158,10 @@ class Index implements AtomicIndex {
         return findAtomByNameAndAltLoc(this.residueOffsets[rI], this.residueOffsets[rI + 1], this.map.label_atom_id, this.map.label_alt_id, label_atom_id, label_alt_id);
     }
 
+    findAtomsOnResidue(rI: ResidueIndex, label_atom_ids: Set<string>) {
+        return findAtomByNames(this.residueOffsets[rI], this.residueOffsets[rI + 1], this.map.label_atom_id, label_atom_ids)
+    }
+
     constructor(private map: Mapping) {
         this.entityIndex = map.entities.getEntityIndex;
         this.residueOffsets = this.map.segments.residueAtomSegments.offsets;
@@ -171,6 +175,13 @@ function findAtomByName(start: ElementIndex, end: ElementIndex, data: Column<str
     return -1 as ElementIndex;
 }
 
+function findAtomByNames(start: ElementIndex, end: ElementIndex, data: Column<string>, atomNames: Set<string>): ElementIndex {
+    for (let i = start; i < end; i++) {
+        if (atomNames.has(data.value(i))) return i;
+    }
+    return -1 as ElementIndex;
+}
+
 function findAtomByNameAndAltLoc(start: ElementIndex, end: ElementIndex, nameData: Column<string>, altLocData: Column<string>,
     atomName: string, altLoc: string): ElementIndex {
     for (let i = start; i < end; i++) {

+ 12 - 19
src/mol-model/structure/model/properties/utils/atomic-ranges.ts

@@ -5,7 +5,7 @@
  */
 
 import { AtomicSegments } from '../atomic';
-import { AtomicData, AtomicRanges } from '../atomic/hierarchy';
+import { AtomicData, AtomicRanges, AtomicIndex } from '../atomic/hierarchy';
 import { Segmentation, Interval } from 'mol-data/int';
 import SortedRanges from 'mol-data/int/sorted-ranges';
 import { MoleculeType, isPolymer } from '../../types';
@@ -16,33 +16,26 @@ import { Vec3 } from 'mol-math/linear-algebra';
 
 // TODO add gaps at the ends of the chains by comparing to the polymer sequence data
 
-function getElementIndexForAtomId(rI: ResidueIndex, atomId: string, data: AtomicData, segments: AtomicSegments): ElementIndex {
-    const { offsets } = segments.residueAtomSegments
-    const { label_atom_id } = data.atoms
-    for (let j = offsets[rI], _j = offsets[rI + 1]; j < _j; j++) {
-        if (label_atom_id.value(j) === atomId) return j
-    }
-    return offsets[rI]
-}
-
-function areBackboneConnected(riStart: ResidueIndex, riEnd: ResidueIndex, data: AtomicData, segments: AtomicSegments, conformation: AtomicConformation, moleculeType: ArrayLike<MoleculeType>) {
+function areBackboneConnected(riStart: ResidueIndex, riEnd: ResidueIndex, data: AtomicData, segments: AtomicSegments, conformation: AtomicConformation, index: AtomicIndex, moleculeType: ArrayLike<MoleculeType>) {
     const mtStart = moleculeType[riStart]
     const mtEnd = moleculeType[riEnd]
     if (!isPolymer(mtStart) || !isPolymer(mtEnd)) return false
 
-    const startId = getAtomIdForAtomRole(mtStart, 'backboneStart')
-    const endId = getAtomIdForAtomRole(mtEnd, 'backboneEnd')
+    let eiStart = index.findAtomsOnResidue(riStart, getAtomIdForAtomRole(mtStart, 'backboneStart'))
+    let eiEnd = index.findAtomsOnResidue(riEnd, getAtomIdForAtomRole(mtEnd, 'backboneEnd'))
 
-    const eiStart = getElementIndexForAtomId(riStart, startId, data, segments)
-    const eiEnd = getElementIndexForAtomId(riEnd, endId, data, segments)
+    if (eiStart === -1 || eiEnd === -1) {
+        eiStart = index.findAtomsOnResidue(riStart, getAtomIdForAtomRole(mtStart, 'coarseBackbone'))
+        eiEnd = index.findAtomsOnResidue(riEnd, getAtomIdForAtomRole(mtEnd, 'coarseBackbone'))
+    }
 
     const { x, y, z } = conformation
     const pStart = Vec3.create(x[eiStart], y[eiStart], z[eiStart])
     const pEnd = Vec3.create(x[eiEnd], y[eiEnd], z[eiEnd])
-    return Vec3.distance(pStart, pEnd) < 10
+    return Vec3.distance(pStart, pEnd) < 10 // TODO better distance check, take into account if protein/nucleic and if coarse
 }
 
-export function getAtomicRanges(data: AtomicData, segments: AtomicSegments, conformation: AtomicConformation, moleculeType: ArrayLike<MoleculeType>): AtomicRanges {
+export function getAtomicRanges(data: AtomicData, segments: AtomicSegments, conformation: AtomicConformation, index: AtomicIndex, moleculeType: ArrayLike<MoleculeType>): AtomicRanges {
     const polymerRanges: number[] = []
     const gapRanges: number[] = []
     const cyclicPolymerMap = new Map<ResidueIndex, ResidueIndex>()
@@ -65,7 +58,7 @@ export function getAtomicRanges(data: AtomicData, segments: AtomicSegments, conf
 
         const riStart = segments.residueAtomSegments.index[chainSegment.start]
         const riEnd = segments.residueAtomSegments.index[chainSegment.end - 1]
-        if (areBackboneConnected(riStart, riEnd, data, segments, conformation, moleculeType)) {
+        if (areBackboneConnected(riStart, riEnd, data, segments, conformation, index, moleculeType)) {
             cyclicPolymerMap.set(riStart, riEnd)
             cyclicPolymerMap.set(riEnd, riStart)
         }
@@ -85,7 +78,7 @@ export function getAtomicRanges(data: AtomicData, segments: AtomicSegments, conf
                     } else {
                         const riStart = segments.residueAtomSegments.index[residueSegment.start]
                         const riEnd = segments.residueAtomSegments.index[prevEnd - 1]
-                        if (!areBackboneConnected(riStart, riEnd, data, segments, conformation, moleculeType)) {
+                        if (!areBackboneConnected(riStart, riEnd, data, segments, conformation, index, moleculeType)) {
                             polymerRanges.push(startIndex, prevEnd - 1)
                             startIndex = residueSegment.start
                         }

+ 22 - 18
src/mol-model/structure/model/types.ts

@@ -59,32 +59,36 @@ export const enum MoleculeType {
     saccharide
 }
 
-export type AtomRole = 'trace' | 'direction' | 'backboneStart' | 'backboneEnd'
+export type AtomRole = 'trace' | 'direction' | 'backboneStart' | 'backboneEnd' | 'coarseBackbone'
 
-export const MoleculeTypeAtomRoleId: { [k: number]: { [k in AtomRole]: string } } = {
+export const MoleculeTypeAtomRoleId: { [k: number]: { [k in AtomRole]: Set<string> } } = {
     [MoleculeType.protein]: {
-        trace: 'CA', // TODO 'BB'
-        direction: 'O', // TODO 'OC1', 'O1', 'OX1', 'OXT'
-        backboneStart: 'N',
-        backboneEnd: 'C'
+        trace: new Set(['CA']),
+        direction: new Set(['O', 'OC1', 'O1', 'OX1', 'OXT']),
+        backboneStart: new Set(['N']),
+        backboneEnd: new Set(['C']),
+        coarseBackbone: new Set(['CA', 'BB'])
     },
     [MoleculeType.RNA]: {
-        trace: 'C4\'', // TODO 'C4*'
-        direction: 'C3\'', // 'C3*'
-        backboneStart: 'P',
-        backboneEnd: 'O3\'' // TODO 'O3*'
+        trace: new Set(['C4\'', 'C4*']),
+        direction: new Set(['C3\'', 'C3*']),
+        backboneStart: new Set(['P']),
+        backboneEnd: new Set(['O3\'', 'O3*']),
+        coarseBackbone: new Set(['P'])
     },
     [MoleculeType.DNA]: {
-        trace: 'C3\'', // TODO 'C3*'
-        direction: 'C1\'', // TODO 'C1*'
-        backboneStart: 'P',
-        backboneEnd: 'O3\'' // TODO 'O3*'
+        trace: new Set(['C3\'', 'C3*']),
+        direction: new Set(['C1\'', 'C1*']),
+        backboneStart: new Set(['P']),
+        backboneEnd: new Set(['O3\'', 'O3*']),
+        coarseBackbone: new Set(['P'])
     },
     [MoleculeType.PNA]: {
-        trace: 'N4\'', // TODO 'N4*'
-        direction: 'C7\'', // TODO 'C7*'
-        backboneStart: 'N1\'', // TODO 'N1*'
-        backboneEnd: 'C1\'' // TODO 'C1*'
+        trace: new Set(['N4\'', 'N4*']),
+        direction: new Set(['C7\'', 'C7*']),
+        backboneStart: new Set(['N1\'', 'N1*']),
+        backboneEnd: new Set(['C1\'', 'C1*']),
+        coarseBackbone: new Set(['P'])
     }
 }
 

+ 2 - 1
src/mol-model/structure/util.ts

@@ -35,13 +35,14 @@ export function getAtomicMoleculeType(model: Model, rI: ResidueIndex): MoleculeT
     return model.atomicHierarchy.derived.residue.moleculeType[rI]
 }
 
+const EmptyAtomIds = new Set<string>()
 export function getAtomIdForAtomRole(moleculeType: MoleculeType, atomRole: AtomRole) {
     const m = MoleculeTypeAtomRoleId[moleculeType]
     if (m !== undefined) {
         const a = m[atomRole]
         if (a !== undefined) return a
     }
-    return ''
+    return EmptyAtomIds
 }
 
 export function residueLabel(model: Model, rI: number) {

+ 2 - 2
src/mol-repr/structure/visual/polymer-trace-mesh.ts

@@ -72,11 +72,11 @@ function createPolymerTraceMesh(ctx: VisualContext, unit: Unit, structure: Struc
             addSheet(builderState, curvePoints, normalVectors, binormalVectors, linearSegments, w1, h1, arrowHeight, v.secStrucFirst, v.secStrucLast)
         } else {
             let h0: number, h1: number, h2: number
-            if (isHelix) {
+            if (isHelix && !v.isCoarseBackbone) {
                 h0 = w0 * aspectRatio
                 h1 = w1 * aspectRatio
                 h2 = w2 * aspectRatio
-            } else if (isNucleicType) {
+            } else if (isNucleicType && !v.isCoarseBackbone) {
                 h0 = w0 * aspectRatio;
                 [w0, h0] = [h0, w0]
                 h1 = w1 * aspectRatio;

+ 3 - 0
src/mol-repr/structure/visual/util/polymer/trace-iterator.ts

@@ -35,6 +35,7 @@ interface PolymerTraceElement {
     secStrucFirst: boolean, secStrucLast: boolean
     secStrucType: SecondaryStructureType
     moleculeType: MoleculeType
+    isCoarseBackbone: boolean
 
     p0: Vec3, p1: Vec3, p2: Vec3, p3: Vec3, p4: Vec3
     d12: Vec3, d23: Vec3
@@ -51,6 +52,7 @@ function createPolymerTraceElement (unit: Unit): PolymerTraceElement {
         secStrucFirst: false, secStrucLast: false,
         secStrucType: SecStrucTypeNA,
         moleculeType: MoleculeType.unknown,
+        isCoarseBackbone: false,
         p0: Vec3.zero(), p1: Vec3.zero(), p2: Vec3.zero(), p3: Vec3.zero(), p4: Vec3.zero(),
         d12: Vec3.create(1, 0, 0), d23: Vec3.create(1, 0, 0),
     }
@@ -199,6 +201,7 @@ export class AtomicPolymerTraceIterator implements Iterator<PolymerTraceElement>
             value.centerNext.element = this.traceElementIndex[residueIndexNext1]
             this.pos(this.p6,  this.traceElementIndex[residueIndexNext3])
             this.pos(this.v23, this.directionElementIndex[residueIndex])
+            value.isCoarseBackbone = this.directionElementIndex[residueIndex] === -1
 
             this.setControlPoint(value.p0, this.p0, this.p1, this.p2, residueIndexPrev2)
             this.setControlPoint(value.p1, this.p1, this.p2, this.p3, residueIndexPrev1)