import molecularnodes as mn
import MDAnalysis as mda
from MDAnalysis.tests.datafiles import DCD, PSF, TPR, XTC
# create a canvas object
= mn.Canvas() canvas
Annotations
Annotations API allows adding annotations to molecular entities. Molecular Nodes comes with a few bundled annotations for trajectories, but new ones can be dynamically added as well.
Setup Molecular Nodes
Add a Trajectory Entity
= mda.Universe(PSF, DCD)
u = mn.Trajectory(u).add_style("cartoon") t
Adding Annotations
Annotations can be added using the add_<annotation_type>
method of the annotations manager of an entity. Each entity can have different annotation types supported.
The bundled annotation types for Trajectories are:
add_atom_info()
- For some basic atom infoadd_com()
- For center-of-mass of a selectionadd_com_distance()
- For distance between two center-of-massesadd_canonical_dihedrals()
- For canonical dihedrals of a residueadd_universe_info()
- For universe infoadd_label_2d()
- For adding a generic 2d label in viewport / renderadd_label_3d()
- For adding a generic 3d label
All selections used in the annotation APIs can be either MDAnalysis selection phrases (strings) or instances of AtomGroup
s.
Here is how to access the annotations manager of an entity:
# annotations manager
t.annotations
<molecularnodes.entities.trajectory.annotations.TrajectoryAnnotationManager at 0x7d79c4b52950>
Available annotation types can be seen as follows:
# available annotation types
for a in t.annotations.__dir__() if a.startswith("add_")] [a
['add_atom_info',
'add_com',
'add_com_distance',
'add_canonical_dihedrals',
'add_universe_info',
'add_label_2d',
'add_label_3d']
Each annotation type can have different input parameters. A common parameter name
can be passed to name the annotation for easier name based lookups later. All the parameters to the add_<annotation_type>
method have to be keyword params.
Annotations can be added using the add_<annotation_type>
method, which returns an instance that can be used to further customize the annotation. Annotation specific inputs as well as common annotation parameters can be customized.
The function signature for adding an annotation type can be seen as follows:
t.annotations.add_atom_info.func.__signature__
<Signature (self, annotation_class, /, *, selection: str | MDAnalysis.core.groups.AtomGroup = 'name CA', show_resid: bool = False, show_segid: bool = False, name: str = None) -> molecularnodes.annotations.interface.AnnotationInterface>
Annotation Examples
atom_info
Display the atom info of a selection.
The input parameters for this annotation are:
selection
- An MDAnalysis selection phrase orAtomGroup
show_resid
- Whether to show the residshow_segid
- Whether to show the segid
# add a style to display all the alpha carbons as spheres
t.add_style(="name CA", style="ball_and_stick", color=(0.162, 0.624, 0.196, 1.0)
selection
)# add the atom_info annotation
= t.annotations.add_atom_info(
a1 ="resid 73:78 and name CA",
selection=True,
show_resid=True,
show_segid="r1 atom info"
name
)# set the font size to 12
= 12 a1.text_size
# frame the view and render
"resid 73:78"), viewpoint="front")
canvas.frame_view(t.get_view( canvas.snapshot()
# hide the annotation and remove the added style
= False
a1.visible 1].remove() t.styles[
com
Display the center-of-mass of a selection.
The input parameters for this annotation are:
selection
- An MDAnalysis selection phrase orAtomGroup
text
- Text to be displayed at the center-of-mass of selection
# show com of the whole protein
= t.annotations.add_com(selection="protein", text="Protein|COM", name="Protein COM")
a2 # show com of residues 150 through 170
= t.annotations.add_com(selection="resid 150:170", text="resid 150:170|COM") a3
# frame the view and render
="front")
canvas.frame_view(t.get_view(), viewpoint canvas.snapshot()
# hide the added annotations
= False
a2.visible = False a3.visible
com_distance
Display the distance between center-of-masses of two selections.
The input parameters for this annotation are:
selection1
- An MDAnalysis selection phrase orAtomGroup
of first selectionselection2
- An MDAnalysis selection phrase orAtomGroup
of second selectiontext1
- Text to display at com of first selectiontext2
- Text to display at com of second selection
# add com_distance between resid 1 and 129
= t.annotations.add_com_distance(
a4 ="resid 1",
selection1=u.select_atoms("resid 129"),
selection2="resid 1|COM",
text1="resid 129|COM",
text2="r1-129 distance",
name )
# frame the view and render
"resid 1 129"), viewpoint="bottom")
canvas.frame_view(t.get_view( canvas.snapshot()
# hide the added annotation
= False a4.visible
canonical_dihedrals
Display the canonical dihedrals of a residue
The input parameters for this annotation are:
resid
- The residue idshow_atom_names
- Whether to show the atom names in the residueshow_direction
- Whether to show the direction arcs of the dihedral angles
# show canonical dihedrals of residue 200
= t.annotations.add_canonical_dihedrals(resid=200) a5
# frame the view and render
"resid 200"), viewpoint="back")
canvas.frame_view(t.get_view( canvas.snapshot()
# hide the added annotation
= False a5.visible
universe_info
Display the universe info of the trajectory.
The input parameters for this annotation are:
location
- Normalized 2d location (0.0 - 1.0) to show the info wrt viewport / rendershow_frame
- Whether or not to show the frame number of the trajectoryshow_topology
- Whether or not to show the topology filenameshow_trajectory
- Whether or not to show the trajectory filenameshow_atoms
- Whether or not to show the number of atoms in the universe
# add universe_info annotation
= t.annotations.add_universe_info() a6
# frame the view and render
="top")
canvas.frame_view(t.get_view(), viewpoint=50) canvas.snapshot(frame
# hide the added annotation
= False a6.visible
label_2d
Display a generic 2d label in the viewport / render.
The input parameters for this annotation are:
text
- Text to displaylocation
- Normalized location (0.0 - 1.0) to show the text wrt viewport / render
# show a 2d label at the top left
= t.annotations.add_label_2d(text="Any|2D Label", location=(0.1, 0.8)) a7
# frame the view and render
="left")
canvas.frame_view(t.get_view(), viewpoint canvas.snapshot()
# hide the added annotation
= False a7.visible
label_3d
Display a generic 3d label on the universe.
The input parameters for this annotation are:
text
- Text to displaylocation
- 3d coordinates in universe to display text
# show the alpha carbon of resid 1 as a sphere
t.add_style(="resid 1 and name CA",
selection="ball_and_stick",
style=(0.162, 0.624, 0.196, 1.0),
color
)# select the alpha carbon of resid 1
= t.universe.select_atoms("resid 1 and name CA")
r1 = r1.atoms[0]
atom # add a 3d label to display text at this atom's location
= t.annotations.add_label_3d(text=f"CA|resid 1|{atom.segid}", location=atom.position)
a8 # add a pointer to point at the location
= 2
a8.pointer_length = 2 a8.line_width
# frame the view and render
"resid 1"), viewpoint="front")
canvas.frame_view(t.get_view( canvas.snapshot()
# hide the added annotation
= False
a8.visible # remove the added style
1].remove() t.styles[
Accessing Annotations
Annotations added to an entity can be accessed in several different ways.
Name based access
# get the resid 1 atom info annotation by name - subscriptable
= t.annotations["r1 atom info"]
a print(a)
# get the resid 1 atom info annotation by name - get method
= t.annotations.get("r1 atom info")
a print(a)
<molecularnodes.annotations.manager.AtomInfo_interface object at 0x7d79c4b6d150>
<molecularnodes.annotations.manager.AtomInfo_interface object at 0x7d79c4b6d150>
Index based access
print("# annotations = ", len(t.annotations))
# get the protein COM annotation by index
= t.annotations[1]
a a
# annotations = 8
<molecularnodes.annotations.manager.COM_interface at 0x7d79c5416dd0>
Iterable access
# access all annotations by iteration
for a in t.annotations:
print(a.name)
r1 atom info
Protein COM
Annotation
r1-129 distance
Annotation.001
Annotation.002
Annotation.003
Annotation.004
Controlling Visiblity
All annotations of an entity can be collectively hidden or displayed using the visible
attribute of the annotations manager.
# get current annotations visibility
t.annotations.visible
True
# hide all annotations
= False
t.annotations.visible t.annotations.visible
False
# show all annotations
= True
t.annotations.visible t.annotations.visible
True
Common Annotation Params
All annotations have common params that control the display properties. These params are in addition to the annotation specific inputs.
The common annotation params are:
name
- Name of the annotationvisible
- Whether or not the annotation is visibletext_font
- Filename of the custom font to use for texttext_color
- Text color - rgba tuple like (1.0, 0.0, 1.0, 1.0)text_size
- Size of the text displayedtext_alignment
- Alignment of the text (center
,left
,right
)text_rotation
- Angle by which to rotate text when left alignedtext_vspacing
- Vertical spacing between lines in multi line texttext_depth
- Whether to enable showing text size based on depth (default: True)text_falloff
- A normalized value (0.0 - 1.0) of how the text size falls off with distance from the viewport / cameraoffset_x
- Text offset along the x direction (in pixels)offset_y
- Text offset along the y direction (in pixels)arrow_size
- Size of the arrow displayed (for lines with arrow ends)pointer_length
- Length of a pointer line to draw (defaults to 0, which is no pointer)
# common annotation params for a1
getattr(a1, p)) for p in a1.__dir__() if not p.startswith("_")] [(p,
[('selection', 'resid 73:78 and name CA'),
('show_resid', True),
('show_segid', True),
('name', 'r1 atom info'),
('visible', False),
('text_font', ''),
('text_color',
bpy.data.objects['NewUniverseObject'].mn_annotations[0].text_color),
('text_size', 12),
('text_align', 'center'),
('text_rotation', 0.0),
('text_vspacing', 1.350000023841858),
('text_depth', True),
('text_falloff', 1.0),
('offset_x', 0),
('offset_y', 0),
('line_color',
bpy.data.objects['NewUniverseObject'].mn_annotations[0].line_color),
('line_width', 1.0),
('arrow_size', 16),
('pointer_length', 0)]
Removing Annotations
Individual annotaitons can be removed using the annotation name or annotation instance.
Remove by name
# remove by an annotation name
"r1 atom info") t.annotations.remove(
Remove by instance
# remove by an annotation instance
t.annotations.remove(a4)
Remove all annoations
# remove all annotations
t.annotations.clear()
Custom Annotations
In addition to the above bundled annotations for Trajectories, custom annotation types can be created by extending the TrajectoryAnnotation
class. These custom annotation types will be automatically registered and can be added using the same add_<annotation_type>
method of a trajectory instance. The GUI to add and configure the annotation will also be automatically available.
Create a custom annotation
A custom trajectory annotation class has to extend TrajectoryAnnotation
and implement the draw
method. Optional defaults
method can be used to set defaults for the annotation and a validate
method that can validate inputs when they change can be implemented. The custom class will have access to the trajectory entity via self.trajectory
, universe via self.trajectory.universe
and the annotation params via self.interface
. Please see the drawing utilities section for all the methods available to draw onto the viewport / renders from the draw
code.
# Add a CutomAnnotation class
# This class will auto-register with the Trajectory entities
class CustomAnnotation(mn.entities.trajectory.TrajectoryAnnotation):
= "custom_annotation"
annotation_type
str # required param
selection: bool = False # optional bool param
bool_param:
# optional defaults method
def defaults(self) -> None:
# any default settings for this annotation go here
= self.interface
params # set text size to 24
= 20
params.text_size
# optional validate method
def validate(self) -> bool:
# validate any input params that change (either through API or GUI)
# return True if validation succeeds else False or raise an Exception
= self.interface
params = self.trajectory.universe
universe # validate and save the selection atom group
if isinstance(params.selection, str):
# check if selection phrase is valid
# mda throws exception if invalid
self.atom_group = universe.select_atoms(params.selection)
elif isinstance(params.selection, AtomGroup):
self.atom_group = params.selection
else:
raise ValueError(f"Need str or AtomGroup. Got {type(params.selection)}")
return True
# required draw method
def draw(self) -> None:
# the draw code for this annotation
# self.trajectory points to the trajectory instance
# self.interface points to the interface that provides
# the annotation inputs and common annotation parameters
# show the atom names of the selection
for atom in self.atom_group:
self.draw_text_3d(atom.position, atom.name)
Add the custom annotation
="resid 75", style="ball_and_stick")
t.add_style(selection# add the custom annotation and pass the required selection parameter
= t.annotations.add_custom_annotation(selection="resid 75")
ca ca
<molecularnodes.annotations.manager.CustomAnnotation_interface at 0x7d79c4b65710>
# frame the view and render
"resid 75"), viewpoint="front")
canvas.frame_view(t.get_view( canvas.snapshot()
# hide the added annotation
= False ca.visible
Unregister a custom annotation
Custom annotations get automatically registered with the TrajectoryAnnotationManager
. They can be manually unregistered and re-registered using the unregister
and register
methods if required.
# unregister the annotation
= mn.entities.trajectory.TrajectoryAnnotationManager
manager manager.unregister(CustomAnnotation)
Drawing utilities
Custom annotations can use the drawing utilities available from the base annotation class to display text or drawings in the viewport / renders.
The following utility methods are currently available:
distance
- Distance between two vectorsdraw_text_2d
- Draw text at a given 2D position (in pixels) of Viewportdraw_text_2d_norm
- Draw text at a given 2D position (normalized co-ordinates) of Viewport.draw_text_3d
- Draw text at a given 3D positiondraw_line_2d
- Draw a line between two points in 2D viewport spacedraw_line_3d
- Draw a line between two points in 3D spacedraw_circle_3d
- Draw a circle around a 3D point in the plane perpendicular to the given normal
These methods can be used within the draw
method of a custom annotation. Please see the API reference for mn.annotations.base
for details about these methods.