Browse Source

Merge branch 'master' into dev-sb-v2

# Conflicts:
#	CHANGELOG.md
JonStargaryen 3 years ago
parent
commit
faefae3889
7 changed files with 189 additions and 41 deletions
  1. 12 0
      CHANGELOG.md
  2. 2 2
      package-lock.json
  3. 1 1
      package.json
  4. 65 0
      src/viewer/helpers/preset.ts
  5. 16 8
      src/viewer/helpers/selection.ts
  6. 81 27
      src/viewer/index.html
  7. 12 3
      src/viewer/ui/strucmotif.tsx

+ 12 - 0
CHANGELOG.md

@@ -8,6 +8,18 @@
 - Removed `pluginCall()` and `getPlugin()` - use `plugin()` getter instead
 - Signature changes to `setFocus()`, `select()`, `clearSelection()`, and `createComponent()`: effectively the overloaded methods were replaced by ones that use `Target` objects to reference residues/ranges
 
+## [1.7.2] - 2021-07-05
+### Bug fixes
+- Code that determines assemblyId is now aware of label_asym_id
+
+## [1.7.1] - 2021-07-02
+### Bug fixes
+- Strucmotif UI now reports chained operators that can be used by sierra/arches/strucmotif
+- Motif preset now works if no assemblyId was provided
+
+### Breaking Changes
+- Rename experimental 'structOperExpression' prop to 'struct_oper_id'
+
 ## [1.7.0] - 2021-06-24
 ### Added
 - Visualize (an arbitrary number of) structural motifs

+ 2 - 2
package-lock.json

@@ -1,12 +1,12 @@
 {
     "name": "@rcsb/rcsb-molstar",
-    "version": "1.7.0",
+    "version": "1.7.2",
     "lockfileVersion": 2,
     "requires": true,
     "packages": {
         "": {
             "name": "@rcsb/rcsb-molstar",
-            "version": "1.7.0",
+            "version": "1.7.2",
             "license": "MIT",
             "devDependencies": {
                 "@types/react": "^17.0.2",

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
     "name": "@rcsb/rcsb-molstar",
-    "version": "1.7.0",
+    "version": "1.7.2",
     "description": "RCSB PDB apps and props based on Mol*.",
     "homepage": "https://github.com/rcsb/rcsb-molstar#readme",
     "repository": {

+ 65 - 0
src/viewer/helpers/preset.ts

@@ -128,6 +128,8 @@ export const RcsbPreset = TrajectoryHierarchyPresetProvider({
         const p = params.preset;
 
         const modelParams = { modelIndex: p.modelIndex || 0 };
+        // jump through some hoops to determine the unknown assemblyId of query selections
+        if (p.kind === 'motif') determineAssemblyId(trajectory, p);
 
         const structureParams: RootStructureDefinition.Params = { name: 'model', params: {} };
         if (p.assemblyId && p.assemblyId !== '' && p.assemblyId !== '0') {
@@ -292,6 +294,69 @@ export const RcsbPreset = TrajectoryHierarchyPresetProvider({
     }
 });
 
+function determineAssemblyId(traj: any, p: MotifProps) {
+    // nothing to do if assembly is known
+    if (p.assemblyId && p.assemblyId !== '' && p.assemblyId !== '0') return;
+
+    function equals(expr: string, val: string): boolean {
+        const list = parseOperatorList(expr);
+        const split = val.split('x');
+        let matches = 0;
+        for (let i = 0, il = Math.min(list.length, split.length); i < il; i++) {
+            if (list[i].indexOf(split[i]) !== -1) matches++;
+        }
+        return matches === split.length;
+    }
+
+    function parseOperatorList(value: string): string[][] {
+        // '(X0)(1-5)' becomes [['X0'], ['1', '2', '3', '4', '5']]
+        // kudos to Glen van Ginkel.
+
+        const oeRegex = /\(?([^()]+)\)?]*/g, groups: string[] = [], ret: string[][] = [];
+
+        let g: any;
+        while (g = oeRegex.exec(value)) groups[groups.length] = g[1];
+
+        groups.forEach(g => {
+            const group: string[] = [];
+            g.split(',').forEach(e => {
+                const dashIndex = e.indexOf('-');
+                if (dashIndex > 0) {
+                    const from = parseInt(e.substring(0, dashIndex)), to = parseInt(e.substr(dashIndex + 1));
+                    for (let i = from; i <= to; i++) group[group.length] = i.toString();
+                } else {
+                    group[group.length] = e.trim();
+                }
+            });
+            ret[ret.length] = group;
+        });
+
+        return ret;
+    }
+
+    // set of provided [struct_oper_id, label_asym_id] combinations
+    const ids = p.targets.map(t => [t.struct_oper_id || '1', t.label_asym_id!]).filter((x, i, a) => a.indexOf(x) === i);
+
+    try {
+        // find first assembly that contains all requested struct_oper_ids - if multiple, the first will be returned
+        const pdbx_struct_assembly_gen = traj.obj.data.representative.sourceData.data.frame.categories.pdbx_struct_assembly_gen;
+        const assembly_id = pdbx_struct_assembly_gen.getField('assembly_id');
+        const oper_expression = pdbx_struct_assembly_gen.getField('oper_expression');
+        const asym_id_list = pdbx_struct_assembly_gen.getField('asym_id_list');
+
+        for (let i = 0, il = pdbx_struct_assembly_gen.rowCount; i < il; i++) {
+            if (ids.some(val => !equals(oper_expression.str(i), val[0]) || asym_id_list.str(i).indexOf(val[1]) === -1)) continue;
+
+            Object.assign(p, { assemblyId: assembly_id.str(i) });
+            return;
+        }
+    } catch (error) {
+        console.warn(error);
+    }
+    // default to '1' if error or legitimately not found
+    Object.assign(p, { assemblyId: '1' });
+}
+
 async function initVolumeStreaming(plugin: PluginContext, structure: StructureObject, props?: { overrideRadius?: number, hiddenChannels: string[] }) {
     if (!structure?.cell?.parent) return;
 

+ 16 - 8
src/viewer/helpers/selection.ts

@@ -32,7 +32,7 @@ export type Target = {
      * combination thereof. Specify the assemblyId when using this selector. Order matters, use order as specified in
      * the source CIF file.
      */
-    readonly structOperExpression?: string
+    readonly struct_oper_id?: string
 }
 
 type SelectBase = {
@@ -58,7 +58,7 @@ export type SelectionExpression = {
 
 /**
  * This serves as adapter between the strucmotif-/BioJava-approach to identify transformed chains and the Mol* way.
- * Looks for 'structOperExpression', converts it to an 'operatorName', and removes the original value. This will
+ * Looks for 'struct_oper_id', converts it to an 'operatorName', and removes the original value. This will
  * override pre-existing 'operatorName' values.
  * @param targets collection to process
  * @param structure parent structure
@@ -66,9 +66,9 @@ export type SelectionExpression = {
  */
 export function normalizeTargets(targets: Target[], structure: Structure, operatorName: string = 'ASM_1'): Target[] {
     return targets.map(t => {
-        if (t.structOperExpression) {
-            const { structOperExpression, ...others } = t;
-            const oper = toOperatorName(structure, structOperExpression);
+        if (t.struct_oper_id) {
+            const { struct_oper_id, ...others } = t;
+            const oper = toOperatorName(structure, struct_oper_id);
             return { ...others, operatorName: oper };
         }
         return t.operatorName ? t : { ...t, operatorName };
@@ -76,13 +76,21 @@ export function normalizeTargets(targets: Target[], structure: Structure, operat
 }
 
 function toOperatorName(structure: Structure, expression: string): string {
-    // Mol*-internal representation is flipped ('5xX0' insteadof 'X0x5')
-    expression = expression.indexOf('x') === -1 ? expression : expression.split('x').reverse().join('x');
+    function join(opers: any[]) {
+        // this makes the assumptions that '1' is the identity operator
+        if (!opers || !opers.length) return '1';
+        if (opers.length > 1) {
+            // Mol* operators are right-to-left
+            return opers[1] + 'x' + opers[0];
+        }
+        return opers[0];
+    }
+
     for (const unit of structure.units) {
         const assembly = unit.conformation.operator.assembly;
         if (!assembly) continue;
 
-        if (expression === assembly.operList.join('x')) return `ASM_${assembly.operId}`;
+        if (expression === join(assembly.operList)) return `ASM_${assembly.operId}`;
     }
     // TODO better error handling?
     throw Error(`Unable to find expression '${expression}'`);

+ 81 - 27
src/viewer/index.html

@@ -83,17 +83,17 @@
 
             &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
 
-            Superposed
-            <button style="padding: 3px;" onclick="superposed()">3PQR | 1U19</button>
+            <button style="padding: 3px;" onclick="superposed()">Superposed</button>
 
             &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
 
-            Superpose Motifs
-            <button style="padding: 3px;" onclick="motifs()">4CHA | 6YIW</button>
+            <button style="padding: 3px;" onclick="motifs1()">Motifs 1</button>
             &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
 
-            Superpose Propset
-            <button style="padding: 3px" onclick="propset()">4HHB | 1OJ6</button>
+            <button style="padding: 3px;" onclick="motifs2()">Motifs 2</button>
+            &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+
+            <button style="padding: 3px" onclick="propset()">Propset</button>
         </div>
         <script>
 
@@ -360,7 +360,7 @@
                         targets: [
                             { label_asym_id: 'A', label_seq_id: 61 },
                             { label_asym_id: 'A', label_seq_id: 69 },
-                            { label_asym_id: 'A', label_seq_id: 87, structOperExpression: '4' }
+                            { label_asym_id: 'A', label_seq_id: 87, struct_oper_id: '4' }
                         ],
                     }
                 },
@@ -371,9 +371,21 @@
                         kind: 'motif',
                         assemblyId: '6',
                         targets: [
-                            { label_asym_id: 'A', label_seq_id: 46, structOperExpression: 'X0x5' },
-                            { label_asym_id: 'A', label_seq_id: 49, structOperExpression: 'X0x5' },
-                            { label_asym_id: 'A', label_seq_id: 145, structOperExpression: 'X0x5' }
+                            { label_asym_id: 'A', label_seq_id: 46, struct_oper_id: 'X0x5' },
+                            { label_asym_id: 'A', label_seq_id: 49, struct_oper_id: 'X0x5' },
+                            { label_asym_id: 'A', label_seq_id: 145, struct_oper_id: 'X0x5' }
+                        ],
+                    }
+                },
+                {
+                    id: '1G2F',
+                    info: 'motif selection with strucmotif-expression & chain: STRUCTURE OF A CYS2HIS2 ZINC FINGER/TATA BOX COMPLEX (TATAZF;CLONE #6)',
+                    props: {
+                        kind: 'motif',
+                        targets: [
+                            { label_asym_id: 'F', label_seq_id: 7 },
+                            { label_asym_id: 'F', label_seq_id: 25 },
+                            { label_asym_id: 'F', label_seq_id: 29 }
                         ],
                     }
                 }
@@ -407,40 +419,82 @@
                     });
             }
 
-            function motifs() {
+            function motifs1() {
                 viewer.clear()
                     .then(function() {
                         return viewer.loadPdbIds([{
-                            pdbId: '4cha',
+                            pdbId: '5CBG',
                             props: {
-                                label: '4CHA',
+                                label: '5CBG',
                                 kind: 'motif',
-                                assemblyId: '1',
+                                // assemblyId: '2', // library should be able to infer assemblyId of the query
                                 targets: [
-                                    { label_asym_id: 'B', label_seq_id: 42 },
-                                    { label_asym_id: 'B', label_seq_id: 87 },
-                                    { label_asym_id: 'C', label_seq_id: 47 }
+                                    { label_asym_id: 'C', label_seq_id: 30, struct_oper_id: '3' },
+                                    { label_asym_id: 'C', label_seq_id: 32, struct_oper_id: '3' },
+                                    { label_asym_id: 'C', label_seq_id: 34, struct_oper_id: '3' }
                                 ],
                                 // color: 13203230
                             }
                         }, {
-                            pdbId: '6yiw',
+                            pdbId: '2W02',
                             props: {
-                                label: '6YIW #1',
+                                label: '2W02 #1',
                                 kind: 'motif',
-                                assemblyId: '1',
+                                assemblyId: '2',
                                 targets: [
-                                    { label_asym_id: 'A', label_seq_id: 40 },
-                                    { label_asym_id: 'A', label_seq_id: 84 },
-                                    { label_asym_id: 'A', label_seq_id: 177 }
+                                    { label_asym_id: 'B', label_seq_id: 519 },
+                                    { label_asym_id: 'B', label_seq_id: 517 },
+                                    { label_asym_id: 'B', label_seq_id: 515 }
                                 ],
                                 // color: 4947916
                             },
                             matrix: [
-                                0.1651637134205112, 0.7020365618749254, 0.6927233311791812, 0,
-                                0.39076998819946046, 0.5983062863806071, -0.6995201240851049, 0,
-                                -0.9055494266420474, 0.3862308292566522, -0.17551633097799743, 0,
-                                2.4392572425563213, 13.865339409688449, 28.536458135725827, 1
+                                0.7953831708253857, -0.6048923987758781, 0.03835097744411625, 0,
+                                -0.23732979915044658, -0.2525976533016715, 0.9380133218572628, 0,
+                                -0.5577097614377604, -0.7551818399893184, -0.344470913935246, 0,
+                                154.77888998938096, 207.0215940587305, 25.17873980937774, 1
+                            ]
+                        }]);
+                    })
+                    .then(function() {
+                        viewer.resetCamera(0)
+                    });
+            }
+
+            function motifs2() {
+                viewer.clear()
+                    .then(function() {
+                        return viewer.loadPdbIds([{
+                            pdbId: '1M4X',
+                            props: {
+                                label: '1M4X',
+                                kind: 'motif',
+                                // assemblyId: '7', // library should be able to infer assemblyId of the query
+                                targets: [
+                                    { label_asym_id: 'C', label_seq_id: 161, struct_oper_id: 'Px81' },
+                                    { label_asym_id: 'C', label_seq_id: 165, struct_oper_id: 'Px81' },
+                                    { label_asym_id: 'C', label_seq_id: 170, struct_oper_id: 'Px81' }
+                                ],
+                                // color: 13203230
+                            }
+                        }, {
+                            pdbId: '6KIN',
+                            props: {
+                                label: '6KIN @ 1.88 RMSD',
+                                kind: 'motif',
+                                assemblyId: '1',
+                                targets: [
+                                    { label_asym_id: 'B', label_seq_id: 160 },
+                                    { label_asym_id: 'B', label_seq_id: 163 },
+                                    { label_asym_id: 'B', label_seq_id: 167 }
+                                ],
+                                // color: 13203230
+                            },
+                            matrix: [
+                                -0.8175763932146345, -0.5719837107806522, 0.0663586909134339, 0,
+                                -0.4927708198934652, 0.7546183669143351, 0.43327593907008527, 0,
+                                -0.29790226638894773, 0.32153655300313416, -0.8988150448024284, 0,
+                                340.5134831570596, -107.81055538151446, 774.3972821273203, 1.0
                             ]
                         }]);
                     })

+ 12 - 3
src/viewer/ui/strucmotif.tsx

@@ -117,15 +117,24 @@ class SubmitControls extends PurePluginUIComponent<{}, { isBusy: boolean, residu
             return false;
         };
 
+        function join(opers: any[]) {
+            // this makes the assumptions that '1' is the identity operator
+            if (!opers || !opers.length) return '1';
+            if (opers.length > 1) {
+                // Mol* operators are right-to-left
+                return opers[1] + 'x' + opers[0];
+            }
+            return opers[0];
+        }
+
         const loci = this.plugin.managers.structure.selection.additionsHistory;
         for (let i = 0; i < Math.min(MAX_MOTIF_SIZE, loci.length); i++) {
             const l = loci[i];
             const { structure, elements } = l.loci;
             pdbId.add(structure.model.entry);
 
-            // need to reverse here as Mol*, for some reason, inverts the order specified in the CIF file
-            const struct_oper_list_ids = StructureProperties.unit.pdbx_struct_oper_list_ids(location).reverse();
-            const struct_oper_id = struct_oper_list_ids?.length ? struct_oper_list_ids.join('x') : '1';
+            const struct_oper_list_ids = StructureProperties.unit.pdbx_struct_oper_list_ids(location);
+            const struct_oper_id = join(struct_oper_list_ids);
 
             // only first element and only first index will be considered (ignoring multiple residues)
             if (!determineBackboneAtom(structure, elements[0])) {