"""
A Ramachandran Plot widget, along with some tools for manipulating
structures/points.
Usage:
Rama(parent)
Schrodinger L.L.C.
"""
# Copyright Schrodinger, LLC. All rights reserved.
import os
import sys
from past.utils import old_div
import numpy
from schrodinger import structure
from schrodinger.infra import mm
from schrodinger.Qt import QtCore
from schrodinger.Qt import QtWidgets
from schrodinger.structutils import analyze
from schrodinger.ui.qt import smatplotlib
from schrodinger.ui.qt import swidgets
try:
from schrodinger import maestro
except:
in_maestro = False
else:
in_maestro = True
subdir = os.path.join(os.path.dirname(__file__), 'rama_dir')
OK = 1
ERROR = 0
#####################################################
[docs]class DihedralSpinBox(QtWidgets.QDoubleSpinBox):
"""
A DoulbeSpinBox with min/max = -180/180, step=0.1, and takes a layout
argument that it places itself in, and a command to call when its value
changes.
"""
[docs] def __init__(self, layout, command, dtype):
QtWidgets.QDoubleSpinBox.__init__(self)
self.setMaximum(180.)
self.setMinimum(-180.)
self.setSingleStep(1.0)
self.setDecimals(1)
mylayout = swidgets.SHBoxLayout()
mylabel = swidgets.SLabel(dtype, layout=mylayout)
mylayout.addWidget(self)
layout.addLayout(mylayout)
self.valueChanged.connect(command)
[docs]class Rama:
"""
A class showing a matplotlib plot of the Phi and Psi angles in a helix
"""
DEFAULT_CURSOR = "crosshair"
DEFAULT_SYMBOL_SIZE = 1
DEF_COLOR = "black"
GLY_COLOR = "black"
PRO_COLOR = "black"
FAVORABLE_COLOR = "#f20000"
ALLOWED_COLOR = "#ffff00"
ZOOMSTEP = 10.0
SYMBOL_SCALING_FACTOR = 2
ESC_KEY = 27
[docs] class backbone_dihedral:
[docs] def __init__(self):
CA = 0
N1 = 0
C1 = 0
N2 = 0
C2 = 0
phi = 0.0
psi = 0.0
original_phi = 0.0
original_psi = 0.0
saved_phi = 0.0
saved_psi = 0.0
chain = ""
resname = ""
resnum = 0
inscode = ""
entry = ""
fill_colour = ""
outline_colour = ""
[docs] def __init__(self,
widget,
layout,
size=450,
ticks=30,
show_status=True,
show_counters=False,
zoom_on_pick=True,
dpi=100,
multiselect=False):
"""
Create a Rama object
:type widget: QWidget
:param widget: the PyQt widget that 'owns' this plot
:type layout: QLayout
:param layout: the layout to place the plot into
:type size: int
:param size: size of square plot in pixels, (default=450).
:type dpi: int
:param dpi: dots per inch resolution of plot (default=100).
:type ticks: int
:param size: unused, kept for backward compatibility
:type show_status: bool
:param show_status: If True, cursor location feedback is given in the
matplotlib toolbar
:type show_counters: bool
:param show_counters: If True, show controls for changing the Phi and
Psi values of the selected dihedral. Only one of multiselect and
show_counters can be True.
:type zoom_on_pick: bool
:param zoom_on_pick: If True, zoom the Maestro workspace to the
dihedral picked
:type multiselect: bool
:param multiselect: If True, the user can pick multiple points
with shift/cntl-click. If False (default), only one point can be picked
at a time. Only one of multiselect and show_counters can be True.
"""
self.owner = widget
if multiselect and show_counters:
raise RuntimeError('Only one of multiselect and show_counters can '
'be True')
self.multiselect = multiselect
if multiselect:
self.multiselect_keys = set(['shift', 'control'])
else:
self.multiselect_keys = []
self.picking_callback = None
self.preadjust_callback = None
self.postadjust_callback = None
self.last_found_index = -1
self.show_status = show_status
self.zoom_on_pick = zoom_on_pick
self.dihedral_list = []
# self.current_pick contains the index of the selected dihedral that was
# most recently picked. If no dihedrals are currently selected, it is
# None. For historical reasons, this is tracked separately from the
# list of all selected points.
self.current_pick = None
# self.selected_points is the set of all currently selected points.
self.selected_points = set()
self.weights = []
self.styles = []
self.plot_lines = []
# Some incarnations of the Rama plot do not plot all dihedrals, so this
# helps to translate between them
self.dihedral_to_plotline = {}
self.set_symbol_size(self.DEFAULT_SYMBOL_SIZE)
self.size = size
self.ticks = ticks
self.viewport = [0.0, 0.0, 0.0, 0.0] # [ x0, y0, x1, y1 ]
self.zooming = False
self.zoombox = [0.0, 0.0, 0.0, 0.0] # [ x0, y0, x1, y1 ]
self.scrolling = False
self.scrollpoint = [0.0, 0.0]
self.transform_direction = 'C'
side = old_div(float(size), dpi)
self.graph = RamaFigure(width=side,
height=side,
dpi=dpi,
layout=layout,
coordinates=False)
self.nav_toolbar = self.graph.toolbar
self.label = QtWidgets.QLabel(" \n ")
if self.show_status:
self.nav_toolbar.addWidget(self.label)
self.nav_toolbar.update()
self.graph.fig.clf()
self.graph.sub = self.graph.fig.add_subplot(111)
self.first_update = True
# Labelling
self.graph.mpl_connect("motion_notify_event", self.drag)
# Picking
self.graph.mpl_connect("button_release_event", self.pick_by_point)
self.show_counters = show_counters
if show_counters:
counter_layout = swidgets.SHBoxLayout()
counter_layout.addStretch()
spin_layout = swidgets.SVBoxLayout()
self.phispin = DihedralSpinBox(spin_layout, self.adjust_phi, 'Phi')
self.psispin = DihedralSpinBox(spin_layout, self.adjust_psi, 'Psi')
counter_layout.addLayout(spin_layout)
rb_frame = QtWidgets.QFrame()
rb_frame.setFrameShape(rb_frame.StyledPanel)
rb_frame.setFrameShadow(rb_frame.Sunken)
rb_layout = swidgets.SVBoxLayout(rb_frame)
self.rb_group = swidgets.SRadioButtonGroup(
labels=['Transform N-terminus', 'Transform C-terminus'],
layout=rb_layout)
counter_layout.addWidget(rb_frame)
counter_layout.addStretch()
layout.addLayout(counter_layout)
[docs] def draw_regions(self, filename='rama500-general.data'):
# Regions
try:
with open(os.path.join(subdir, filename), 'r') as ramafile:
self._draw_regions(ramafile)
except IOError:
QtWidgets.QMessageBox.warning(
self.owner, "File Invalid",
"Region data file %s not found." % (filename))
return
def _draw_regions(self, ramafile):
datax = []
datay = []
dataz = []
for line in ramafile:
if not line.strip() or line.startswith('#'):
continue
parts = line.split()
datax.append(float(parts[0]))
datay.append(float(parts[1]))
dataz.append(float(parts[2]))
datax = numpy.array(datax).reshape((180, 180))
datay = numpy.array(datay).reshape((180, 180))
dataz = numpy.array(dataz).reshape((180, 180))
self.graph.sub.contour(datax,
datay,
dataz,
levels=[0.0005, 0.02],
colors='black')
self.graph.sub.contourf(datax,
datay,
dataz,
levels=[0.0005, 0.02, 1],
colors=(self.ALLOWED_COLOR,
self.FAVORABLE_COLOR))
xaxis = self.graph.sub.get_xaxis()
xaxis.set_ticks_position('bottom')
yaxis = self.graph.sub.get_yaxis()
yaxis.set_ticks_position('left')
self.graph.sub.set_xlim(-180, 180)
self.graph.sub.set_xticks(list(range(-180, 190, 30)))
self.graph.sub.set_xlabel("Phi (degrees)", size="xx-small")
labels = [str(r) for r in range(-180, 190, 30)]
self.graph.sub.set_xticklabels(labels, size="xx-small")
self.graph.sub.set_ylim(-180, 180)
self.graph.sub.set_yticks(list(range(-180, 190, 30)))
self.graph.sub.set_ylabel("Psi (degrees)", size="xx-small")
self.graph.sub.set_yticklabels(labels, size="xx-small")
# Cross hairs
self.graph.sub.plot([0.0, 0.0], [180.0, -180.0], color="black")
self.graph.sub.plot([-180.0, 180.0], [0.0, 0.0], color="black")
return
[docs] def find_connected_atom(self, ct, iatom, atname):
"""
Return the atom index with PDB atom type atname attached to
iatom in ct, or -1 if no connection is found.
"""
num_bonds = mm.mmct_atom_get_bond_total(ct, iatom)
for ibond in range(1, num_bonds + 1):
conn_atom = mm.mmct_atom_get_bond_atom(ct, iatom, ibond)
if (mm.mmct_atom_get_pdbname(ct, conn_atom) == atname):
return conn_atom
return -1
[docs] def get_connected_pair(self, ct, iatom, at1name, at2name):
"""
Return a pair of atom indexes, where at1name is attached to
iatom and at2name is attached to at1name.
"""
at1 = self.find_connected_atom(ct, iatom, at1name)
if (at1 > 0):
at2 = self.find_connected_atom(ct, at1, at2name)
else:
at2 = -1
return (at1, at2)
[docs] def get_weight_symbol(self, weight):
symbol = ""
if weight <= 1.0:
# Default
symbol = 'ko'
elif weight <= 2.0:
# gly
symbol = 'k^'
elif weight <= 3.0:
# pro
symbol = 'ks'
elif weight >= 10.0 and weight <= 11.0:
# def-p
symbol = 'wo'
elif weight > 11.0 and weight <= 12.0:
# gly-p
symbol = 'w^'
elif weight > 12.0 and weight <= 13.0:
# pro-p
symbol = 'ws'
else:
symbol = 'r*'
return symbol
[docs] def update_point_faces(self, indexes):
"""
Update the face colors for points on the plot - faster than doing a
complete replot.
:type indexes: list
:param indexes: List of indexes to recolor. Should be the index of the
point in the self.dihedral_list list
"""
for index in indexes:
symbol = self.get_weight_symbol(self.weights[index])
color = symbol[0]
line_index = self.dihedral_to_plotline[index]
self.plot_lines[line_index].set_markerfacecolor(color)
self.graph.draw()
[docs] def update_point_xval(self, index, xval):
"""
Update the x-value of a point on the plot
:type index: int
:param index: The index of the point in the self.dihedral_list list
"""
line_index = self.dihedral_to_plotline[index]
self.plot_lines[line_index].set_xdata([xval])
self.graph.draw()
[docs] def update_point_yval(self, index, yval):
"""
Update the y-value of a point
:type index: int
:param index: The index of the point in the self.dihedral_list list
"""
line_index = self.dihedral_to_plotline[index]
self.plot_lines[line_index].set_ydata([yval])
self.graph.draw()
[docs] def update_display(self):
if not self.first_update:
self.nav_toolbar.push_current()
self.graph.sub.clear()
self.draw_regions()
# Point types
xvalues = [dihedral.phi for dihedral in self.dihedral_list]
yvalues = [dihedral.psi for dihedral in self.dihedral_list]
symbols = [self.get_weight_symbol(w) for w in self.weights]
count = 0
self.plot_lines = []
self.dihedral_to_plotline = {}
for x, y, symbol in zip(xvalues, yvalues, symbols):
line = self.graph.sub.plot([x], [y],
symbol,
markersize=self.symbol_size)[0]
# Some incarnations of the Rama plot don't plot all dihedrals, so we
# have to translate between the two arrays
line.dihedral_index = count
self.dihedral_to_plotline[count] = len(self.plot_lines)
line.set_pickradius(self.symbol_size * self.SYMBOL_SCALING_FACTOR +
0.5)
self.plot_lines.append(line)
count = count + 1
if not self.first_update:
self.nav_toolbar.back()
self.graph.draw()
self.first_update = False
return
[docs] def display_structure(self, ct, CA_list=[]): # noqa: M511
self.current_pick = None
self.selected_points = set()
self.weights = []
self.dihedral_list = []
if ct is None:
self.graph.sub.clear()
self.draw_regions()
self.graph.draw()
return
if len(CA_list) == 0:
CA_list = analyze.evaluate_asl(ct, "atom.ptype \" CA \"")
for CA in CA_list:
new_dihedral = self.backbone_dihedral()
new_dihedral.CA = CA
(new_dihedral.N1,
new_dihedral.C1) = self.get_connected_pair(ct, new_dihedral.CA,
" N ", " C ")
(new_dihedral.C2,
new_dihedral.N2) = self.get_connected_pair(ct, new_dihedral.CA,
" C ", " N ")
if new_dihedral.N1 < 0 or new_dihedral.C1 < 0 or new_dihedral.C2 < 0 or new_dihedral.N2 < 0:
continue
new_dihedral.phi = mm.mmct_atom_get_dihedral_angle_s(
ct, new_dihedral.C1, ct, new_dihedral.N1, ct, new_dihedral.CA,
ct, new_dihedral.C2)
new_dihedral.psi = mm.mmct_atom_get_dihedral_angle_s(
ct, new_dihedral.N1, ct, new_dihedral.CA, ct, new_dihedral.C2,
ct, new_dihedral.N2)
new_dihedral.original_phi = new_dihedral.phi
new_dihedral.original_psi = new_dihedral.psi
new_dihedral.saved_phi = new_dihedral.phi
new_dihedral.saved_psi = new_dihedral.psi
new_dihedral.chain = mm.mmct_atom_get_chain(ct, new_dihedral.CA)
new_dihedral.resname = ct.atom[CA].pdbres
(new_dihedral.resnum,
new_dihedral.inscode) = mm.mmct_atom_get_resnum(
ct, new_dihedral.CA)
self.dihedral_list.append(new_dihedral)
if new_dihedral.resname == "GLY ":
self.weights.append(1.5)
elif new_dihedral.resname == "PRO ":
self.weights.append(2.5)
else:
self.weights.append(0.5)
self.update_display()
return
[docs] def save(self):
"""
Store the current phi & psi values of the current point.
This method only makes sense with multiselect == False
"""
if self.multiselect:
raise RuntimeError('The save method cannot be used with '
'multiselect')
if not self.current_pick:
return
dihedral = self.dihedral_list[self.current_pick]
dihedral.saved_phi = dihedral.phi
dihedral.saved_psi = dihedral.psi
return
[docs] def revert_to_original(self):
"""
Set the current point to its original phi & psi values
This method only makes sense with multiselect == False
"""
if self.multiselect:
raise RuntimeError('The revert_to_original method cannot be used '
'with multiselect')
if not self.current_pick:
return
dihedral = self.dihedral_list[self.current_pick]
dihedral.phi = dihedral.original_phi
dihedral.psi = dihedral.original_psi
self.change_current_pick(self.current_pick)
self.apply_dihedral(self.current_pick)
return
[docs] def revert_to_saved(self):
"""
Set the current point to its previously saved phi & psi values
This method only makes sense with multiselect == False
"""
if self.multiselect:
raise RuntimeError('The revert_to_saved method cannot be used '
'with multiselect')
if not self.current_pick:
return
dihedral = self.dihedral_list[self.current_pick]
dihedral.phi = dihedral.saved_phi
dihedral.psi = dihedral.saved_psi
self.change_current_pick(self.current_pick)
self.apply_dihedral(self.current_pick)
return
[docs] def display_residue(self, which='all'):
"""
Select residues in the workspace corresponding to the given points
:type which: int, None or 'all'
:param which: If None, all residues are deselected. If an integer,
that integer is taken as the index in the dihedral list to select. If
'all', residues for all selected points are selected.
"""
if which is None or (which == 'all' and not self.selected_points):
# No point to display
self.label.setText(" \n ")
if in_maestro:
maestro.command("workspaceselectionclear")
else:
# Create the toolbar label for the desired point, which is
# the most recently selected point if multiple points are picked
if which == 'all':
index = self.current_pick
else:
index = which
residue = self.dihedral_list[index]
text = "Residue: %1s:%3s%4d%1s\nphi=%4.1f psi=%4.1f" \
% (residue.chain, residue.resname,
residue.resnum, residue.inscode,
residue.phi, residue.psi)
self.label.setText(text)
# Select the residue for all selected points
if in_maestro:
if which == 'all':
residue_asls = []
for apoint in self.selected_points:
res = self.dihedral_list[apoint]
asl = (
'(chain.name "%s" and res.num %d and res.i "%s")' %
(res.chain, res.resnum, res.inscode))
residue_asls.append(asl)
if residue_asls:
select_asl = ' OR '.join(residue_asls)
else:
select_asl = ""
else:
select_asl = (
'(chain.name "%s" and res.num %d and res.i "%s")' %
(residue.chain, residue.resnum, residue.inscode))
if select_asl:
maestro.command("workspaceselectionreplace %s " %
select_asl)
[docs] def adjust_phi(self, value):
"""
React to changed value of the Phi spinbox
The method only makes sense with multiselect == False
:type value: float
:param value: new value of the spinbox
"""
if self.multiselect:
raise RuntimeError('The adjust_phi method cannot be used '
'with multiselect')
try:
fval = float(value)
except:
return ERROR
if self.current_pick is not None:
# If the new value is different from the old value, adjust the
# angle. The new and old values may be the same if this call is
# generated by the widget updating when a new residue is picked.
if abs(fval - self.dihedral_list[self.current_pick].phi) > 0.049:
self.dihedral_list[self.current_pick].phi = fval
self.apply_dihedral(self.current_pick)
self.update_point_xval(self.current_pick, fval)
return OK
[docs] def adjust_psi(self, value):
"""
React to changed value of the Psi spinbox
The method only makes sense with multiselect == False
:type value: float
:param value: new value of the spinbox
"""
if self.multiselect:
raise RuntimeError('The adjust_psi method cannot be used '
'with multiselect')
try:
fval = float(value)
except:
return ERROR
if self.current_pick is not None:
# If the new value is different from the old value, adjust the
# angle. The new and old values may be the same if this call is
# generated by the widget updating when a new residue is picked.
if abs(fval - self.dihedral_list[self.current_pick].psi) > 0.049:
self.dihedral_list[self.current_pick].psi = fval
self.apply_dihedral(self.current_pick)
self.update_point_yval(self.current_pick, fval)
return OK
[docs] def apply_dihedral(self, idihedral):
"""
Change the value of a dihedral
:type idihedral: int
:param idihedral: The index of the dihedral to change
"""
if self.preadjust_callback:
self.preadjust_callback()
if in_maestro:
dangle = self.dihedral_list[idihedral]
phi_atoms = [dangle.C2, dangle.CA, dangle.N1, dangle.C1]
psi_atoms = [dangle.N2, dangle.C2, dangle.CA, dangle.N1]
if self.rb_group.isChecked(id=1):
# Chang atom order so the other terminus will be adjusted
phi_atoms.reverse()
psi_atoms.reverse()
phiats = ' '.join([str(x) for x in phi_atoms])
psiats = ' '.join([str(x) for x in psi_atoms])
phi = str(dangle.phi)
psi = str(dangle.psi)
maestro.command('adjustdihedral dihedral=%s %s' % (phi, phiats))
maestro.command('adjustdihedral dihedral=%s %s' % (psi, psiats))
if self.postadjust_callback:
self.postadjust_callback()
return
[docs] def minus(self, event):
self.zoombox[0] = self.viewport[0] - self.ZOOMSTEP
self.zoombox[1] = self.viewport[1] - self.ZOOMSTEP
self.zoombox[2] = self.viewport[2] + self.ZOOMSTEP
self.zoombox[3] = self.viewport[3] + self.ZOOMSTEP
self.set_viewport(min_x=self.zoombox[0],
min_y=self.zoombox[1],
max_x=self.zoombox[2],
max_y=self.zoombox[3])
return
[docs] def plus(self, event):
self.zoombox[0] = self.viewport[0] + self.ZOOMSTEP
self.zoombox[1] = self.viewport[1] + self.ZOOMSTEP
self.zoombox[2] = self.viewport[2] - self.ZOOMSTEP
self.zoombox[3] = self.viewport[3] - self.ZOOMSTEP
self.set_viewport(min_x=self.zoombox[0],
min_y=self.zoombox[1],
max_x=self.zoombox[2],
max_y=self.zoombox[3])
return
[docs] def clear_selection(self, event):
"""
Clear the selected points
:type event: event or None
:param event: The event that generated this call, or None if not being
called as a response to an event. This parameter is unused.
"""
for point in self.selected_points:
self.weights[point] -= 10.0
self.update_point_faces(self.selected_points)
self.selected_points = set()
self.current_pick = None
self.display_residue(which=None)
if self.picking_callback:
self.picking_callback()
return
[docs] def drag(self, event):
"""
As the user moves the mouse over a point, display information for that
point in the toolbar and select the corresponding residue in the
workspace.
:type event: matplotlib MouseEvent
:param event: The event that generated the call to this method.
"""
if event.xdata is None or event.ydata is None:
return
#found_index = self.find_closest_point(event.xdata, event.ydata)
found_index = self.find_closest_point(event)
# Certain conditions (EV 119761) can cause this callback to be called
# multiple times for each actual event, which eventually causes a
# recursion error. Bail out quickly and don't modify the workspace if
# the workspace isn't actually going to change.
if found_index == self.last_found_index or \
(found_index is None and
self.last_found_index in self.selected_points):
# Don't update workspace if there will be no change
return
if found_index is None:
# Cursor not on any point, go back to displaying selected residues
self.last_found_index = self.current_pick
self.display_residue()
else:
# Display the residue the cursor is over
self.last_found_index = found_index
self.display_residue(which=found_index)
[docs] def change_current_pick(self, new_pick, multi=False):
points_to_update = []
if new_pick in self.selected_points and multi:
# Deselecting a currently selected point - remove it from the
# selected points and deselect it
self.weights[new_pick] -= 10.0
self.selected_points.remove(new_pick)
points_to_update.append(new_pick)
try:
# Just grab any selected point as the "most recent" point
self.current_pick = self.selected_points.pop()
self.selected_points.add(self.current_pick)
except KeyError:
self.current_pick = None
new_pick = None
elif not multi:
# Making a single point the only selected point
for apoint in self.selected_points:
self.weights[apoint] -= 10.0
points_to_update = list(self.selected_points)
self.selected_points = set()
self.current_pick = new_pick
else:
# Selecting another point
self.current_pick = new_pick
if new_pick is not None:
# Select a new point
self.weights[self.current_pick] += 10.0
self.selected_points.add(self.current_pick)
points_to_update.append(self.current_pick)
self.update_point_faces(points_to_update)
# Update the Phi and Psi counters if they exist
if self.show_counters:
if self.current_pick is not None:
self.phispin.setValue(self.dihedral_list[self.current_pick].phi)
self.psispin.setValue(self.dihedral_list[self.current_pick].psi)
else:
self.phispin.setValue(0.0)
self.psispin.setValue(0.0)
# Select the proper residue(s) in the workspace
if multi and self.current_pick is not None:
self.display_residue(which='all')
else:
self.display_residue(which=self.current_pick)
# Zoom in on the residues
if in_maestro and self.zoom_on_pick:
if self.multiselect and len(self.selected_points) > 1:
# Multiple residues selected
maestro.command('fit atom.selected')
elif self.current_pick is None:
maestro.command("fit")
else:
# Only one residue selected
maestro.command("fit")
maestro.command("spotcenter %d" %
self.dihedral_list[self.current_pick].CA)
maestro.command("zoom factor=70 in")
if self.picking_callback:
self.picking_callback()
[docs] def find_closest_point(self, event):
"""
Find the plot point that is closest to the event
:type event: MouseEvent
:param event: The matplotlib mouse event that generates this call
"""
# Find the closest item
found_index = None
found_distance = None
found_lines = []
for index, line in enumerate(self.plot_lines):
inpoint, points = line.contains(event)
if inpoint:
found_lines.append(line.dihedral_index)
if not found_lines:
# No points close to cursor
return None
elif len(found_lines) == 1:
# Only one point close to cursor
return found_lines[0]
else:
# More than one point found, find the closes point. The indexes of
# self.plot_lines and self.dihedral_lines are the same.
x = event.xdata
y = event.ydata
epsilon = 9 # This is a squared distance
for index in found_lines:
xdelta = abs(x - self.dihedral_list[index].phi)
ydelta = abs(y - self.dihedral_list[index].psi)
# We use a squared distance to save a call to sqrt
distance = xdelta * xdelta + ydelta * ydelta
if distance <= epsilon:
if found_distance is None or distance < found_distance:
found_index = index
found_distance = distance
return found_index
[docs] def pick_by_point(self, event):
if event.button == 1 and event.inaxes and self.nav_toolbar.mode == '':
index = self.find_closest_point(event)
if index is not None:
self.change_current_pick(index,
multi=event.key
in self.multiselect_keys)
return
[docs] def pick_by_atom(self, iatom):
ct = maestro.workspace_get()
chain = ct.atom[iatom].chain
resnum = ct.atom[iatom].resnum
inscode = ct.atom[iatom].inscode
self.clear_selection(None)
for idihedral in range(len(self.dihedral_list)):
dihedral = self.dihedral_list[idihedral]
if dihedral.chain == chain and dihedral.resnum == resnum and dihedral.inscode == inscode:
self.change_current_pick(idihedral)
break
return
[docs] def get_picked(self):
"""
Get the backbone_dihedral object for the selected dihedral that was
most recently selected.
:rtype: `backbone_dihedral` object or None
:return: The `backbone_dihedral` object for the selected dihedral that
was most recently selected, or None if there are no selected dihedrals.
"""
if self.current_pick:
return self.dihedral_list[self.current_pick]
else:
return None
[docs] def set_viewport(self,
min_x=-180.0,
max_x=180.0,
min_y=-180.0,
max_y=180.0):
# Swap coordinates if the box was drawn "backwards"
if min_x > max_x:
min_x, max_x = max_x, min_x
if min_y > max_y:
min_y, max_y = max_y, min_y
# Make sure the view is square and within the plot region
if (max_x - min_x) > (max_y - min_y): # x is the longest side
if (max_x - min_x
) > 360.0: # bigger than the plot, so just set to full size
min_x = -180.0
max_x = 180.0
else:
if min_x < -180.0: # off to the left
max_x += -180.0 - min_x
min_x = -180.0
elif max_x > 180.0: # off to the right
min_x -= max_x - 180.0
max_x = 180.0
addition = 0.5 * ((max_x - min_x) -
(max_y - min_y)) # enlarge y to make it square
min_y -= addition
max_y += addition
if min_y < -180.0: # off the bottom
max_y += -180.0 - min_y
min_y = -180.0
elif max_y > 180.0: # off the top
min_y -= max_y - 180.0
max_y = 180.0
else: # y is the longest side
if (max_y - min_y
) > 360.0: # bigger than the plot, so just set to full size
min_y = -180.0
max_y = 180.0
else:
if min_y < -180.0: # off the bottom
max_y += -180.0 - min_y
min_y = -180.0
elif max_y > 180.0: # off the top
min_y -= max_y - 180.0
max_y = 180.0
addition = 0.5 * ((max_y - min_y) -
(max_x - min_x)) # enlarge x to make it square
min_x -= addition
max_x += addition
if min_x < -180.0: # off to the left
max_x += -180.0 - min_x
min_x = -180.0
elif max_x > 180.0: # off to the right
min_x -= max_x - 180.0
max_x = 180.0
# Don't do anything if the box was too small
if (max_x - min_x) > 5 and (max_y - min_y) > 5:
self.viewport = [min_x, min_y, max_x, max_y]
return
[docs] def write_file(self, filename):
if filename:
try:
self.graph.fig.savefig(filename, format="png")
except:
print('Failure writing file ' + filename)
return
[docs] def set_symbol_size(self, value):
self.symbol_size = self.SYMBOL_SCALING_FACTOR * int(value)
[docs] def adjust_symbol_size(self, value):
self.set_symbol_size(value)
self.update_display()
return
[docs] def options_quit(self):
self.options_top.destroy()
[docs] def clearGraph(self):
""" Gets called when project is closed """
# FIXME: Maybe call when workspace changes?
self.dihedral_list = []
self.update_display()
return
#############################################################
if __name__ == '__main__':
import schrodinger.ui.qt.appframework as appframework
mypanel = appframework.AppFramework(buttons={'Close': {
'command': quit
}},
title='Ramachandran Plot')
#rama = Rama( mypanel, mypanel.interior_layout, show_status=True, size=600,
# show_counters=True, dpi=200)
rama = Rama(mypanel,
mypanel.interior_layout,
show_status=True,
size=600,
show_counters=False,
dpi=200,
multiselect=True)
ct = structure.StructureReader.read(sys.argv[1])
CAs = analyze.evaluate_asl(ct, "atom.ptype \" CA \"")
rama.display_structure(ct, CA_list=CAs)
mypanel.show()
mypanel.exec()