Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extend NGL dynophore visualization #28

Merged
merged 9 commits into from
May 26, 2021
11 changes: 3 additions & 8 deletions devtools/conda-envs/test_env.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,9 @@ dependencies:
- matplotlib
- seaborn
- jupyter
- jupyterlab=2
- nodejs # Remove when using JLab3
- jupyterlab>=3
- ipywidgets>=7.5
- nglview
- tqdm
- nglview>=3
- rdkit
- mdanalysis
# Testing
Expand All @@ -35,7 +33,4 @@ dependencies:
- flake8
- pip:
- black-nb
- flake8-nb

## For Jupyter lab extensions, run:
# jupyter labextension install @jupyter-widgets/jupyterlab-manager nglview-js-widgets @jupyterlab/toc @ijmbarr/jupyterlab_spellchecker
- flake8-nb
148 changes: 114 additions & 34 deletions docs/tutorials/explore_view3d.ipynb

Large diffs are not rendered by default.

20 changes: 20 additions & 0 deletions dynophores/core/dynophore.py
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,26 @@ def frequency(self):

return self.count.apply(lambda x: round(x / self.n_frames * 100, 2))

@property
def unique_envpartners_chain_residue_number(self):
"""
List of unique environmental partners (chain and residue number).
Useful for 3D visualization of interacting pocket residues.

Returns
-------
list of tuple (str, int)
List of (chain, residue number) tuples.
"""

envpartners = [
(envpartner.chain, envpartner.residue_number)
for superfeature_id, superfeature in self.superfeatures.items()
for envpartner_id, envpartner in superfeature.envpartners.items()
]
envpartners = set(envpartners)
return envpartners

def _raise_keyerror_if_invalid_superfeature_id(self, superfeature_id):
"""
Check if dynophore has a certain superfeature (by name).
Expand Down
28 changes: 17 additions & 11 deletions dynophores/tests/viz/test_view3d_interactive.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,23 @@


@pytest.mark.parametrize(
"pdb_path, dcd_path",
"pdb_path, dcd_path, visualization_type",
[
(
PATH_TEST_DATA / "in/startframe.pdb",
None,
),
(
PATH_TEST_DATA / "in/startframe.pdb",
PATH_TEST_DATA / "in/trajectory.dcd",
),
(PATH_TEST_DATA / "in/startframe.pdb", None, "spheres"),
(PATH_TEST_DATA / "in/startframe.pdb", PATH_TEST_DATA / "in/trajectory.dcd", "points"),
],
)
def test_show(dynophore, pdb_path, dcd_path):
view3d.show(dynophore, pdb_path, dcd_path)
def test_show(dynophore, pdb_path, dcd_path, visualization_type):
view3d.show(dynophore, pdb_path, dcd_path, visualization_type)


@pytest.mark.parametrize(
"pdb_path, dcd_path, visualization_type",
[
(PATH_TEST_DATA / "in/startframe.pdb", None, "xxx"),
],
)
def test_show_raises(dynophore, pdb_path, dcd_path, visualization_type):

with pytest.raises(ValueError):
view3d.show(dynophore, pdb_path, dcd_path, visualization_type)
134 changes: 116 additions & 18 deletions dynophores/viz/view3d/interactive.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,37 +11,59 @@

warnings.filterwarnings("ignore", category=DeprecationWarning)

MACROMOLECULE_COLOR = "#005780" # blue
LIGAND_COLOR = "#808080" # grey

def show(pml_path, pdb_path, dcd_path=None):

def show(
dynophore,
pdb_path,
dcd_path=None,
visualization_type="spheres",
macromolecule_color=MACROMOLECULE_COLOR,
):
"""
Show the dynophore point cloud with its ligand-bound structure and optionally the underlying
MD trajectory.

Parameters
----------
pml_path : str or pathlib.Path
Path to PML file (dynophore).
dynophore : dynophore.Dynophore
Dynophore.
pdb_path : str or pathlib.Path
Path to PDB file (structure/topology)
dcd_path : None or str or pathlib.Path
Optionally: Path to DCD file (trajectory).
visualization_type : str
Visualization type for dynophore cloud: `spheres` or `points`
macromolecule_color : str
Hex code for macromolecule color.

Returns
-------
nglview.widget.NGLWidget
Visualization with the NGL Viewer.
"""

# Show structure or trajectory
if dcd_path is None:
view = _show_structure(pdb_path)
else:
view = _show_trajectory(pdb_path, dcd_path)

# Set representation and color for protein and ligand
view.clear_representations()
view.add_representation("cartoon", selection="protein", color="grey")
view.add_representation("ball+stick", selection="ligand")
view.add_representation("cartoon", selection="protein", color=macromolecule_color)
view.add_representation("hyperball", selection="ligand")

# Add interacting pocket residues
envpartners = dynophore.unique_envpartners_chain_residue_number
envpartners_string = " or ".join(
[f"(:{chain} and {residue_number})" for chain, residue_number in envpartners]
)
view.add_representation("licorice", selection=envpartners_string)
# Add dynophore
_add_dynophore(view, dynophore, visualization_type)

view = _add_dynophore(view, pml_path)
return view


Expand Down Expand Up @@ -71,6 +93,8 @@ def _show_trajectory(pdb_path, dcd_path):

Parameters
----------
pdb_path : str or pathlib.Path
Path to PDB file (structure/topology)
dcd_path : None or str or pathlib.Path
Optionally: Path to DCD file (trajectory).

Expand All @@ -85,7 +109,7 @@ def _show_trajectory(pdb_path, dcd_path):
return view


def _add_dynophore(view, dynophore):
def _add_dynophore(view, dynophore, visualization_type):
"""
Add the dynophore point cloud to an existing view of its underlying structure (and optionally
its trajectory).
Expand All @@ -97,6 +121,9 @@ def _add_dynophore(view, dynophore):
belonging to the dynophore.
dynophore : Dynophore
Dynophore data (includes data from JSON and PML file).
visualization_type : str
Visualization type for dynophore cloud: `spheres` or `points`


Returns
-------
Expand All @@ -105,19 +132,90 @@ def _add_dynophore(view, dynophore):
"""

for _, superfeature in dynophore.superfeatures.items():
sphere_buffer = {"position": [], "color": [], "radius": []}
buffer = {"position": [], "color": [], "radius": []}
for point in superfeature.cloud.points:
sphere_buffer["position"] += [point.x, point.y, point.z]
sphere_buffer["color"] += hex_to_rgb(superfeature.color)
sphere_buffer["radius"] += [0.1]
js = f"""
var params = {sphere_buffer};
var shape = new NGL.Shape('{superfeature.id}');
buffer["position"] += [point.x, point.y, point.z]
buffer["color"] += hex_to_rgb(superfeature.color)
buffer["radius"] += [0.1]

if visualization_type == "spheres":
js = _js_sphere_buffer(buffer, superfeature.id)
elif visualization_type == "points":
js = _js_point_buffer(buffer, superfeature.id)
else:
raise ValueError(
"Visualization style unknown. Please choose from `spheres` or `points`."
)

view._js(js)

return view


def _js_sphere_buffer(buffer, superfeature_id):
"""
Create JavaScript string generating a sphere buffer from buffer parameters.

Parameters
----------
buffer : str
Buffer parameters.
superfeature_id : str
Superfeature ID.

Returns
-------
JavaScript string generating a sphere buffer.
"""

return f"""
var params = {buffer};
var shape = new NGL.Shape('{superfeature_id}');
var buffer = new NGL.SphereBuffer(params);

shape.addBuffer(buffer);
var shapeComp = this.stage.addComponentFromObject(shape);
shapeComp.addRepresentation("buffer");
shapeComp.addRepresentation("buffer", {{opacity:0.55}});
"""
view._js(js)

return view

def _js_point_buffer(buffer, superfeature_id):
"""
Create JavaScript string generating a point buffer from buffer parameters.
TODO: Visualization contains artifacts

Parameters
----------
buffer : str
Buffer parameters.
superfeature_id : str
Superfeature ID.

Returns
-------
JavaScript string generating a point buffer.
"""

return f"""
var point_buffer = new NGL.PointBuffer(
{{
position: new Float32Array({buffer["position"]}),
color: new Float32Array({buffer["color"]})
}},
{{
sizeAttenuation: true,
pointSize: 2,
opacity: 0.1,
useTexture: true,
alphaTest: 0.0,
edgeBleach: 0.7,
forceTransparent: true,
sortParticles: true
}}
)
var shape = new NGL.Shape('{superfeature_id}');

shape.addBuffer(point_buffer);
var shapeComp = this.stage.addComponentFromObject(shape);
shapeComp.addRepresentation("buffer", {{ opacity: 0.55 }});
"""