Browse Source

add assembly symmetry support

Alexander Rose 5 years ago
parent
commit
3fb02c3ea9

+ 39 - 0
src/structure-viewer/helpers/structure.ts

@@ -12,6 +12,9 @@ import { Vec3 } from 'molstar/lib/mol-math/linear-algebra';
 import { PluginContext } from 'molstar/lib/mol-plugin/context';
 import { PluginStateObject as PSO } from 'molstar/lib/mol-plugin/state/objects';
 import { Structure, StructureElement } from 'molstar/lib/mol-model/structure';
+import { AssemblySymmetryProvider } from 'molstar/lib/mol-model-props/rcsb/assembly-symmetry';
+import { Task } from 'molstar/lib/mol-task';
+import { AssemblySymmetry3D } from 'molstar/lib/mol-plugin/behavior/dynamic/custom-props/rcsb/assembly-symmetry';
 
 export class StructureView {
     async applyState(tree: StateBuilder) {
@@ -59,6 +62,15 @@ export class StructureView {
         }
     }
 
+    private async attachSymmetry() {
+        const assembly = this.getAssembly()
+        if (!assembly || assembly.data.isEmpty) return
+
+        await this.plugin.runTask(Task.create('Assembly symmetry', async runtime => {
+            await AssemblySymmetryProvider.attach({ fetch: this.plugin.fetch, runtime }, assembly.data)
+        }))
+    }
+
     async setAssembly(id: string) {
         const state = this.plugin.state.dataState;
         const tree = state.build();
@@ -110,6 +122,7 @@ export class StructureView {
                 )
         }
         await this.applyState(tree)
+        await this.attachSymmetry()
         await this.preset()
         await this.experimentalData.init()
     }
@@ -142,9 +155,35 @@ export class StructureView {
             }
         }
         await this.applyState(tree)
+        await this.attachSymmetry()
         await this.preset()
     }
 
+    async setSymmetry(symmetryIndex: number) {
+        const state = this.plugin.state.dataState;
+        const tree = state.build();
+        if (symmetryIndex === -1) {
+            tree.delete(StateElements.AssemblySymmetry)
+        } else {
+            if (state.tree.transforms.has(StateElements.AssemblySymmetry)) {
+                tree.to(StateElements.AssemblySymmetry).update(
+                    AssemblySymmetry3D,
+                    props => ({ ...props, symmetryIndex })
+                )
+            } else {
+                const assembly = this.getAssembly()
+                if (!assembly || assembly.data.isEmpty) return
+
+                const props = AssemblySymmetry3D.createDefaultParams(assembly, this.plugin)
+                tree.to(StateElements.Assembly).apply(
+                    AssemblySymmetry3D,
+                    { ...props, symmetryIndex }, { ref: StateElements.AssemblySymmetry }
+                )
+            }
+        }
+        await this.applyState(tree)
+    }
+
     constructor(private plugin: PluginContext) {
 
     }

+ 34 - 2
src/structure-viewer/index.html

@@ -73,9 +73,41 @@
                     id: '3PQR',
                     info: 'medium: polymer, carbs, ligands'
                 },
+                {
+                    id: '1A6D',
+                    info: 'medium: dihedral symmetry (D8)'
+                },
+                {
+                    id: '1LTI',
+                    info: 'medium: local symmetry (C5)'
+                },
+                {
+                    id: '1QO1',
+                    info: 'medium: C-alpha only, local symmetries (C10, C3)'
+                },
+                {
+                    id: '6V9Q',
+                    info: 'medium: local symmetries (H, C2)'
+                },
+                {
+                    id: '6R6B',
+                    info: 'medium: local symmetries (H x2)'
+                },
+                {
+                    id: '2VTU',
+                    info: 'medium: octahedral symmetry (O)'
+                },
+                {
+                    id: '4NWP',
+                    info: 'medium: tetrahedral symmetry (T)'
+                },
+                {
+                    id: '1RB8',
+                    info: 'medium-large: small virus capsid, icosahedral symmetry (I)'
+                },
                 {
                     id: '6QVK',
-                    info: 'large: The cryo-EM structure of bacteriophage phi29 prohead'
+                    info: 'large: The cryo-EM structure of bacteriophage phi29 prohead (C5)'
                 },
                 {
                     id: '5Y6P',
@@ -83,7 +115,7 @@
                 },
                 {
                     id: '6O2S',
-                    info: 'large: Deacetylated Microtubules'
+                    info: 'large: Deacetylated Microtubules (H)'
                 },
                 {
                     id: '5MQ7',

+ 2 - 1
src/structure-viewer/types.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -42,6 +42,7 @@ export enum StateElements {
     ModelProps = 'model-props',
     ModelUnitcell = 'model-unitcell',
     Assembly = 'assembly',
+    AssemblySymmetry = 'assembly-symmetry',
 
     VolumeStreaming = 'volume-streaming',
 }

+ 62 - 7
src/structure-viewer/ui/structure.tsx

@@ -16,6 +16,7 @@ import { Model } from 'molstar/lib/mol-model/structure';
 import { MmcifFormat } from 'molstar/lib/mol-model-formats/structure/mmcif';
 import { stringToWords } from 'molstar/lib/mol-util/string';
 import { ModelSymmetry } from 'molstar/lib/mol-model-formats/structure/property/symmetry';
+import { AssemblySymmetryProvider } from 'molstar/lib/mol-model-props/rcsb/assembly-symmetry'
 
 interface StructureControlsState extends CollapsableState {
     trajectoryRef: string
@@ -37,15 +38,40 @@ export class StructureControls<P, S extends StructureControlsState> extends Coll
         const tree = state.build();
 
         const assembly = this.getAssembly()
+        const symmetry = this.getSymmetry()
         const dataCtx = { structure: assembly && assembly.data }
 
         Object.keys(theme).forEach(k => {
             const repr = this.getRepresentation(k)
             if (repr && repr.params) {
-                const values = PD.getDefaultValues(themeCtx.colorThemeRegistry.get(name).getParams(dataCtx))
+                const name = theme[k]
+                const params = PD.getDefaultValues(themeCtx.colorThemeRegistry.get(name).getParams(dataCtx))
+                if (symmetry && name === 'rcsb-assembly-symmetry-cluster') {
+                    Object.assign(params, { symmetryIndex: symmetry.params.symmetryIndex })
+                }
                 tree.to(repr.transform.ref).update(
                     StateTransforms.Representation.StructureRepresentation3D,
-                    props => ({ ...props, colorTheme: { name: theme[k], params: values }})
+                    props => ({ ...props, colorTheme: { name, params }})
+                )
+            }
+        })
+        await this.structureView.applyState(tree)
+    }
+
+    async syncSymmetryIndex() {
+        const symmetry = this.getSymmetry()
+        if (!symmetry) return
+
+        const state = this.plugin.state.dataState;
+        const tree = state.build();
+
+        this.plugin.helpers.structureRepresentation.eachRepresentation(repr => {
+            if (repr.params?.values.colorTheme.name === 'rcsb-assembly-symmetry-cluster') {
+                const { name, params } = repr.params.values.colorTheme
+                Object.assign(params, { symmetryIndex: symmetry.params.symmetryIndex })
+                tree.to(repr.transform.ref).update(
+                    StateTransforms.Representation.StructureRepresentation3D,
+                    props => ({ ...props, colorTheme: { name, params }})
                 )
             }
         })
@@ -55,11 +81,14 @@ export class StructureControls<P, S extends StructureControlsState> extends Coll
     onChange = async (p: { param: PD.Base<any>, name: string, value: any }) => {
         // console.log('onChange', p.name, p.value)
         if (p.name === 'assembly') {
-            this.structureView.setAssembly(p.value)
+            await this.structureView.setAssembly(p.value)
         } else if (p.name === 'model') {
-            this.structureView.setModel(p.value)
+            await this.structureView.setModel(p.value)
+        } else if (p.name === 'symmetry') {
+            await this.structureView.setSymmetry(p.value)
+            await this.syncSymmetryIndex()
         } else if (p.name === 'colorThemes') {
-            this.setColorTheme(p.value)
+            await this.setColorTheme(p.value)
         }
     }
 
@@ -75,6 +104,7 @@ export class StructureControls<P, S extends StructureControlsState> extends Coll
 
         const modelOptions: [number, string][] = []
         const assemblyOptions: [string, string][] = []
+        const symmetryOptions: [number, string][] = [[-1, 'Off']]
 
         if (model && modelFromCrystallography(model.data)) {
             assemblyOptions.push([AssemblyNames.Deposited, 'Asymmetric Unit'])
@@ -123,6 +153,18 @@ export class StructureControls<P, S extends StructureControlsState> extends Coll
             types = registry.getApplicableTypes(assembly.data)
         }
 
+        let symmetryValue = -1
+        if (assembly) {
+            const assemblySymmetry = AssemblySymmetryProvider.get(assembly.data).value
+            if (assemblySymmetry) {
+                symmetryValue = 0
+                for (let i = 0, il = assemblySymmetry.length; i < il; ++i) {
+                    const { symbol, kind } = assemblySymmetry[i]
+                    symmetryOptions.push([i, `${i + 1}: ${symbol} ${kind}`])
+                }
+            }
+        }
+
         const colorThemes: { [k: string]: PD.Any } = {}
         for (let i = 0, il = types.length; i < il; ++i) {
             const type = types[i][0]
@@ -145,7 +187,10 @@ export class StructureControls<P, S extends StructureControlsState> extends Coll
                 isHidden: modelOptions.length === 1,
                 description: 'Show a specific model or the full ensamble of models'
             }),
-            // symmetry: PD.Select('todo', [['todo', 'todo']]), // TODO
+            symmetry: PD.Select(symmetryValue, symmetryOptions, {
+                isHidden: symmetryOptions.length === 1,
+                description: 'Show a specific assembly symmetry'
+            }),
             colorThemes: PD.Group(colorThemes, { isExpanded: true }),
         }
     }
@@ -154,6 +199,7 @@ export class StructureControls<P, S extends StructureControlsState> extends Coll
         const trajectory = this.getTrajectory()
         const model = this.getModel()
         const assembly = this.getAssembly()
+        const symmetry = this.getSymmetry()
 
         const { registry } = this.plugin.structureRepresentation
         const types = assembly ? registry.getApplicableTypes(assembly.data) : registry.types
@@ -184,10 +230,15 @@ export class StructureControls<P, S extends StructureControlsState> extends Coll
             }
         }
 
+        let symmetryValue = -1
+        if (symmetry) {
+            symmetryValue = symmetry.params.symmetryIndex
+        }
+
         return {
             assembly: assemblyValue,
             model: modelValue,
-            // symmetry: 'todo', // TODO
+            symmetry: symmetryValue,
             colorThemes,
         }
     }
@@ -251,6 +302,10 @@ export class StructureControls<P, S extends StructureControlsState> extends Coll
         return assemblies.length > 0 ? assemblies[0].obj : undefined
     }
 
+    private getSymmetry() {
+        return this.plugin.state.dataState.transforms.get(StateElements.AssemblySymmetry)
+    }
+
     defaultState() {
         return {
             isCollapsed: false,