Difference between revisions of "DynoPlot"

From PyMOLWiki
Jump to navigation Jump to search
m
(general overhaul of the script)
Line 45: Line 45:
 
DynoPlot.py
 
DynoPlot.py
 
  <source lang="python">
 
  <source lang="python">
#!/usr/bin/env python
 
 
###############################################
 
###############################################
 
#  File:          DynoPlot.py
 
#  File:          DynoPlot.py
 
#  Author:        Dan Kulp
 
#  Author:        Dan Kulp
 
#  Creation Date: 8/29/05
 
#  Creation Date: 8/29/05
 +
#
 +
#  Modified 2011-09-20 by Thomas Holder
 
#
 
#
 
#  Notes:
 
#  Notes:
Line 60: Line 61:
 
from __future__ import generators
 
from __future__ import generators
  
import os,math
 
 
import Tkinter
 
import Tkinter
from Tkinter import *
 
import Pmw
 
import distutils.spawn # used for find_executable
 
import random
 
 
from pymol import cmd
 
from pymol import cmd
  
try:
+
# workaround: Set to True if nothing gets drawn on canvas, for example on linux with "pymol -x"
    import pymol
+
with_mainloop = False
    REAL_PYMOL = True
 
except ImportError:
 
        print "Nope"
 
 
 
canvas = None
 
rootframe = None
 
init = 0
 
  
 
class SimplePlot(Tkinter.Canvas):
 
class SimplePlot(Tkinter.Canvas):
  
        # Class variables
+
    # Class variables
        mark = 'Oval' # Only 'Oval' for now..
+
    mark = 'Oval' # Only 'Oval' for now..
        mark_size = 5
+
    mark_size = 4
        xlabels = []  # axis labels
 
        ylabels = []
 
        spacingx = 0  # spacing in x direction
 
        spacingy = 0
 
        xmin = 0      # min value from each axis
 
        ymin = 0
 
        lastx = 0      # previous x,y pos of mouse
 
        lasty = 0
 
        down  = 0      # flag for mouse pressed
 
        item = (0,)    # items array used for clickable events
 
        shapes = {}    # store plot data, x,y etc..
 
  
         def axis(self,xmin=40,xmax=300,ymin=10,ymax=290,xint=290,yint=40,xlabels=[],ylabels=[]):
+
    def __init__(self, *args, **kwargs):
 +
         Tkinter.Canvas.__init__(self, *args, **kwargs)
 +
        self.xlabels = []  # axis labels
 +
        self.ylabels = []
 +
        self.spacingx = 0  # spacing in x direction
 +
        self.spacingy = 0
 +
        self.xmin = 0      # min value from each axis
 +
        self.ymin = 0
 +
        self.lastx = 0      # previous x,y pos of mouse
 +
        self.lasty = 0
 +
        self.isdown  = 0    # flag for mouse pressed
 +
        self.item = (0,)    # items array used for clickable events
 +
        self.shapes = {}    # store plot data, x,y etc..
 +
        self.idx2resn = {}  # residue name mapping
  
                # Store variables in self object
+
    def axis(self,xmin=40,xmax=300,ymin=10,ymax=290,xint=290,yint=40,xlabels=[],ylabels=[]):
                self.xlabels = xlabels
 
                self.ylabels = ylabels
 
                self.spacingx = (xmax-xmin) / (len(xlabels) - 1)
 
                self.spacingy = (ymax-ymin) / (len(ylabels) - 1)
 
                self.xmin = xmin
 
                self.ymin = ymin
 
  
                # Create axis lines
+
        # Store variables in self object
                self.create_line((xmin,xint,xmax,xint),fill="black",width=3)
+
        self.xlabels = xlabels
                self.create_line((yint,ymin,yint,ymax),fill="black",width=3)
+
        self.ylabels = ylabels
 +
        self.spacingx = (xmax-xmin) / (len(xlabels) - 1)
 +
        self.spacingy = (ymax-ymin) / (len(ylabels) - 1)
 +
        self.xmin = xmin
 +
        self.ymin = ymin
  
                # Create tick marks and labels
+
        # Create axis lines
                nextspot = xmin
+
        self.create_line((xmin,xint,xmax,xint),fill="black",width=3)
                for label in xlabels:
+
        self.create_line((yint,ymin,yint,ymax),fill="black",width=3)
                    self.create_line((nextspot, xint+5,nextspot, xint-5),fill="black",width=2)
 
                    self.create_text(nextspot, xint-15, text=label)
 
                    if len(xlabels) == 1:
 
                        nextspot = xmax
 
                    else:
 
                        nextspot += (xmax - xmin)/ (len(xlabels) - 1)
 
  
 +
        # Create tick marks and labels
 +
        nextspot = xmin
 +
        for label in xlabels:
 +
            self.create_line((nextspot, xint+5,nextspot, xint-5),fill="black",width=2)
 +
            self.create_text(nextspot, xint-15, text=label)
 +
            if len(xlabels) == 1:
 +
                nextspot = xmax
 +
            else:
 +
                nextspot += (xmax - xmin)/ (len(xlabels) - 1)
  
                nextspot = ymax
 
                for label in ylabels:
 
                    self.create_line((yint+5,nextspot,yint-5,nextspot),fill="black",width=2)
 
                    self.create_text(yint-20,nextspot,text=label)
 
                    if len(ylabels) == 1:
 
                        nextspot = ymin
 
                    else:
 
                        nextspot -= (ymax - ymin)/ (len(ylabels) - 1)
 
  
 +
        nextspot = ymax
 +
        for label in ylabels:
 +
            self.create_line((yint+5,nextspot,yint-5,nextspot),fill="black",width=2)
 +
            self.create_text(yint-20,nextspot,text=label)
 +
            if len(ylabels) == 1:
 +
                nextspot = ymin
 +
            else:
 +
                nextspot -= (ymax - ymin)/ (len(ylabels) - 1)
  
        # Plot a point
 
        def plot(self,xp,yp,meta):
 
  
                # Convert from 'label' space to 'pixel' space
+
    # Plot a point
                x = self.convertToPixel("X",xp)
+
    def plot(self,xp,yp,meta):
                y = self.convertToPixel("Y",yp)
 
  
                if self.mark == "Oval":
+
        # Convert from 'label' space to 'pixel' space
                    oval = self.create_oval(x-self.mark_size,y-self.mark_size,x+self.mark_size,y+self.mark_size,width=1,outline="black",fill="SkyBlue2")
+
        x = self.convertToPixel("X",xp)
 +
        y = self.convertToPixel("Y",yp)
  
                    self.shapes[oval] = [x,y,0,xp,yp,meta]
+
        resn = self.idx2resn.get(meta)
 +
        mark = {'GLY': 'Tri', 'PRO': 'Rect'}.get(resn, self.mark)
  
 +
        if mark == 'Oval':
 +
            create_shape = self.create_oval
 +
            coords = [x-self.mark_size, y-self.mark_size,
 +
                    x+self.mark_size, y+self.mark_size]
 +
        elif mark == 'Tri':
 +
            create_shape = self.create_polygon
 +
            coords = [x, y-self.mark_size,
 +
                    x+self.mark_size, y+self.mark_size,
 +
                    x-self.mark_size, y+self.mark_size]
 +
        else:
 +
            create_shape = self.create_rectangle
 +
            coords = [x-self.mark_size, y-self.mark_size,
 +
                    x+self.mark_size, y+self.mark_size]
  
         # Repaint all points
+
         oval = create_shape(*coords,
        def repaint(self):
+
                 width=1, outline="black", fill="SkyBlue2")
                 for value in self.shapes.values():
+
        self.shapes[oval] = [x,y,0,xp,yp,meta]
                        x = value[0]
 
                        y = value[1]
 
                        self.create_oval(x-self.mark_size,y-self.mark_size,x+self.mark_size,y+self.mark_size,width=1,outline="black",fill="SkyBlue2")
 
  
        # Convert from pixel space to label space
+
    # Convert from pixel space to label space
        def convertToLabel(self,axis, value):
+
    def convertToLabel(self,axis, value):
  
                # Defaultly use X-axis info
+
        # Defaultly use X-axis info
                label0  = self.xlabels[0]
+
        label0  = self.xlabels[0]
                label1  = self.xlabels[1]
+
        label1  = self.xlabels[1]
                spacing = self.spacingx
+
        spacing = self.spacingx
                min    = self.xmin
+
        min    = self.xmin
  
                # Set info for Y-axis use
+
        # Set info for Y-axis use
                if axis == "Y":
+
        if axis == "Y":
                    label0    = self.ylabels[0]
+
            label0    = self.ylabels[0]
                    label1    = self.ylabels[1]
+
            label1    = self.ylabels[1]
                    spacing  = self.spacingy
+
            spacing  = self.spacingy
                    min      = self.ymin
+
            min      = self.ymin
  
                pixel = value - min
+
        pixel = value - min
                label = pixel / spacing
+
        label = pixel / spacing
                label = label0 + label * abs(label1 - label0)
+
        label = label0 + label * abs(label1 - label0)
  
                if axis == "Y":
+
        if axis == "Y":
                        label = - label
+
                label = - label
  
                return label
+
        return label
  
        # Converts value from 'label' space to 'pixel' space
+
    # Converts value from 'label' space to 'pixel' space
        def convertToPixel(self,axis, value):
+
    def convertToPixel(self,axis, value):
  
                # Defaultly use X-axis info
+
        # Defaultly use X-axis info
                label0  = self.xlabels[0]
+
        label0  = self.xlabels[0]
                label1  = self.xlabels[1]
+
        label1  = self.xlabels[1]
                spacing = self.spacingx
+
        spacing = self.spacingx
                min    = self.xmin
+
        min    = self.xmin
  
                # Set info for Y-axis use
+
        # Set info for Y-axis use
                if axis == "Y":
+
        if axis == "Y":
                    label0    = self.ylabels[0]
+
            label0    = self.ylabels[0]
                    label1    = self.ylabels[1]
+
            label1    = self.ylabels[1]
                    spacing  = self.spacingy
+
            spacing  = self.spacingy
                    min      = self.ymin       
+
            min      = self.ymin       
  
 +
        # Get axis increment in 'label' space
 +
        inc = abs(label1 - label0)
  
                # Get axis increment in 'label' space
+
        # 'Label' difference from value and smallest label (label0)
                inc = abs(label1 - label0)
+
        diff = float(value - label0)
  
                # 'Label' difference from value and smallest label (label0)
+
        # Get whole number in 'label' space
                diff = float(value - label0)
+
        whole = int(diff / inc)
  
                # Get whole number in 'label' space
+
        # Get fraction number in 'label' space
                whole = int(diff / inc)
+
        part = float(float(diff/inc) - whole)
  
                # Get fraction number in 'label' space
+
        # Return 'pixel' position value
                part = float(float(diff/inc) - whole)
+
        pixel = whole * spacing + part * spacing
  
                # Return 'pixel' position value
+
        # Reverse number by subtracting total number of pixels - value pixels
                pixel = whole * spacing + part * spacing
+
        if axis == "Y":
 +
          tot_label_diff = float(self.ylabels[-1] - label0)
 +
          tot_label_whole = int(tot_label_diff / inc)
 +
          tot_label_part = float(float(tot_label_diff / inc) - tot_label_whole)
 +
          tot_label_pix  = tot_label_whole * spacing + tot_label_part *spacing
  
#              print "Pixel: %f * %f + %f * %f = %f" % (whole, spacing, part, spacing,pixel)
+
          pixel = tot_label_pix - pixel
  
                # Reverse number by subtracting total number of pixels - value pixels
+
        # Add min edge pixels
                if axis == "Y":
+
        pixel = pixel + min
                  tot_label_diff = float(self.ylabels[-1] - label0)
 
                  tot_label_whole = int(tot_label_diff / inc)
 
                  tot_label_part = float(float(tot_label_diff / inc) - tot_label_whole)
 
                  tot_label_pix  = tot_label_whole * spacing + tot_label_part *spacing
 
  
                  pixel = tot_label_pix - pixel
+
        return pixel
  
                # Add min edge pixels
 
                pixel = pixel + min
 
  
                return pixel
+
    # Print out which data point you just clicked on..
 +
    def pickWhich(self,event):
  
 +
        # Find closest data point             
 +
        x = event.widget.canvasx(event.x)
 +
        y = event.widget.canvasx(event.y)
 +
        spot = event.widget.find_closest(x,y)
  
         # Print out which data point you just clicked on..
+
         # Print the shape's meta information corresponding with the shape that was picked
        def pickWhich(self,event):
+
        if spot[0] in self.shapes:
 +
            cmd.select('sele', '(%s`%d)' % self.shapes[spot[0]][5])
 +
            cmd.iterate('sele', 'print " You clicked /%s/%s/%s/%s`%s/%s (DynoPlot)" %' + \
 +
                    ' (model, segi, chain, resn, resi, name)')
 +
            cmd.center('byres sele', animate=1)
  
            # Find closest data point             
+
    # Mouse Down Event
            x = event.widget.canvasx(event.x)
+
    def down(self,event):
            y = event.widget.canvasx(event.y)
 
            spot = event.widget.find_closest(x,y)
 
  
            # Print the shape's meta information corresponding with the shape that was picked
+
        # Store x,y position
            if spot[0] in self.shapes:
+
        self.lastx = event.x
                print "Residue(Ca): %s\n" % self.shapes[spot[0]][5][2]
+
        self.lasty = event.y
  
 +
        # Find the currently selected item
 +
        x = event.widget.canvasx(event.x)
 +
        y = event.widget.canvasx(event.y)
 +
        self.item = event.widget.find_closest(x,y)
  
         # Mouse Down Event
+
         # Identify that the mouse is down
         def down(self,event):
+
         self.isdown  = 1
  
            # Store x,y position
+
    # Mouse Up Event
            self.lastx = event.x
+
    def up(self,event):
            self.lasty = event.y
 
  
            # Find the currently selected item
+
        # Get label space version of x,y
            x = event.widget.canvasx(event.x)
+
        labelx = self.convertToLabel("X",event.x)
            y = event.widget.canvasx(event.y)
+
        labely = self.convertToLabel("Y",event.y)
            self.item = event.widget.find_closest(x,y)
 
  
             # Identify that the mouse is down
+
        # Convert new position into label space..
             self.down = 1
+
        if self.item[0] in self.shapes:
 +
            self.shapes[self.item[0]][0] = event.x
 +
             self.shapes[self.item[0]][1] = event.y
 +
             self.shapes[self.item[0]][2] = 1
 +
            self.shapes[self.item[0]][3] = labelx
 +
            self.shapes[self.item[0]][4] = labely
  
         # Mouse Up Event
+
         # Reset Flags
         def up(self,event):
+
         self.item = (0,)
 +
        self.isdown = 0
  
            # Get label space version of x,y
+
    # Mouse Drag(Move) Event
            labelx = self.convertToLabel("X",event.x)
+
    def drag(self,event):
            labely = self.convertToLabel("Y",event.y)
 
  
            # Convert new position into label space..
+
        # Check that mouse is down and item clicked is a valid data point
            if self.item[0] in self.shapes:
+
        if self.isdown and self.item[0] in self.shapes:
                self.shapes[self.item[0]][0] = event.x
 
                self.shapes[self.item[0]][1] = event.y
 
                self.shapes[self.item[0]][2] =  1
 
                self.shapes[self.item[0]][3] = labelx
 
                self.shapes[self.item[0]][4] = labely
 
  
            # Reset Flags
+
             self.move(self.item, event.x - self.lastx, event.y - self.lasty)
             self.item = (0,)
 
            self.down = 0
 
  
 +
            self.lastx = event.x
 +
            self.lasty = event.y
  
         # Mouse Drag(Move) Event
+
def set_phipsi(model, index, phi, psi):
         def drag(self,event):
+
    atsele = [
 +
        'first ((%s`%d) extend 2 and name C)' % (model, index), # prev C
 +
        'first ((%s`%d) extend 1 and name N)' % (model, index), # this N
 +
        '(%s`%d)' % (model, index),                            # this CA
 +
         'last ((%s`%d) extend 1 and name C)' % (model, index),  # this C
 +
        'last ((%s`%d) extend 2 and name N)' % (model, index),  # next N
 +
    ]
 +
    try:
 +
        cmd.set_dihedral(atsele[0], atsele[1], atsele[2], atsele[3], phi)
 +
         cmd.set_dihedral(atsele[1], atsele[2], atsele[3], atsele[4], psi)
 +
    except:
 +
        print ' DynoPlot Error: cmd.set_dihedral failed'
  
                # Check that mouse is down and item clicked is a valid data point
+
# New Callback object, so that we can update the structure when phi,psi points are moved.
                if self.down and self.item[0] in self.shapes:
+
class DynoRamaObject:
 
+
    def __init__(self, selection=None, name=None):
                    self.move(self.item, event.x - self.lastx, event.y - self.lasty)
+
        from pymol import _ext_gui as pmgapp
 
+
        if pmgapp is not None:
                    self.lastx = event.x
+
            import Pmw
                    self.lasty = event.y
+
            rootframe = Pmw.MegaToplevel(pmgapp.root)
 
+
            parent = rootframe.interior()
 
+
         else:
def __init__(self):
+
            rootframe = Tkinter.Tk()
 
+
            parent = rootframe
        self.menuBar.addcascademenu('Plugin', 'PlotTools', 'Plot Tools',
 
                                    label='Plot Tools')
 
         self.menuBar.addmenuitem('PlotTools', 'command',
 
                                'Launch Rama Plot',
 
                                label='Rama Plot',
 
                                command = lambda s=self: ramaplot())
 
 
 
 
 
def ramaplot(x=0,y=0,meta=[],clear=0):
 
    global canvas
 
    global rootframe
 
    global init
 
  
    # If no window is open
 
    if init == 0:
 
        rootframe=Tk()
 
 
         rootframe.title(' Dynamic Angle Plotting ')
 
         rootframe.title(' Dynamic Angle Plotting ')
         rootframe.protocol("WM_DELETE_WINDOW", close_callback)
+
         rootframe.protocol("WM_DELETE_WINDOW", self.close_callback)
  
         canvas = SimplePlot(rootframe,width=320,height=320)
+
         canvas = SimplePlot(parent,width=320,height=320)
 
         canvas.bind("<Button-2>",canvas.pickWhich)
 
         canvas.bind("<Button-2>",canvas.pickWhich)
 
         canvas.bind("<Button-3>",canvas.pickWhich)
 
         canvas.bind("<Button-3>",canvas.pickWhich)
        canvas.bind("<ButtonPress-1>",canvas.down)
 
        canvas.bind("<ButtonRelease-1>",canvas.up)
 
        canvas.bind("<Motion>",canvas.drag)
 
 
         canvas.pack(side=Tkinter.LEFT,fill="both",expand=1)
 
         canvas.pack(side=Tkinter.LEFT,fill="both",expand=1)
         canvas.axis(xint=150,xlabels=[-180,-150,-120,-90,-60,-30,0,30,60,90,120,150,180],ylabels=[-180,-150,-120,-90,-60,-30,0,30,60,90,120,150,180])
+
         canvas.axis(xint=150,
 +
                xlabels=[-180,-120,-60,0,60,120,180],
 +
                ylabels=[-180,-150,-120,-90,-60,-30,0,30,60,90,120,150,180])
 
         canvas.update()
 
         canvas.update()
        init = 1
 
    else:
 
      canvas.plot(int(x), int(y),meta)
 
  
def close_callback():
+
        if name is None:
    global init
+
            try:
    global rootframe
+
                name = cmd.get_unused_name('DynoRama')
    init = 0
+
            except AttributeError:
    rootframe.destroy()
+
                name = 'DynoRamaObject'
  
 +
        self.rootframe = rootframe
 +
        self.canvas = canvas
 +
        self.name = name
 +
        self.lock = 0
  
# New Callback object, so that we can update the structure when phi,psi points are moved.
+
        if name != 'none':
class DynoRamaObject:
+
            auto_zoom = cmd.get('auto_zoom')
        global canvas
+
            cmd.set('auto_zoom', 0)
 +
            cmd.load_callback(self, name)
 +
            cmd.set('auto_zoom', auto_zoom)
 +
            canvas.bind("<ButtonPress-1>",canvas.down)
 +
            canvas.bind("<ButtonRelease-1>",canvas.up)
 +
            canvas.bind("<Motion>",canvas.drag)
  
         def start(self,sel):
+
         if selection is not None:
 +
            self.start(selection)
  
            # Get selection model
+
        if with_mainloop and pmgapp is None:
             model = cmd.get_model(sel)
+
             rootframe.mainloop()
            residues = ['dummy']
 
            resnames = ['dummy']
 
            phi = []
 
            psi = []
 
            dummy = []
 
            i = 0
 
  
            # Loop through each atom
+
    def close_callback(self):
            for at in model.atom:
+
        cmd.delete(self.name)
 +
        self.rootframe.destroy()
  
                # Only plot once per residue
+
    def start(self,sel):
                if at.chain+":"+at.resn+":"+at.resi not in residues:
+
        self.lock = 1
                    residues.append(at.chain+":"+at.resn+":"+at.resi)
+
        cmd.iterate('(%s) and name CA' % sel, 'idx2resn[model,index] = resn',
                    resnames.append(at.resn+at.resi)
+
                space={'idx2resn': self.canvas.idx2resn})
                    dummy.append(i)
+
        for model_index, (phi,psi) in cmd.get_phipsi(sel).iteritems():
                    i += 1
+
            print " Plotting Phi,Psi: %8.2f,%8.2f" % (phi, psi)
 +
            self.canvas.plot(phi, psi, model_index)
 +
        self.lock = 0
  
                    # Check for a null chain id (some PDBs contain this)
+
    def __call__(self):
                    unit_select = ""
+
        if self.lock:
                    if at.chain != "":
+
            return
                        unit_select = "chain "+str(at.chain)+" and "
+
        # Loop through each item on plot to see if updated
 +
        for value in self.canvas.shapes.itervalues():
 +
            # Look for update flag...
 +
            if value[2]:
 +
                # Set residue's phi,psi to new values
 +
                model, index = value[5]
 +
                print " Re-setting Phi,Psi: %8.2f,%8.2f" % (value[3],value[4])
 +
                set_phipsi(model, index, value[3], value[4])
 +
                value[2] = 0
  
                    # Define selections for residue i-1, i and i+1
+
def rama(sel='(all)', name=None):
                    residue_def = unit_select+'resi '+str(at.resi)
+
    '''
                    residue_def_prev = unit_select+'resi '+str(int(at.resi)-1)
+
DESCRIPTION
                    residue_def_next = unit_select+'resi '+str(int(at.resi)+1)
 
  
                    try:
+
    Ramachandran Plot
                        # Store phi,psi residue definitions to pass on to plot routine
+
    http://pymolwiki.org/index.php/DynoPlot
                        phi_psi = [
 
                                # Phi angles
 
                                  residue_def_prev+' and name C',
 
                                  residue_def+' and name N',
 
                                  residue_def+' and name CA',
 
                                  residue_def+' and name C',
 
                                # Psi angles
 
                                  residue_def+' and name N',
 
                                  residue_def+' and name CA',
 
                                  residue_def+' and name C',
 
                                  residue_def_next+' and name N']
 
  
                        # Compute phi/psi angle
+
ARGUMENTS
                        phi = cmd.get_dihedral(phi_psi[0],phi_psi[1],phi_psi[2],phi_psi[3])
 
                        psi = cmd.get_dihedral(phi_psi[4],phi_psi[5],phi_psi[6],phi_psi[7])
 
  
                        print "Plotting Phi,Psi: "+str(phi)+","+str(psi)
+
    sel = string: atom selection {default: all}
                        ramaplot(phi,psi,meta=phi_psi)
 
                    except:
 
                        continue
 
  
 +
    name = string: name of callback object which is responsible for setting
 +
    angles when canvas points are dragged, or 'none' to not create a callback
 +
    object {default: DynoRamaObject}
 +
    '''
 +
    DynoRamaObject(sel, name)
  
        def __call__(self):
+
# Extend these commands
 +
cmd.extend('ramachandran', rama)
 +
cmd.auto_arg[0]['ramachandran'] = cmd.auto_arg[0]['zoom']
  
            # Loop through each item on plot to see if updated
+
# Add to plugin menu
            for key,value in canvas.shapes.items():
+
def __init_plugin__(self):
                dihedrals = value[5]
+
    self.menuBar.addcascademenu('Plugin', 'PlotTools', 'Plot Tools', label='Plot Tools')
 +
    self.menuBar.addmenuitem('PlotTools', 'command', 'Launch Rama Plot', label='Rama Plot',
 +
            command = lambda: DynoRamaObject('(enabled)'))
  
                # Look for update flag...
+
# vi:expandtab:smarttab
                if value[2]:
 
 
 
                    # Set residue's phi,psi to new values
 
                    print "Re-setting Phi,Psi: %s,%s" % (value[3],value[4])
 
                    cmd.set_dihedral(dihedrals[0],dihedrals[1],dihedrals[2],dihedrals[3],value[3])
 
                    cmd.set_dihedral(dihedrals[4],dihedrals[5],dihedrals[6],dihedrals[7],value[4])
 
 
 
                    value[2] = 0
 
 
 
 
 
 
 
# The wrapper function, used to create the Ploting window and the PyMol callback object               
 
def rama(sel):
 
        rama = DynoRamaObject()
 
        rama.start(sel)
 
        cmd.load_callback(rama, "DynoRamaObject")
 
        cmd.zoom("all")
 
 
 
 
 
# Extend these commands
 
cmd.extend('rama',rama)
 
cmd.extend('ramaplot',ramaplot)
 
 
</source>
 
</source>
  

Revision as of 13:43, 20 September 2011

DESCRIPTION

This script was setup to do generic plotting, that is given a set of data and axis labels it would create a plot. Initially, I had it setup to draw the plot directly in the PyMol window (allowing for both 2D and 3D style plots), but because I couldn't figure out how to billboard CGO objects (Warren told me at the time that it couldn't be done) I took a different approach. The plot now exists in it's own window and can only do 2D plots. It is however interactive. I only have here a Rama.(phi,psi) plot, but the code can be easily extended to other types of data. For instance, I had this working for an energy vs distance data that I had generated by another script.

This script will create a Phi vs Psi(Ramachandran) plot of the selection given. The plot will display data points which can be dragged around Phi,Psi space with the corresponding residue's Phi,Psi angles changing in the structure (PyMol window).

IMAGES

SETUP

place the DynoPlot.py script into the appropriate startup directory then restart PyMol

LINUX old-style installation

$PYMOL_PATH/modules/pmg_tk/startup/

LINUX distutils installation

/usr/lib/pythonX.X/site-packages/pmg_tk/startup/

Windows

PYMOL_PATH/modules/pmg_tk/startup/ , where PYMOL_PATH on Windows is defaulted to C:/Program Files/DeLano Scientific/PyMol/start/

NOTES / STATUS

  • Tested on Windows, PyMol version 0.97
  • This is an initial version, which needs some work.
  • Left, Right mouse buttons do different things; Right = identify data point, Left = drag data point around
  • Post comments/questions or send them to: dwkulp@mail.med.upenn.edu

USAGE

rama SELECTION

EXAMPLES

  • load pdb file 1ENV (download it or use the PDB loader plugin)
  • select resi 129-136
  • rama sel01
  • rock # the object needs to be moving in order for the angles to be updated.

REFERENCES

SCRIPTS (DynoPlot.py)

DynoPlot.py

###############################################
#  File:          DynoPlot.py
#  Author:        Dan Kulp
#  Creation Date: 8/29/05
#
#  Modified 2011-09-20 by Thomas Holder
#
#  Notes:
#  Draw plots that display interactive data.
#   Phi,Psi plot shown.
###############################################


from __future__ import division
from __future__ import generators

import Tkinter
from pymol import cmd

# workaround: Set to True if nothing gets drawn on canvas, for example on linux with "pymol -x"
with_mainloop = False

class SimplePlot(Tkinter.Canvas):

    # Class variables
    mark = 'Oval' # Only 'Oval' for now..
    mark_size = 4

    def __init__(self, *args, **kwargs):
        Tkinter.Canvas.__init__(self, *args, **kwargs)
        self.xlabels = []   # axis labels
        self.ylabels = []
        self.spacingx = 0   # spacing in x direction
        self.spacingy = 0
        self.xmin = 0       # min value from each axis
        self.ymin = 0
        self.lastx = 0      # previous x,y pos of mouse
        self.lasty = 0
        self.isdown  = 0    # flag for mouse pressed
        self.item = (0,)    # items array used for clickable events
        self.shapes = {}    # store plot data, x,y etc..
        self.idx2resn = {}  # residue name mapping

    def axis(self,xmin=40,xmax=300,ymin=10,ymax=290,xint=290,yint=40,xlabels=[],ylabels=[]):

        # Store variables in self object
        self.xlabels = xlabels
        self.ylabels = ylabels
        self.spacingx = (xmax-xmin) / (len(xlabels) - 1)
        self.spacingy = (ymax-ymin) / (len(ylabels) - 1)
        self.xmin = xmin
        self.ymin = ymin

        # Create axis lines
        self.create_line((xmin,xint,xmax,xint),fill="black",width=3)
        self.create_line((yint,ymin,yint,ymax),fill="black",width=3)

        # Create tick marks and labels
        nextspot = xmin
        for label in xlabels:
            self.create_line((nextspot, xint+5,nextspot, xint-5),fill="black",width=2)
            self.create_text(nextspot, xint-15, text=label)
            if len(xlabels) == 1:
                nextspot = xmax
            else:
                nextspot += (xmax - xmin)/ (len(xlabels) - 1)


        nextspot = ymax
        for label in ylabels:
            self.create_line((yint+5,nextspot,yint-5,nextspot),fill="black",width=2)
            self.create_text(yint-20,nextspot,text=label)
            if len(ylabels) == 1:
                nextspot = ymin
            else:
                nextspot -= (ymax - ymin)/ (len(ylabels) - 1)


    # Plot a point
    def plot(self,xp,yp,meta):

        # Convert from 'label' space to 'pixel' space
        x = self.convertToPixel("X",xp)
        y = self.convertToPixel("Y",yp)

        resn = self.idx2resn.get(meta)
        mark = {'GLY': 'Tri', 'PRO': 'Rect'}.get(resn, self.mark)

        if mark == 'Oval':
            create_shape = self.create_oval
            coords = [x-self.mark_size, y-self.mark_size,
                    x+self.mark_size, y+self.mark_size]
        elif mark == 'Tri':
            create_shape = self.create_polygon
            coords = [x, y-self.mark_size,
                    x+self.mark_size, y+self.mark_size,
                    x-self.mark_size, y+self.mark_size]
        else:
            create_shape = self.create_rectangle
            coords = [x-self.mark_size, y-self.mark_size,
                    x+self.mark_size, y+self.mark_size]

        oval = create_shape(*coords,
                width=1, outline="black", fill="SkyBlue2")
        self.shapes[oval] = [x,y,0,xp,yp,meta]

    # Convert from pixel space to label space
    def convertToLabel(self,axis, value):

        # Defaultly use X-axis info
        label0  = self.xlabels[0]
        label1  = self.xlabels[1]
        spacing = self.spacingx
        min     = self.xmin

        # Set info for Y-axis use
        if axis == "Y":
            label0    = self.ylabels[0]
            label1    = self.ylabels[1]
            spacing   = self.spacingy
            min       = self.ymin

        pixel = value - min
        label = pixel / spacing
        label = label0 + label * abs(label1 - label0)

        if axis == "Y":
                label = - label

        return label

    # Converts value from 'label' space to 'pixel' space
    def convertToPixel(self,axis, value):

        # Defaultly use X-axis info
        label0  = self.xlabels[0]
        label1  = self.xlabels[1]
        spacing = self.spacingx
        min     = self.xmin

        # Set info for Y-axis use
        if axis == "Y":
            label0    = self.ylabels[0]
            label1    = self.ylabels[1]
            spacing   = self.spacingy
            min       = self.ymin       

        # Get axis increment in 'label' space
        inc = abs(label1 - label0)

        # 'Label' difference from value and smallest label (label0)
        diff = float(value - label0)

        # Get whole number in 'label' space
        whole = int(diff / inc)

        # Get fraction number in 'label' space
        part = float(float(diff/inc) - whole)

        # Return 'pixel' position value
        pixel = whole * spacing + part * spacing

        # Reverse number by subtracting total number of pixels - value pixels
        if axis == "Y":
           tot_label_diff = float(self.ylabels[-1] - label0)
           tot_label_whole = int(tot_label_diff / inc)
           tot_label_part = float(float(tot_label_diff / inc) - tot_label_whole)
           tot_label_pix  = tot_label_whole * spacing + tot_label_part *spacing

           pixel = tot_label_pix - pixel

        # Add min edge pixels
        pixel = pixel + min

        return pixel


    # Print out which data point you just clicked on..
    def pickWhich(self,event):

        # Find closest data point               
        x = event.widget.canvasx(event.x)
        y = event.widget.canvasx(event.y)
        spot = event.widget.find_closest(x,y)

        # Print the shape's meta information corresponding with the shape that was picked
        if spot[0] in self.shapes:
            cmd.select('sele', '(%s`%d)' % self.shapes[spot[0]][5])
            cmd.iterate('sele', 'print " You clicked /%s/%s/%s/%s`%s/%s (DynoPlot)" %' + \
                    ' (model, segi, chain, resn, resi, name)')
            cmd.center('byres sele', animate=1)

    # Mouse Down Event
    def down(self,event):

        # Store x,y position
        self.lastx = event.x
        self.lasty = event.y

        # Find the currently selected item
        x = event.widget.canvasx(event.x)
        y = event.widget.canvasx(event.y)
        self.item = event.widget.find_closest(x,y)

        # Identify that the mouse is down
        self.isdown  = 1

    # Mouse Up Event
    def up(self,event):

        # Get label space version of x,y
        labelx = self.convertToLabel("X",event.x)
        labely = self.convertToLabel("Y",event.y)

        # Convert new position into label space..
        if self.item[0] in self.shapes:
            self.shapes[self.item[0]][0] = event.x
            self.shapes[self.item[0]][1] = event.y
            self.shapes[self.item[0]][2] =  1
            self.shapes[self.item[0]][3] = labelx
            self.shapes[self.item[0]][4] = labely

        # Reset Flags
        self.item = (0,)
        self.isdown = 0

    # Mouse Drag(Move) Event
    def drag(self,event):

        # Check that mouse is down and item clicked is a valid data point
        if self.isdown and self.item[0] in self.shapes:

            self.move(self.item, event.x - self.lastx, event.y - self.lasty)

            self.lastx = event.x
            self.lasty = event.y

def set_phipsi(model, index, phi, psi):
    atsele = [
        'first ((%s`%d) extend 2 and name C)' % (model, index), # prev C
        'first ((%s`%d) extend 1 and name N)' % (model, index), # this N
        '(%s`%d)' % (model, index),                             # this CA
        'last ((%s`%d) extend 1 and name C)' % (model, index),  # this C
        'last ((%s`%d) extend 2 and name N)' % (model, index),  # next N
    ]
    try:
        cmd.set_dihedral(atsele[0], atsele[1], atsele[2], atsele[3], phi)
        cmd.set_dihedral(atsele[1], atsele[2], atsele[3], atsele[4], psi)
    except:
        print ' DynoPlot Error: cmd.set_dihedral failed'

# New Callback object, so that we can update the structure when phi,psi points are moved.
class DynoRamaObject:
    def __init__(self, selection=None, name=None):
        from pymol import _ext_gui as pmgapp
        if pmgapp is not None:
            import Pmw
            rootframe = Pmw.MegaToplevel(pmgapp.root)
            parent = rootframe.interior()
        else:
            rootframe = Tkinter.Tk()
            parent = rootframe

        rootframe.title(' Dynamic Angle Plotting ')
        rootframe.protocol("WM_DELETE_WINDOW", self.close_callback)

        canvas = SimplePlot(parent,width=320,height=320)
        canvas.bind("<Button-2>",canvas.pickWhich)
        canvas.bind("<Button-3>",canvas.pickWhich)
        canvas.pack(side=Tkinter.LEFT,fill="both",expand=1)
        canvas.axis(xint=150,
                xlabels=[-180,-120,-60,0,60,120,180],
                ylabels=[-180,-150,-120,-90,-60,-30,0,30,60,90,120,150,180])
        canvas.update()

        if name is None:
            try:
                name = cmd.get_unused_name('DynoRama')
            except AttributeError:
                name = 'DynoRamaObject'

        self.rootframe = rootframe
        self.canvas = canvas
        self.name = name
        self.lock = 0

        if name != 'none':
            auto_zoom = cmd.get('auto_zoom')
            cmd.set('auto_zoom', 0)
            cmd.load_callback(self, name)
            cmd.set('auto_zoom', auto_zoom)
            canvas.bind("<ButtonPress-1>",canvas.down)
            canvas.bind("<ButtonRelease-1>",canvas.up)
            canvas.bind("<Motion>",canvas.drag)

        if selection is not None:
            self.start(selection)

        if with_mainloop and pmgapp is None:
            rootframe.mainloop()

    def close_callback(self):
        cmd.delete(self.name)
        self.rootframe.destroy()

    def start(self,sel):
        self.lock = 1
        cmd.iterate('(%s) and name CA' % sel, 'idx2resn[model,index] = resn',
                space={'idx2resn': self.canvas.idx2resn})
        for model_index, (phi,psi) in cmd.get_phipsi(sel).iteritems():
            print " Plotting Phi,Psi: %8.2f,%8.2f" % (phi, psi)
            self.canvas.plot(phi, psi, model_index)
        self.lock = 0

    def __call__(self):
        if self.lock:
            return
        # Loop through each item on plot to see if updated
        for value in self.canvas.shapes.itervalues():
            # Look for update flag...
            if value[2]:
                # Set residue's phi,psi to new values
                model, index = value[5]
                print " Re-setting Phi,Psi: %8.2f,%8.2f" % (value[3],value[4])
                set_phipsi(model, index, value[3], value[4])
                value[2] = 0

def rama(sel='(all)', name=None):
    '''
DESCRIPTION

    Ramachandran Plot
    http://pymolwiki.org/index.php/DynoPlot

ARGUMENTS

    sel = string: atom selection {default: all}

    name = string: name of callback object which is responsible for setting
    angles when canvas points are dragged, or 'none' to not create a callback
    object {default: DynoRamaObject}
    '''
    DynoRamaObject(sel, name)

# Extend these commands
cmd.extend('ramachandran', rama)
cmd.auto_arg[0]['ramachandran'] = cmd.auto_arg[0]['zoom']

# Add to plugin menu
def __init_plugin__(self):
    self.menuBar.addcascademenu('Plugin', 'PlotTools', 'Plot Tools', label='Plot Tools')
    self.menuBar.addmenuitem('PlotTools', 'command', 'Launch Rama Plot', label='Rama Plot',
            command = lambda: DynoRamaObject('(enabled)'))

# vi:expandtab:smarttab

ADDITIONAL RESOURCES