import os
import nglview as nv
from IPython.display import IFrame
import pyunitwizard as puw
from openpharmacophore import Protein, ComplexBindingSite, Ligand, Pharmacophore
from openpharmacophore.molecular_systems.chem_feats import ChemFeatContainer
from openpharmacophore.io.pdb import write_pdb_block
from openpharmacophore import LigandBasedPharmacophore, LigandReceptorPharmacophore
from openpharmacophore import constants
[docs]class Viewer:
""" Class to visualize molecular systems and pharmacophores.
Wrapper around nglview NGLWidget
"""
# TODO: method to convert to HTML
def __init__(self):
self._widget = nv.NGLWidget()
self._components = []
self._has_protein = False
self._has_ligand = False
self._has_chem_feats = False
self._has_pharmacophore = False
@property
def n_components(self) -> int:
return len(self._widget._ngl_component_names)
@property
def has_protein(self) -> bool:
""" Returns true if the visualization contains a protein."""
return self._has_protein
@property
def has_ligand(self) -> bool:
""" Returns true if the visualization contains a ligand. """
return self._has_ligand
@property
def has_chem_feats(self) -> bool:
""" Returns true if the visualization contains chemical features. """
return self._has_chem_feats
@property
def has_pharmacophore(self) -> bool:
""" Returns true if the visualization contains a pharmacophore. """
return self._has_pharmacophore
[docs] def add_components(self, components):
""" Add multiple components to the viewer, such as Proteins, Ligands and
BindingSites.
Parameters
----------
components : list[Any]
"""
for comp in components:
self._components.append(comp)
[docs] def load_component(self, component, struct):
""" Load a component to the viewer, such as a Protein, Ligand or
BindingSite so, it can be visualized.
Parameters
----------
component : Any
struct : int, optional
Structure, frame or conformer index.
"""
if isinstance(component, Protein):
text_struct = nv.TextStructure(
write_pdb_block(component.topology, component.coords, conformer=0),
ext="pdb")
self._widget.add_component(text_struct)
self._has_protein = True
elif isinstance(component, ComplexBindingSite):
self._add_molecule(component.to_rdkit(), struct)
self._has_protein = True
elif isinstance(component, Ligand):
self._add_molecule(component.to_rdkit(), struct)
self._has_ligand = True
elif isinstance(component, ChemFeatContainer):
self.add_chem_feats(component)
elif isinstance(component, Pharmacophore):
self.add_pharmacophore(component)
elif isinstance(component, (LigandReceptorPharmacophore, LigandBasedPharmacophore)):
self.add_pharmacophore(component[struct])
else:
raise NotImplementedError
[docs] def add_chem_feats(self, chem_feats):
""" Add chemical features to the viewer.
Parameters
----------
chem_feats : ChemFeatContainer
"""
radius = 1.0 # in angstroms
for feat in chem_feats:
color = constants.PALETTE[feat.type]
coords = puw.get_value(feat.coords, "angstroms").tolist()
self._add_sphere(coords, radius, color, feat.type)
self._has_chem_feats = True
[docs] def add_pharmacophore(self, pharmacophore):
""" Add a pharmacophore to the viewer
Parameters
----------
pharmacophore : Pharmacophore
"""
for point in pharmacophore:
coords = puw.get_value(point.center, "angstroms")
color = constants.PALETTE[point.feature_name]
radius = puw.get_value(point.radius, "angstroms")
self._add_sphere(coords.tolist(), radius, color, point.feature_name)
if point.has_direction:
self._add_arrow(coords, radius, point.direction, color)
self._has_pharmacophore = True
def _add_sphere(self, centroid, radius, color, label):
""" Add a sphere to the viewer.
Parameters
----------
centroid : list[float]
A list of length 3 with the coordinates of the centroid
radius : float
Radius of the sphere
color : tuple[float]
Color in rgb format.
label : str
The label of the sphere
"""
self._widget.shape.add_sphere(centroid, color, radius, label)
def _add_arrow(self, centroid, length, direction, color):
""" Add an arrow to the visualization
Parameters
----------
centroid : np.ndarray
Aan array of length 3 with the coordinates of the centroid.
length : float
Length of the arrow.
direction : np.ndarray
Directionality of the arrow
color : tuple[float]
Color in rgb format.
"""
arrow_radius = 0.2
end_arrow = (centroid + length * direction * 2.0).tolist()
self._widget.shape.add_arrow(centroid, end_arrow, color, arrow_radius)
def _add_molecule(self, mol, conformer):
""" Add an rdkit molecule to the viewer.
Parameters
----------
mol : rdkit.Chem.Mol
conformer : int
"""
self._widget.add_component(
nv.RdkitStructure(mol, conf_id=conformer)
)
def _restore_widget(self):
""" Restore the widget to have a clean visualization."""
if self.n_components > 0:
self._widget = nv.NGLWidget()
[docs] def show(self, struct=0):
""" Shows the visualization.
Parameters
----------
struct : int, optional
Frame, structure or conformer index to show if there are any proteins,
ligands or pharmacophores with multiple structures.
Returns
-------
nv.NGLWidget
"""
self._restore_widget()
for comp in self._components:
self.load_component(comp, struct)
self._update_opacity()
return self._widget
def _show_html(self, src):
""" View as html. Useful for documentation notebooks.
Parameters
----------
src : str
Path to the html file.
Returns
-------
IFrame
"""
if not os.path.isfile(src):
nv.write_html(src, [self._widget])
return IFrame(src=src, width="100%", height="320vh")
def _update_opacity(self):
""" Add opacity to chemical features and pharmacophoric points. """
for ii, comp in enumerate(self._widget._ngl_component_names):
if comp == "nglview.shape.Shape":
self._widget.update_representation(component=ii, repr_index=0, opacity=0.6)
[docs] def set_protein_style(self, style):
""" Set the style of the protein.
Parameters
----------
style : str
Name of the style
"""
styles = [
"cartoon",
"licorice",
"ball+stick",
]
if style not in styles:
raise ValueError
self._widget.clear_representations()
self._widget.add_representation(style, selection="protein")
[docs] def to_nglview(self):
""" Get an NGLView widget representing the view.
Returns
-------
nv.NGLWidget
"""
return self._widget