Browse Source

pdbx_sifts_xref PR feedback

dsehnal 3 years ago
parent
commit
bfe46e3604

+ 25 - 9
src/mol-model/structure/structure/util/superposition-db-mapping.ts → src/mol-model/structure/structure/util/superposition-sifts-mapping.ts

@@ -11,13 +11,19 @@ import { ElementIndex } from '../../model/indexing';
 import { Structure } from '../structure';
 import { Unit } from '../unit';
 
-export interface AlignmentResult {
+export interface AlignmentResultEntry {
     transform: MinimizeRmsd.Result,
     pivot: number,
     other: number
 }
 
-export function alignAndSuperposeWithBestDatabaseMapping(structures: Structure[]): AlignmentResult[] {
+export interface AlignmentResult {
+    entries: AlignmentResultEntry[],
+    zeroOverlapPairs: [number, number][],
+    failedPairs: [number, number][]
+}
+
+export function alignAndSuperposeWithSIFTSMapping(structures: Structure[]): AlignmentResult {
     const indexMap = new Map<string, IndexEntry>();
 
     for (let i = 0; i < structures.length; i++) {
@@ -29,14 +35,26 @@ export function alignAndSuperposeWithBestDatabaseMapping(structures: Structure[]
     // TODO: support non-first structure pivots
     const pairs = findPairs(structures.length, index);
 
-    const ret: AlignmentResult[] = [];
+    const zeroOverlapPairs: AlignmentResult['zeroOverlapPairs'] = [];
+    const failedPairs: AlignmentResult['failedPairs'] = [];
+
+
+    const entries: AlignmentResultEntry[] = [];
     for (const p of pairs) {
-        const [a, b] = getPositionTables(index, p.i, p.j, p.count);
-        const transform = MinimizeRmsd.compute({ a, b });
-        ret.push({ transform, pivot: p.i, other: p.j });
+        if (p.count === 0) {
+            zeroOverlapPairs.push([p.i, p.j]);
+        } else {
+            const [a, b] = getPositionTables(index, p.i, p.j, p.count);
+            const transform = MinimizeRmsd.compute({ a, b });
+            if (Number.isNaN(transform.rmsd)) {
+                failedPairs.push([p.i, p.j]);
+            } else {
+                entries.push({ transform, pivot: p.i, other: p.j });
+            }
+        }
     }
 
-    return ret;
+    return { entries, zeroOverlapPairs, failedPairs };
 }
 
 function getPositionTables(index: IndexEntry[], pivot: number, other: number, N: number) {
@@ -159,6 +177,4 @@ function buildIndex(structure: Structure, index: Map<string, IndexEntry>, sI: nu
             }
         }
     }
-
-    console.log(index);
 }

+ 20 - 7
src/mol-plugin-ui/structure/superposition.tsx

@@ -20,7 +20,7 @@ import { ParameterControls } from '../controls/parameters';
 import { stripTags } from '../../mol-util/string';
 import { StructureSelectionHistoryEntry } from '../../mol-plugin-state/manager/structure/selection';
 import { ToggleSelectionModeButton } from './selection';
-import { alignAndSuperposeWithBestDatabaseMapping } from '../../mol-model/structure/structure/util/superposition-db-mapping';
+import { alignAndSuperposeWithSIFTSMapping } from '../../mol-model/structure/structure/util/superposition-sifts-mapping';
 import { PluginCommands } from '../../mol-plugin/commands';
 import { SIFTSMapping } from '../../mol-model-props/sequence/sifts-mapping';
 
@@ -48,7 +48,7 @@ export class StructureSuperpositionControls extends CollapsableControls {
 }
 
 export const StructureSuperpositionParams = {
-    alignSequences: PD.Boolean(true, { isEssential: true, description: 'Perform a sequence alignment and use the aligned residue pairs to guide the 3D superposition.' }),
+    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.' }),
 };
 const DefaultStructureSuperpositionOptions = PD.getDefaultValues(StructureSuperpositionParams);
 export type StructureSuperpositionOptions = PD.ValuesFor<typeof StructureSuperpositionParams>
@@ -176,18 +176,31 @@ export class SuperpositionControls extends PurePluginUIComponent<{ }, Superposit
     superposeDb = async () => {
         const input = this.plugin.managers.structure.hierarchy.behaviors.selection.value.structures;
 
-        const transforms = alignAndSuperposeWithBestDatabaseMapping(input.map(s => s.cell.obj?.data!));
+        const structures = input.map(s => s.cell.obj?.data!);
+        const { entries, failedPairs, zeroOverlapPairs } = alignAndSuperposeWithSIFTSMapping(structures);
 
         let rmsd = 0;
 
-        for (const xform of transforms) {
+        for (const xform of entries) {
             await this.transform(input[xform.other].cell, xform.transform.bTransform);
             rmsd += xform.transform.rmsd;
         }
 
-        rmsd /= Math.max(transforms.length - 1, 1);
+        rmsd /= Math.max(entries.length - 1, 1);
 
-        this.plugin.log.info(`Superposed ${input.length} structures with avg. RMSD ${rmsd.toFixed(2)} Å.`);
+        const formatPairs = (pairs: [number, number][]) => {
+            return `[${pairs.map(([i, j]) => `(${structures[i].models[0].entryId}, ${structures[j].models[0].entryId})`).join(', ')}]`;
+        };
+
+        if (zeroOverlapPairs.length) {
+            this.plugin.log.warn(`No UNIPROT mapping overlap between structures ${formatPairs(zeroOverlapPairs)}.`);
+        }
+
+        if (failedPairs.length) {
+            this.plugin.log.error(`Failed to superpose structures ${formatPairs(failedPairs)}.`);
+        }
+
+        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);
     };
@@ -323,7 +336,7 @@ export class SuperpositionControls extends PurePluginUIComponent<{ }, Superposit
 
     superposeByDbMapping() {
         return <>
-            <Button icon={SuperposeChainsSvg} title='Superpose structures using UNIPROT mapping.' className='msp-btn msp-btn-block' onClick={this.superposeDb} style={{ marginTop: '1px' }} disabled={this.state.isBusy}>
+            <Button icon={SuperposeChainsSvg} title='Superpose structures using intersection of residues from SIFTS UNIPROT mapping.' className='msp-btn msp-btn-block' onClick={this.superposeDb} style={{ marginTop: '1px' }} disabled={this.state.isBusy}>
                 Uniprot
             </Button>
         </>;