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

working for single operators

JonStargaryen 3 роки тому
батько
коміт
8d46e1c97e

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

@@ -129,6 +129,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,68 @@ 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_ids
+    const struct_oper_ids = p.targets.map(t => t.struct_oper_id || '1').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');
+
+        for (let i = 0, il = pdbx_struct_assembly_gen.rowCount; i < il; i++) {
+            if (struct_oper_ids.some(val => !equals(oper_expression.str(i), val))) 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;
 

+ 0 - 2
src/viewer/helpers/selection.ts

@@ -64,8 +64,6 @@ 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');
     for (const unit of structure.units) {
         const assembly = unit.conformation.operator.assembly;
         if (!assembly) continue;

+ 20 - 23
src/viewer/index.html

@@ -83,17 +83,14 @@
 
             &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="propset()">Propset</button>
         </div>
         <script>
 
@@ -407,40 +404,40 @@
                     });
             }
 
-            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
                             ]
                         }]);
                     })

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

@@ -117,14 +117,6 @@ class SubmitControls extends PurePluginUIComponent<{}, { isBusy: boolean, residu
             return false;
         };
 
-        function join(opers: any[]) {
-            if (!opers || !opers.length) return '1';
-            if (opers.length > 1) {
-                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];
@@ -132,7 +124,9 @@ class SubmitControls extends PurePluginUIComponent<{}, { isBusy: boolean, residu
             pdbId.add(structure.model.entry);
 
             const struct_oper_list_ids = StructureProperties.unit.pdbx_struct_oper_list_ids(location);
-            const struct_oper_id = join(struct_oper_list_ids);
+            // this makes the assumptions that '1' is the identity operator
+            // TODO is the order always correct or does Mol* sometimes gives right-to-left order?
+            const struct_oper_id = struct_oper_list_ids?.length ? struct_oper_list_ids.join('x') : '1';
 
             // only first element and only first index will be considered (ignoring multiple residues)
             if (!determineBackboneAtom(structure, elements[0])) {