Browse Source

Merge pull request #373 from JonStargaryen/master

Add TraceOnly option for Structure Superposition
David Sehnal 3 years ago
parent
commit
097277e397

+ 1 - 0
CHANGELOG.md

@@ -9,6 +9,7 @@ Note that since we don't clearly distinguish between a public and private interf
 - Rename "best database mapping" to "SIFTS Mapping"
 - Add schema and export support for ``atom_site.pdbx_sifts_xref_*`` fields
 - Add schema export support for ``atom_site.pdbx_label_index`` field
+- Add `traceOnly` parameter to chain/UniProt-based structure alignment
 
 ## [v3.1.0] - 2022-02-06
 

+ 15 - 7
src/mol-model/structure/structure/util/superposition-sifts-mapping.ts

@@ -1,7 +1,8 @@
 /**
- * Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2021-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author David Sehnal <david.sehnal@gmail.com>
+ * @author Sebastian Bittrich <sebastian.bittrich@rcsb.org>
  */
 
 import { Segmentation } from '../../../../mol-data/int';
@@ -23,11 +24,11 @@ export interface AlignmentResult {
     failedPairs: [number, number][]
 }
 
-export function alignAndSuperposeWithSIFTSMapping(structures: Structure[]): AlignmentResult {
+export function alignAndSuperposeWithSIFTSMapping(structures: Structure[], options?: { traceOnly?: boolean }): AlignmentResult {
     const indexMap = new Map<string, IndexEntry>();
 
     for (let i = 0; i < structures.length; i++) {
-        buildIndex(structures[i], indexMap, i);
+        buildIndex(structures[i], indexMap, i, options?.traceOnly ?? true);
     }
 
     const index = Array.from(indexMap.values());
@@ -69,7 +70,6 @@ function getPositionTables(index: IndexEntry[], pivot: number, other: number, N:
 
         const l = Math.min(a[2] - a[1], b[2] - b[1]);
 
-        // TODO: allow to use just backbone atoms?
         // TODO: check if residue types match?
         for (let i = 0; i < l; i++) {
             let eI = (a[1] + i) as ElementIndex;
@@ -137,7 +137,7 @@ interface IndexEntry {
     pivots: { [i: number]: [unit: Unit.Atomic, start: ElementIndex, end: ElementIndex] | undefined }
 }
 
-function buildIndex(structure: Structure, index: Map<string, IndexEntry>, sI: number) {
+function buildIndex(structure: Structure, index: Map<string, IndexEntry>, sI: number, traceOnly: boolean) {
     for (const unit of structure.units) {
         if (unit.kind !== Unit.Kind.Atomic) continue;
 
@@ -150,6 +150,7 @@ function buildIndex(structure: Structure, index: Map<string, IndexEntry>, sI: nu
 
         const chainsIt = Segmentation.transientSegments(unit.model.atomicHierarchy.chainAtomSegments, elements);
         const residuesIt = Segmentation.transientSegments(unit.model.atomicHierarchy.residueAtomSegments, elements);
+        const traceElementIndex = unit.model.atomicHierarchy.derived.residue.traceElementIndex;
 
         while (chainsIt.hasNext) {
             const chainSegment = chainsIt.move();
@@ -160,8 +161,15 @@ function buildIndex(structure: Structure, index: Map<string, IndexEntry>, sI: nu
 
                 if (!dbName[rI]) continue;
 
-                const start = elements[residueSegment.start];
-                const end = elements[residueSegment.end - 1] + 1 as ElementIndex;
+                let start, end;
+                if (traceOnly) {
+                    start = traceElementIndex[rI];
+                    if (start === -1) continue;
+                    end = start + 1 as ElementIndex;
+                } else {
+                    start = elements[residueSegment.start];
+                    end = elements[residueSegment.end - 1] + 1 as ElementIndex;
+                }
 
                 const key = `${dbName[rI]}-${accession[rI]}-${num[rI]}`;
 

+ 18 - 9
src/mol-plugin-ui/structure/superposition.tsx

@@ -1,7 +1,8 @@
 /**
- * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2020-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ * @author Sebastian Bittrich <sebastian.bittrich@rcsb.org>
  */
 
 import { CollapsableControls, PurePluginUIComponent } from '../base';
@@ -49,6 +50,7 @@ export class StructureSuperpositionControls extends CollapsableControls {
 
 export const StructureSuperpositionParams = {
     alignSequences: PD.Boolean(true, { isEssential: true, description: 'For Chain-based 3D superposition, perform a sequence alignment and use the aligned residue pairs to guide the 3D superposition.' }),
+    traceOnly: PD.Boolean(true, { description: 'For Chain- and Uniprot-based 3D superposition, base superposition only on CA (and equivalent) atoms.' })
 };
 const DefaultStructureSuperpositionOptions = PD.getDefaultValues(StructureSuperpositionParams);
 export type StructureSuperpositionOptions = PD.ValuesFor<typeof StructureSuperpositionParams>
@@ -123,10 +125,10 @@ export class SuperpositionControls extends PurePluginUIComponent<{ }, Superposit
     }
 
     superposeChains = async () => {
-        const { query } = StructureSelectionQueries.trace;
+        const { query } = this.state.options.traceOnly ? StructureSelectionQueries.trace : StructureSelectionQueries.polymer;
         const entries = this.chainEntries;
 
-        const traceLocis = entries.map((e, i) => {
+        const locis = entries.map((e, i) => {
             const s = StructureElement.Loci.toStructure(e.loci);
             const loci = StructureSelection.toLociWithSourceUnits(query(new QueryContext(s)));
             return StructureElement.Loci.remap(loci, i === 0
@@ -136,11 +138,11 @@ export class SuperpositionControls extends PurePluginUIComponent<{ }, Superposit
         });
 
         const transforms = this.state.options.alignSequences
-            ? alignAndSuperpose(traceLocis)
-            : superpose(traceLocis);
+            ? alignAndSuperpose(locis)
+            : superpose(locis);
 
         const eA = entries[0];
-        for (let i = 1, il = traceLocis.length; i < il; ++i) {
+        for (let i = 1, il = locis.length; i < il; ++i) {
             const eB = entries[i];
             const { bTransform, rmsd } = transforms[i - 1];
             await this.transform(eB.cell, bTransform);
@@ -148,6 +150,7 @@ export class SuperpositionControls extends PurePluginUIComponent<{ }, Superposit
             const labelB = stripTags(eB.label);
             this.plugin.log.info(`Superposed [${labelA}] and [${labelB}] with RMSD ${rmsd.toFixed(2)}.`);
         }
+        await this.cameraReset();
     };
 
     superposeAtoms = async () => {
@@ -171,13 +174,15 @@ export class SuperpositionControls extends PurePluginUIComponent<{ }, Superposit
             const count = entries[i].atoms.length;
             this.plugin.log.info(`Superposed ${count} ${count === 1 ? 'atom' : 'atoms'} of [${labelA}] and [${labelB}] with RMSD ${rmsd.toFixed(2)}.`);
         }
+        await this.cameraReset();
     };
 
     superposeDb = async () => {
         const input = this.plugin.managers.structure.hierarchy.behaviors.selection.value.structures;
+        const traceOnly = this.state.options.traceOnly;
 
         const structures = input.map(s => s.cell.obj?.data!);
-        const { entries, failedPairs, zeroOverlapPairs } = alignAndSuperposeWithSIFTSMapping(structures);
+        const { entries, failedPairs, zeroOverlapPairs } = alignAndSuperposeWithSIFTSMapping(structures, { traceOnly });
 
         let rmsd = 0;
 
@@ -202,11 +207,15 @@ export class SuperpositionControls extends PurePluginUIComponent<{ }, Superposit
 
         if (entries.length) {
             this.plugin.log.info(`Superposed ${entries.length + 1} structures with avg. RMSD ${rmsd.toFixed(2)} Å.`);
-            await new Promise(res => requestAnimationFrame(res));
-            PluginCommands.Camera.Reset(this.plugin);
+            await this.cameraReset();
         }
     };
 
+    async cameraReset() {
+        await new Promise(res => requestAnimationFrame(res));
+        PluginCommands.Camera.Reset(this.plugin);
+    }
+
     toggleByChains = () => this.setState({ action: this.state.action === 'byChains' ? void 0 : 'byChains' });
     toggleByAtoms = () => this.setState({ action: this.state.action === 'byAtoms' ? void 0 : 'byAtoms' });
     toggleOptions = () => this.setState({ action: this.state.action === 'options' ? void 0 : 'options' });