Interface to PyMOL#

PyMOL is a prominent molecular visualization software. Arguably most users interact with it via it graphical user interface, but it also offers an extensive Python API. This API is interfaced by the biotite.interface.pymol subpackage to transfer structures to PyMOL and back again for convenient visualization.

Launching PyMOL#

By default biotite.interface.pymol starts PyMOL in object-oriented library mode. This means that there is no graphical user interface (GUI) available, by default: The interaction with PyMOL purely happens via its Python API and the resulting image can be inspected by saving it as PNG file. There is also the option to start an interactive PyMOL GUI by running

import biotite.interface.pymol as pymol_interface
pymol_interface.launch_pymol()

but for the purpose of this tutorial we will stick to the library mode. For now, just keep in mind that there are different options to launch PyMOL, which are more thoroughly described in the API reference of biotite.interface.pymol.

Transferring structures from Biotite to PyMOL and vice versa#

An AtomArray or AtomArrayStack can be converted into a PyMOL object via PyMOLObject.from_structure(). This static method returns a PyMOLObject - a wrapper around a PyMOL object (alias PyMOL model).

import numpy as np
from matplotlib.colors import to_rgb
import biotite
import biotite.database.rcsb as rcsb
import biotite.interface.pymol as pymol_interface
import biotite.structure.io.pdbx as pdbx

# Fetch and load cytochrome C structure and remove water
pdbx_file = pdbx.BinaryCIFFile.read(rcsb.fetch("1C75", "bcif"))
structure = pdbx.get_structure(pdbx_file, model=1, include_bonds=True)
cyt_c = structure[structure.res_name != "HOH"]

# Aromatic bonds can be either displayed as formal single/double bonds
# or as delocalized (dashed) bonds
pymol_cyt_c = pymol_interface.PyMOLObject.from_structure(cyt_c, delocalize_bonds=True)
# By default the name of the PyMOL object is auto-generated
print(pymol_cyt_c.name)
biotite_0

Note

A PyMOLObject becomes invalid when atoms are added to or are deleted from the wrapped PyMOL object or if the underlying PyMOL object does not exist anymore.

Conversely, PyMOLObject.to_structure() would convert a PyMOLObject object back into an AtomArray or AtomArrayStack.

From now on we can run any usual PyMOL commands on this PyMOL object by referring to its name and eventually render the image and save it as PNG file. For convenience, biotite.interface.pymol allows displaying the current canvas in a Jupyter notebook.

PNG_SIZE = (1500, 1500)

# Do not confuse with `PyMOLObject.show()` method we will see later
pymol_interface.show(PNG_SIZE, use_ray=True)
../../_images/pymol_3_0.png

Atom selections#

PyMOL uses selection expressions (strings) to select atoms for its commands. On the other side, Biotite uses NumPy-compatible indices to select atoms: boolean masks, integer arrays and single indices. To bring these two worlds together, NumPy-compatible indices can be converted to These boolean masks can be converted into selection expressions via the PyMOLObject.where() method.

# Select heme group
selection_expression = pymol_cyt_c.where(cyt_c.res_name == "HEC")
print(selection_expression)
%biotite_0 and (index 948-1021)

Invoking commands#

As mentioned above, PyMOL commands can be called as usual: The current PyMOL session is obtained via the pymol attribute and commands can be invoked using pymol.cmd.

pymol_interface.cmd.set("sphere_scale", 1.5)

To add syntactic sugar, most object-specific commands are available as PyMOLObject methods. These methods accept NumPy-compatible indices directly, without the need to call PyMOLObject.where().

# Style protein, use PyMOL-style selection string
pymol_cyt_c.show_as("cartoon", "polymer")
# PyMOLObject.color() command directly allows RGB values
pymol_cyt_c.color(to_rgb(biotite.colors["lightgreen"]), "polymer and name CA")

# Style heme group, use NumPy-style fancy indexing
heme_mask = cyt_c.res_name == "HEC"
pymol_cyt_c.show_as("sticks", heme_mask)
pymol_cyt_c.color(
    to_rgb(biotite.colors["lightorange"]), heme_mask & (cyt_c.element == "C")
)

# Style Fe2+ ion, use single index
fe_index = np.where(cyt_c.element == "FE")[0]
pymol_cyt_c.show_as("spheres", fe_index)
pymol_cyt_c.color(to_rgb(biotite.colors["darkorange"]), fe_index)
pymol_cyt_c.set("sphere_scale", 0.75, fe_index)

pymol_interface.show(PNG_SIZE, use_ray=True)
../../_images/pymol_6_0.png

Have a look at the reference page of PyMOLObject to see all available commands.

Resetting the canvas#

To remove all objects from the canvas, and reset all parameters to defaults, call pymol_interface.reset().

Warning

Do not call the PyMOL reinitialize command directly. reset() does this internally, but additionally sets some PyMOL parameters so Biotite and PyMOL interact properly.

However, we want to keep the styling here, so we only remove the existing PyMOL object from the canvas simply by deleting the variable.

del pymol_cyt_c

Drawing custom shapes#

In addition to visualization of molecules PyMOL is capable of drawing arbitrary geometric shapes using compiled graphics objects (CGOs). A CGO (for example a sphere) is represented by a list of floating point values. biotite.interface.pymol supports a range of CGOs, that can be created conveniently using dedicated functions, such as get_cylinder_cgo(). Calling this function does not draw anything, yet. Instead, one or multiple combined CGOs can be drawn using draw_cgo() creating a single PyMOLObject object. For example, to draw two spheres connected by a line (a cylinder) and a color gradient from red to blue, you can call

PNG_SIZE = (1500, 750)
RED = to_rgb("#db3a35")
BLUE = to_rgb("#1772f0")

gradient_bond = pymol_interface.draw_cgo(
    [
        pymol_interface.get_sphere_cgo(pos=(0, 0, 0), radius=1.0, color=RED),
        pymol_interface.get_cylinder_cgo(
            start=(0, 0, 0), end=(5, 0, 0), radius=0.5, start_color=RED, end_color=BLUE
        ),
        pymol_interface.get_sphere_cgo(pos=(5, 0, 0), radius=1.0, color=BLUE),
    ],
)
# Zomm a little bit out
gradient_bond.zoom(buffer=2)
pymol_interface.show(PNG_SIZE, use_ray=True)
../../_images/pymol_8_0.png

For convenience, some predefined shapes can be drawn, that rely on a combination of CGOs.

box = pymol_interface.draw_box(
    np.diag([10, 10, 10]),
    origin=[-2.5, -5.0, -5.0],
    width=5,
    # white
    color=(1, 1, 1),
)
box.zoom(buffer=2)
pymol_interface.show(PNG_SIZE, use_ray=True)
../../_images/pymol_9_0.png