Selectors#

In ProteoTwirl, a Selector controls which atoms are included when calculating a property or when visualizing sampled orientations. Selectors can also be chained together to create more complex selection logic.

Selectors are especially useful for angle-dependent properties. For example, they can be used to select only exposed atoms, atoms within a field of view, or atoms within a defined distance from the rotation center.

Available Selectors#

There are four different types of Selector implemented in ProteoTwirl, the ClippingPlaneSelector, ClippingRadiusSelector, ExposedAtomSelector and FieldOfViewSelector.

The examples below use the histamine H4 receptor structure PDB: 8JXW, loaded as described in Loading Protein. Only 32 orientations are sampled here. This is intentionally low and is used to keep the documentation examples small.

Load a Protein for Selector Examples#
import ProteoTwirl.ProteinRotation as pt

from ProteoTwirl.Selectors.Selector import (
    ClippingPlaneSelector,
    ClippingRadiusSelector,
    ExposedAtomSelector,
    FieldOfViewSelector,
)

protein_structure = pt.ProteinRotation(
    protein                 = "8JXW_H.pdb",             # Protein structure to load
    selection               = "protein and segid E",    # Select receptor only; here, segment E
    base_rotation           = "principle",              # Align to the principal axes
    center_selection        = True,                     # Center the selection at (0, 0, 0)
    atom_radii              = "vdw",                    # Definition of atomic radii to use

    # Default values avoid having to re-enter these for each added property
    default_rotation_res    = 32,                       # Number of orientations to sample
    default_rotation_range  = None,                     # Optional min/max values for theta and phi
    default_selectors       = None,                     # Optional orientation-dependent atom selectors
    default_sampler         = None,                     # Sampling method; currently Fibonacci sphere only
)

# Show an interactive view of the loaded protein.
protein_structure

Clipping Plane#

ClippingPlaneSelector selects atoms based on their position along the viewing direction for each sampled orientation. The selector takes a distance interval, given as (start, stop), and only atoms within that interval are included.

A value of 0 corresponds to the rotation center. Positive values indicate distance on the other side of the center from the viewing direction while negative values are towards the viewing direction. The units are the units of the protein loaded, usually Ångström.

If either value is None, no limit is applied on that side of the interval. For example, to select atoms on the side of the protein facing the viewer, use (0, None).

The visualization below shows the selected atoms for each sampled orientation. Use the slider at the bottom of the visualization to change which orientation is shown.

In this example, we create a RotationCounter object using protein_structure.iter so that the selector can be visualized. Selector``s can be provided to ``.add_property in the same manner, example of this can be found in Using Count Properties

Select Atoms Toward the Viewer#
clipping_rotation_iter = protein_structure.iter(
    rotation_res    = 32,
    rotation_range  = None,
    selectors       = [ClippingPlaneSelector((0, None))],
)

clipping_rotation_iter.show_3d(height=400)

If both interval values are set, the selector returns atoms within a band across the protein.

Select a Band Across the Protein#
clipping_rotation_iter_2 = protein_structure.iter(
    rotation_res    = 32,
    rotation_range  = None,
    selectors       = [ClippingPlaneSelector((-13, 2))],
)

clipping_rotation_iter_2.show_3d(height=400)

Clipping Radius#

ClippingRadiusSelector is not angle-dependent. It works similarly to ClippingPlaneSelector, but selects atoms based on their radial distance from one or more center points.

The selector takes a distance interval, given as (start, stop). Only atoms whose distance from the center falls within this interval are included. As with clipping-plane selection, either value can be set to None to remove that bound.

By default, the radius is measured from (0, 0, 0).

Select Atoms Within a Radius from the Origin#
clipping_rad_rotation_iter = protein_structure.iter(
    rotation_res    = 32,
    rotation_range  = None,
    selectors       = [ClippingRadiusSelector((0, 15))],
)

clipping_rad_rotation_iter.show_3d(height=400)

A custom center can also be provided. Multiple centers can be supplied, either as atom indices or as points in 3D space. If multiple centers are provided, an atom is selected if its distance to at least one of the centers falls within the interval.

In the example below, atoms 35 and 1550 are used as centers. Note that when using indicies like this, its indicies in ProteinRotation.positions.

Select Atoms Around Two Atom Centers#
clipping_rad2_rotation_iter = protein_structure.iter(
    rotation_res    = 32,
    rotation_range  = None,
    selectors       = [ClippingRadiusSelector((0, 15), center=[35, 1550])],
)

clipping_rad2_rotation_iter.show_3d(height=400)

if instead you want to select based on indices in the protein file, you can set center to a MDAnalysis selection string, for example to select atom with index 12200 to 13000 you can do center = "index 12200:13000". You can also select residue and make more complex selection using this method, such as selecting everything within a radius of 7 from any cystine residue.

Select Atoms Around Two Atom Centers#
clipping_rad2_rotation_iter = protein_structure.iter(
    rotation_res    = 32,
    rotation_range  = None,
    selectors       = [ClippingRadiusSelector((0, 7), center="resname CYS")],
)

clipping_rad2_rotation_iter.show_3d(height=400)

Field of View#

FieldOfViewSelector narrows which atoms are considered, similar to decreasing the field of view of a camera. Technically, this is not a perspective camera view; ProteoTwirl uses an orthographic projection.

The selector works by creating a tube along the viewing direction and including only atoms that fall within that tube.

Select Atoms Within a Field of View#
fov_rotation_iter = protein_structure.iter(
    rotation_res    = 32,
    rotation_range  = None,
    selectors       = [FieldOfViewSelector(10)],
)

fov_rotation_iter.show_3d(height=400)

Exposed Atoms#

ExposedAtomSelector selects atoms that are visible from a given orientation. In other words, it selects atoms that are not hidden behind other atoms from the current viewing direction.

This selector uses the atomic radii defined in the ProteinRotation object to determine whether one atom occludes another.

Select Exposed Atoms#
exposed_rotation_iter = protein_structure.iter(
    rotation_res    = 32,
    rotation_range  = None,
    selectors       = [ExposedAtomSelector()],
)

exposed_rotation_iter.show_3d(height=400)

Combining Selectors#

Multiple selectors can be provided as a list. They are applied in the order they appear in the list, so selector order can affect the final selection.

One useful application is to analyze the surface of a binding pocket. This can be done by combining:

  • ClippingPlaneSelector to select the side of the protein viewed from inside the pocket,

  • FieldOfViewSelector to restrict the view to a local region, and

  • ExposedAtomSelector to keep only atoms visible from that direction.

Together, these selectors allow each orientation to capture a local view of the binding pocket rather than a view of the entire protein surface.

First, use MDAnalysis to find the center of the ligand. In this example, the ligand residue name is VCF.

Find the Binding Pocket Center#
import MDAnalysis as mda

u = mda.Universe("8JXW_H.pdb")
ligand = u.select_atoms("resname VCF")
center = ligand.center_of_geometry()

print(center)
# >>> array([141.21148831, 133.99280492, 108.46190253])

Next, load the protein and set the rotation origin to the ligand center. Here, center_selection is set to False so that the original coordinates are preserved.

Load Protein with Binding Pocket as Rotation Origin#
protein_structure = pt.ProteinRotation(
    protein          = "8JXW_H.pdb",
    selection        = "protein and segid E",
    center_selection = False,
    center           = center,
)

The selectors can then be combined to look from the inside of the binding pocket outward.

Combine Selectors for a Binding Pocket View#
binding_pocket_rotation_iter = protein_structure.iter(
    rotation_res    = 32,
    rotation_range  = None,
    selectors       = [
        ClippingPlaneSelector((None, 0)),
        FieldOfViewSelector(10),
        ExposedAtomSelector(),
    ],
)

binding_pocket_rotation_iter.show_3d(height=400)

Creating Custom Selectors#

Custom selectors can be created by inheriting from the Selector base class and defining the _select_impl method. You also need to specify whether the selector is angle-dependent.

A selector is angle-dependent if it may select different atoms for different protein orientations. ClippingPlaneSelector is angle-dependent, while ClippingRadiusSelector is angle-independent.

If the selection depends on the rotated atom positions or on the viewing direction, set angle_dependent=True. If the selection depends only on static atom information, such as atom names, residue names, or atom indices, set angle_dependent=False.

As an example, we can create a selector that selects backbone atoms.

Define a Backbone Atom Selector#
import numpy as np

from ProteoTwirl.Selectors.Selector import Selector

class BackboneSelector(Selector):

    def __init__(self):
        super().__init__(
            angle_dependent=False,
        )

    def _select_impl(
        self,
        current_points,
        current_selection,
        current_radii,
        rotation,
        protein_rotation,
    ):
        backbone_atom_names = {"N", "CA", "C", "O"}

        return np.array(
            [
                index
                for index, atom in enumerate(current_selection.atoms)
                if atom.name in backbone_atom_names
            ],
            dtype=np.int64,
        )

The _select_impl method receives the following values:

  • current_points: positions of the currently selected atoms;

  • current_radii: radii of the currently selected atoms;

  • current_selection: the current MDAnalysis AtomGroup;

  • rotation: the current rotation;

  • protein_rotation: the ProteinRotation object being analyzed.

The method should return atom indices relative to current_selection. If the selector is the first selector in the chain, current_selection contains all atoms in the loaded ProteinRotation selection. If other selectors have already been applied, current_selection contains only the atoms that passed the previous selectors.

The new selector can then be used like the built-in selectors.

Visualize the Custom Backbone Selector#
custom_iter = protein_structure.iter(
    rotation_res    = 32,
    rotation_range  = None,
    selectors       = [
        BackboneSelector(),
    ],
)

custom_iter.show_3d(height=400)