modules/pmg_tk/volume.py (902 lines of code) (raw):
'''
Volume color ramp GUI
'''
import os
import math
import colorsys
import itertools
try:
import Tkinter
except ImportError:
import tkinter as Tkinter
try:
from pymol import cmd
except ImportError:
cmd = None
CIRCLE_RADIUS = 4
STATE_SHIFT = 0x0001
STATE_CTRL = 0x0004
DEFAULT_COLORS = [
(1., 1., 0.),
(1., 0., 0.),
(0., 0., 1.),
(0., 1., 0.),
(0., 1., 1.),
(1., 0., 1.),
]
help = '''
VOLUME PANEL HELP
--------------------------------------------------
Canvas Mouse Actions (no Point under Cursor)
L-Click Add point
CTRL+L-Click Add 3 points (isosurface)
--------------------------------------------------
Mouse Actions with Point under Cursor
L-Click Edit point color
CTRL+L-Click Edit 3 points
M-Click Remove Point
SHIFT+L-Click Remove Point
CTRL+M-Click Remove 3 points
CTRL+SHIFT+L-Click Remove 3 points
L-Drag Move point
CTRL+L-Drag Move 3 points (horizontal only)
R-Drag Move point along one axis only
--------------------------------------------------
L = Left mouse button
M = Middle mouse button
R = Right mouse button
--------------------------------------------------
See also the "volume_color" command for getting and
setting volume colors on the command line.
'''
class ColorChooser(Tkinter.Toplevel):
"""
HSV-based color chooser modal dialog with data and alpha entry fields.
"""
def __init__(self, panel, points, title='Color Chooser'):
'''
@type panel: VolumePanel
@type points: sequence of VRGBA
'''
self.panel = panel
parent = panel.frame
Tkinter.Toplevel.__init__(self, parent)
self.transient(parent)
self.withdraw()
self.resizable(0, 0)
self.title(title)
self.wm_protocol('WM_DELETE_WINDOW', self.accept)
self.points = points
self._undo_data = [p.__getstate__() for p in points]
imgName = os.path.expandvars("$PYMOL_DATA/pymol/hsv.ppm")
img = Tkinter.PhotoImage(file=imgName)
self.image_width = img.width()
self.image_height = img.height()
frame = Tkinter.Frame(self)
self.canvas = Tkinter.Canvas(frame,
width=self.image_width,
height=self.image_height)
self.canvas.pack(side=Tkinter.TOP)
self.canvas.create_image((0, 0), image=img, anchor=Tkinter.NW)
self.oval_id = self.canvas.create_oval((-10,) * 4)
p = points[0]
self.var_value = Tkinter.DoubleVar(self, value=p.value)
self.var_alpha = Tkinter.DoubleVar(self, value=p.alpha)
self.var_color = Tkinter.StringVar(self)
self.set_rgb(p.rgb)
self.canvas.bind("<Button-1>", self.pick_color)
self.canvas.bind('<Button1-Motion>', self.pick_color)
self.canvas.bind("<Double-Button-1>", self.accept)
grid = Tkinter.Frame(frame)
for i, text, var in [
(0, 'Color:', self.var_color),
(1, 'Data value:', self.var_value),
(2, 'Opacity [0..1]:', self.var_alpha),
]:
e = Tkinter.Label(grid, text=text)
e.grid(row=i, column=0, sticky='nw')
e = Tkinter.Entry(grid, width=7, textvariable=var)
e.grid(row=i, column=1, sticky='nw')
e.bind('<Return>', self.accept)
e.bind('<FocusOut>', self.update_canvas)
grid.pack(side=Tkinter.LEFT)
buttonbox = Tkinter.Frame(frame)
Tkinter.Button(buttonbox, text='OK', command=self.accept).pack(side=Tkinter.RIGHT)
Tkinter.Button(buttonbox, text='Cancel', command=self.cancel).pack(side=Tkinter.RIGHT)
buttonbox.pack(side=Tkinter.BOTTOM, fill='x')
x, y = self.winfo_pointerxy()
self.geometry("+%d+%d" % (x + 20, y - 20))
frame.pack(padx=5, pady=5)
self.deiconify()
self.grab_set()
self.focus_set()
self._img = img # keep a reference!
def update_upstream(self):
'''
Update the volume panel
'''
p = self.points[0]
p.value = self.var_value.get()
p.alpha = self.var_alpha.get()
rgb = self.get_rgb()
for p in self.points:
p.rgb = rgb
self.panel.ramp_changed()
def move_oval(self, X, Y):
self.canvas.coords(self.oval_id, (
X - CIRCLE_RADIUS, Y - CIRCLE_RADIUS,
X + CIRCLE_RADIUS, Y + CIRCLE_RADIUS))
def update_canvas(self, event):
'''
Update canvas from entry fields
'''
self.set_rgb(self.get_rgb())
def set_rgb(self, rgb, update_oval=True):
'''
Update RGB entry field and optionally move the oval
'''
self.var_color.set('#%02x%02x%02x' % tuple(
min(max(v, 0), 1) * 255 for v in rgb))
if update_oval:
h, s, v = colorsys.rgb_to_hsv(*rgb)
X = h * self.image_width
H2 = self.image_height / 2
Y = s * H2 if (s < 1.0) else (2.0 - v) * H2
self.move_oval(X, Y)
if self.panel.instant_update.get():
self.update_upstream()
def get_rgb(self):
'''
return RGB float (0..1) tuple
'''
s = self.var_color.get()
return tuple(int(s[i:i+2], 16) / 255. for i in range(1, 6, 2))
def pick_color(self, event):
'''
Move the oval and update the RGB entry field
'''
x = min(max(event.x, 0), self.image_width)
y = min(max(event.y, 0), self.image_height)
self.move_oval(x, y)
h = float(x) / self.image_width
s, v = 2.0 * y / self.image_height, 1.0
if s > 1.0:
s, v = 1.0, 2.0 - s
self.set_rgb(colorsys.hsv_to_rgb(h, s, v), False)
def cancel(self, event=None):
for p, undo in zip(self.points, self._undo_data):
p.__setstate__(undo)
self.panel.ramp_changed()
self.destroy()
def accept(self, event=None):
self.update_upstream()
self.destroy()
class VRGBA(object):
'''
Simple Value-RGB-Alpha type
'''
def __init__(self, canvas, value, alpha, rgb):
'''
@type canvas: Tkinter.Canvas
@type value: float
@type alpha: float
@type rgb: tuple
@param rgb: 0..1 float RGB
'''
self.value = value
self.rgb = rgb
self.alpha = alpha
self.canvas_id = canvas.create_oval((-10,) * 4, tags=('dynamic', 'colorpoint'))
def __iter__(self):
yield self.value
yield self.rgb[0]
yield self.rgb[1]
yield self.rgb[2]
yield self.alpha
def __getstate__(self):
return list(self)
def __setstate__(self, state):
self.value = state[0]
self.rgb = state[1:4]
self.alpha = state[4]
@property
def alpha(self):
'''
@type: float
'''
return self._alpha
@alpha.setter
def alpha(self, v):
self._alpha = min(max(v, 0.0), 1.0)
@property
def hexcolor(self):
'''
@type: str
'''
r, g, b = self.rgb
return "#%02x%02x%02x" % (r * 255, g * 255, b * 255)
class RangeEntry(Tkinter.Entry):
'''
Entry field for canvas value range
'''
def __init__(self, parent, panel, vname, rname):
'''
@type parent: Tkinter.Widget
@type panel: VolumePanel
@type vname: str
@param vname: panel field name of linked property
@type rname: str
@param vname: panel field name of corresponding range
'''
Tkinter.Entry.__init__(self, parent, width=5, bg='white')
self._panel = panel
self._vname = vname
self._rname = rname
self.bind('<Return>', self.onchange)
self.bind('<FocusOut>', self.onchange)
self.bind('<Button-4>', self.increment)
self.bind('<Button-5>', self.decrement)
self.bind('<Up>', self.increment)
self.bind('<Down>', self.decrement)
self._update()
def _set(self, v):
setattr(self._panel, self._vname, v)
self._update()
self._panel.redraw()
def _update(self):
v = getattr(self._panel, self._vname)
self.delete(0, Tkinter.END)
self.insert(0, v)
self.config(bg='white')
def increment(self, event, m=0.05):
r = getattr(self._panel, self._rname)
v = getattr(self._panel, self._vname)
self._set(v + r * m)
return 'break'
def decrement(self, event, m=0.05):
return self.increment(event, -m)
def onchange(self, event):
try:
v_new = float(self.get())
except ValueError:
self.config(bg='red')
return False
v_old = getattr(self._panel, self._vname)
if v_new != v_old:
self._set(v_new)
return True
class VRGBACanvas(object):
'''
Editable Value-RGBA ramp graph
'''
def __init__(self, parent):
'''
@type parent: Tkinter.Widget
@type name: name of volume in PyMOL
'''
self.frame = Tkinter.Frame(parent)
self.canvas = canvas = Tkinter.Canvas(self.frame)
self.buttonbox = Tkinter.Frame(self.frame)
self.canvas_ids = {} # ID -> VRGBA
self.ramp = []
self.instant_update = Tkinter.BooleanVar(parent, value=1)
self.canvas_width = 0
self.canvas_height = 0
self._vmin = -5.
self._vmax = 5.
self._amin = 0.
self._amax = 1.
self._histcoords = ()
self.drag_ids = None
self.color_cycle = itertools.cycle(DEFAULT_COLORS)
# plot area padding
self.pad_left = 50
self.pad_right = 10
self.pad_bottom = 30
self.pad_top = 10
# entries
self.minmax_entries = [
RangeEntry(self.canvas, self, 'vmin', 'vrange'),
RangeEntry(self.canvas, self, 'vmax', 'vrange'),
RangeEntry(self.canvas, self, 'amax', 'arange'),
]
self.minmax_ids = [
canvas.create_window((-10, -10), window=self.minmax_entries[i], anchor=a)
for i, a in enumerate(['nw', 'ne', 'ne'])
]
# color line
self.colorline_id = canvas.create_line((-10,) * 4, tags=('dynamic',))
# coord tooltip
self.tooltip_id = canvas.create_text([-1, -1], text='', tags=('dynamic',), anchor='sw')
# bindings
canvas.bind('<Configure>', self.onconfig)
for i in range(1, 4):
self.canvas.bind('<Button-%d>' % i, self.onmousedown)
self.canvas.bind('<ButtonRelease-%d>' % i, self.onmouseup)
self.canvas.bind('<Button%d-Motion>' % i, self.ondrag)
for i in range(4, 6):
self.canvas.bind('<Button-%d>' % i, self.onmousewheel)
# buttons
Tkinter.Button(self.buttonbox, text='Get colors as script',
command=self.popup_script).pack(side="left")
Tkinter.Button(self.buttonbox, text='Help',
command=self.popup_help).pack(side="left")
Tkinter.Checkbutton(self.buttonbox, text='Update Volume while dragging',
var=self.instant_update).pack(side="right")
self.buttonbox.pack(side='bottom', fill='x')
self.canvas.pack(side='left', expand=1, fill='both')
def popup_help(self, event=None):
text_dialog(self.frame, help, 'Volume Panel Help')
def popup_script(self, event=None):
'''
Open a dialog with a script equivalent of this color ramp
'''
import random
r = self.get_flat()
rname = 'ramp%03d' % random.randint(0, 999)
s = ['### cut below here and paste into script ###']
s.append('cmd.volume_ramp_new(%s, [\\' % repr(rname))
for i in range(0, len(r), 5):
s.append(' %6.2f, %.2f, %.2f, %.2f, %.2f, \\' % tuple(r[i:i+5]))
s.append(' ])')
s.append('### cut above here and paste into script ###')
s += [
'',
'Paste into a .pml or .py script or your pymolrc file and use this',
'named color ramp on the PyMOL command line like this:',
'',
'PyMOL> volume_color yourvolume, %s' % rname,
]
text_dialog(self.frame, '\n'.join(s), 'Script')
def onhover(self, event=None):
'''
Mouse enter/leave event handler. Changes the mouse cursor when hovering over color points.
'''
if event and event.type == '7':
cursor = 'crosshair'
else:
cursor = ''
self.canvas.config(cursor=cursor)
def bind_hover_events(self):
'''
Bind mouse enter/leave events for color points
'''
self.canvas.tag_bind('colorpoint', '<Enter>', self.onhover, add=False)
self.canvas.tag_bind('colorpoint', '<Leave>', self.onhover, add=False)
def onconfig(self, event):
'''
Event callback for canvas configure, redraws everything
'''
self.canvas_width = event.width - self.pad_left - self.pad_right
self.canvas_height = event.height - self.pad_top - self.pad_bottom
self.redraw()
def set_flat(self, ramp):
'''
Set color points from flat (v0, r0, g0, b0, a0, v1, r1, ...) list
@type ramp: sequence of floats
'''
self.canvas.delete('colorpoint')
self.ramp = [VRGBA(self.canvas, ramp[i], ramp[i + 4], ramp[i + 1:i + 4])
for i in range(0, len(ramp), 5)]
self.update_canvas_ids()
self.plot_ramp()
def update_canvas_ids(self):
'''
Update mapping canvas_ids -> color point
'''
self.canvas_ids = dict((p.canvas_id, p) for p in self.ramp)
self.bind_hover_events()
def get_flat(self):
'''
Get color points as flat (v0, r0, g0, b0, a0, v1, r1, ...) list
@rtype: list of float
'''
flat = []
for p in self.ramp:
flat.append(p.value)
flat.extend(p.rgb)
flat.append(p.alpha)
return flat
@property
def vmin(self):
return self._vmin
@property
def vmax(self):
return self._vmax
@vmin.setter
def vmin(self, v):
self._vmin = min(v, self._vmax - 1e-2)
@vmax.setter
def vmax(self, v):
self._vmax = max(v, self._vmin + 1e-2)
@property
def vrange(self):
'''
value range of ploting area
@type: float
'''
return self.vmax - self.vmin
@property
def amin(self):
'''
alpha minimum of plotting area
@type: float
'''
return self._amin
@property
def amax(self):
'''
alpha maximum of plotting area
@type: float
'''
return self._amax
@amax.setter
def amax(self, a):
self._amax = min(max(a, 0.1), 1.0)
@property
def arange(self):
'''
alpha range of plotting area
@type: float
'''
return self.amax - self.amin
def add_point(self, value, alpha, color):
'''
Add a RGBA color at given value
@type value: float
@type alpha: float
@type color: 3-tuple
'''
pt = VRGBA(self.canvas, value, alpha, color)
for i, c in enumerate(self.ramp):
if c.value > value:
self.ramp.insert(i, pt)
break
else:
self.ramp.append(pt)
self.update_canvas_ids()
def closest_point(self, value):
'''
Search for point closest to value
@type value: float
@rtype: VRGBA or None
'''
if not self.ramp:
return None
return min(self.ramp, key=lambda p: abs(p.value - value))
def remove_point(self, p):
'''
@type p: VRGBA
'''
self.canvas.delete(p.canvas_id)
self.ramp.remove(p)
self.update_canvas_ids()
self.onhover()
# coordinate transform methods
def valueToCanvas(self, v):
x = (v - self.vmin) / self.vrange
return self.pad_left + self.canvas_width * x
def canvasToValue(self, x):
x = float(x - self.pad_left) / self.canvas_width
return self.vrange * x + self.vmin
def alphaToCanvas(self, a):
a = min(max(a, 0.), 1.)
y = (a - self.amin) / self.arange
y = math.log(1.0 + 9.0 * y, 10.0)
return self.pad_top + self.canvas_height * (1.0 - y)
def canvasToAlpha(self, y):
y = 1.0 - float(y - self.pad_top) / self.canvas_height
y = (10.0 ** y - 1.0) / 9.0
a = y * self.arange + self.amin
return min(max(a, 0.), 1.)
def vaToCanvas(self, coords):
return self._transCoords(coords,
self.valueToCanvas, self.alphaToCanvas)
def canvasToVA(self, coords):
return self._transCoords(coords,
self.canvasToValue, self.canvasToAlpha)
def _transCoords(self, coords, xfunc, yfunc):
c = [0] * len(coords)
c[0::2] = [xfunc(x) for x in coords[0::2]]
c[1::2] = [yfunc(y) for y in coords[1::2]]
return c
# end coordinate transformation methods
def create_line_va(self, coords, *args, **kwargs):
'''
Draw a line with value and alpha coordinates
@type coords: sequence of floats
'''
c = self.vaToCanvas(coords)
self.canvas.create_line(c, *args, **kwargs)
def set_hist(self, hist):
'''
Set the histogram, see also C{plot_hist}.
@type coords: sequence of floats
@param coords: histogram with (min, max, mean, stdev) as first 4
elements, followed by equally spaced bin counts.
'''
vmin, vmax = hist[:2]
hist = hist[4:]
try:
ihistmax = 1.0 / max(hist)
except ZeroDivisionError:
ihistmax = 0.0
binwidth = (vmax - vmin) / float(len(hist) - 1)
if not self._histcoords:
self.vmin = vmin
self.vmax = vmax
for e in self.minmax_entries:
e._update()
c = self._histcoords = []
for i in range(len(hist)):
c.append(float(i) * binwidth + vmin)
c.append(hist[i] * ihistmax)
def plot_hist(self):
'''
Plot the histogram which was set by C{set_hist}
'''
self.canvas.delete('hist')
self.create_line_va(self._histcoords, fill='blue', tags=('hist'))
def plot_axis(self):
'''
Plot the coordinate system axes
'''
self.canvas.delete('axis')
width, height = self.canvas_width, self.canvas_height
if width < 1 or height < 1:
return
x0 = self.valueToCanvas(self.vmin)
x1 = self.valueToCanvas(self.vmax)
y0 = self.alphaToCanvas(self.amin)
y1 = self.alphaToCanvas(self.amax)
gridkw = {'dash': (2, 5), 'fill': '#999999', 'tags': ('axis')}
# x-grid
u30 = self.canvasToValue(30 + self.pad_left) - self.vmin
try:
u = 5 ** int(1 + math.log(u30, 5))
except ValueError:
u = self.vrange / 4.0
v = round(self.vmin / u) * u
while v < self.vmax:
v += u
x = self.valueToCanvas(v)
self.canvas.create_line([x, y0, x, y1], **gridkw)
if x > self.pad_left + 50 and x < width:
self.canvas.create_line([x, y0, x, y0 + 5], tags=('axis'))
self.canvas.create_text([x, y0 + 7], text='%.4G' % v, tags=('axis'), anchor='n')
# y-grid
for i in range(1, 11):
a = i * 0.1
y = self.alphaToCanvas(a)
self.canvas.create_line([x0, y, x1, y], **gridkw)
if y > 50:
self.canvas.create_line([x0, y, x0 - 5, y], tags=('axis'))
self.canvas.create_text([x0 - 7, y], text='%.1f' % a, tags=('axis'), anchor='e')
# axes
coords = [x0, y1, x0, y0, x1, y0]
self.canvas.create_line(coords, width=3, tags=('axis'))
# entries
for xy, e in [
((x0, y0 + 4), self.minmax_ids[0]),
((x1, y0 + 4), self.minmax_ids[1]),
((x0 - 2, y1), self.minmax_ids[2]),
]:
self.canvas.coords(e, xy)
def redraw(self):
'''
Redraw or reposition all elements on canvas
'''
self.plot_hist()
self.plot_axis()
self.plot_ramp()
self.canvas.tag_raise('dynamic')
def plot_ramp(self):
'''
Update color points and line coordinates
'''
radius = CIRCLE_RADIUS
if self.canvas_width <= 0 or self.canvas_height <= 0:
return
polycoords = []
for idx, p in enumerate(self.ramp):
x = self.valueToCanvas(p.value)
y = self.alphaToCanvas(p.alpha)
polycoords.extend((x, y))
# plot the pt
self.canvas_set_position(p.canvas_id, x, y)
self.canvas.coords(p.canvas_id, (x - radius, y - radius, x + radius, y + radius))
self.canvas.itemconfig(p.canvas_id, fill=p.hexcolor)
if len(polycoords) < 4:
polycoords = (-10,) * 4
self.canvas.coords(self.colorline_id, tuple(polycoords))
def plot_colorline(self):
'''
Update color line coordinates
'''
if len(self.ramp) < 2:
polycoords = (-10,) * 4
else:
polycoords = []
for p in self.ramp:
xy = self.canvas_get_position(p.canvas_id)
polycoords.extend(xy)
self.canvas.coords(self.colorline_id, tuple(polycoords))
def canvas_get_position(self, ID):
'''
Get x, y canvas position (center) of color point
@type ID: int
'''
radius = CIRCLE_RADIUS
coords = self.canvas.coords(ID)
return coords[0] + radius, coords[1] + radius
def canvas_set_position(self, ID, x, y):
'''
Set canvas position (center) of color point
'''
x_old, y_old = self.canvas_get_position(ID)
self.canvas.move(ID, x - x_old, y - y_old)
def update_va_from_canvas(self, IDs):
'''
Update color points value and alpha from positions on canvas
@type IDs: iterable
@param IDs: canvas element ids
'''
for ID in IDs:
p = self.canvas_ids[ID]
x, y = self.canvas_get_position(ID)
p.value = self.canvasToValue(x)
p.alpha = self.canvasToAlpha(y)
def canvas_remove_point(self, ID):
'''
@type ID: int
@param ID: canvas element id
'''
p = self.canvas_ids[ID]
return self.remove_point(p)
def canvas_add_point(self, x, y):
'''
Add point on canvas. Use the color of the closest neighbor.
@type x: int
@type y: int
'''
v = self.canvasToValue(x)
a = self.canvasToAlpha(y)
c = next(self.color_cycle)
self.add_point(v, a, c)
def canvas_add_peak(self, x, y):
'''
Add three points on canvas, one at (x,y) and two at (x +/- 10, y=0)
@type x: int
@type y: int
'''
v1 = self.canvasToValue(x - 10)
v2 = self.canvasToValue(x)
v3 = self.canvasToValue(x + 10)
a = self.canvasToAlpha(y)
c = next(self.color_cycle)
self.add_point(v1, 0, c)
self.add_point(v2, a, c)
self.add_point(v3, 0, c)
def canvas_pick(self, x, y, pad=2.):
'''
Get canvas ID of color point at (x, y)
@type x: int
@type y: int
@type pad: float
@param pad: extra padding for easier picking
'''
a = self.canvas.find_closest(x, y)
if not a or a[0] not in self.canvas_ids:
return None
center = self.canvas_get_position(a[0])
d2 = (center[0] - x) ** 2 + (center[1] - y) ** 2
if d2 > (CIRCLE_RADIUS + pad) ** 2:
return None
return a[0]
def onmousewheel(self, event):
'''
Mouse wheel event handler
'''
factor = 1.05 if event.num == 4 else 0.95;
for p in self.ramp:
p.alpha *= factor
self.ramp_changed()
def onmousedown(self, event):
'''
Mouse down event handler
'''
x, y = event.x, event.y
canvas_right = self.pad_left + self.canvas_width
self.dragging = False
self.drag_ids = ()
self.drag_button = event.num
self.dragging_x = self.drag_start_x = x
self.dragging_y = self.drag_start_y = y
c = self.canvas_pick(x, y)
if not c:
if not event.num == 1:
return
# add
if x < self.pad_left \
or x > canvas_right \
or y < self.pad_top \
or y > self.pad_top + self.canvas_height:
return
if event.state & STATE_CTRL:
self.canvas_add_peak(event.x, event.y)
else:
self.canvas_add_point(event.x, event.y)
self.ramp_changed()
else:
# move
self.drag_ids = [c]
i = self.ramp.index(self.canvas_ids[c])
i_lower = i - 1
i_upper = i + 1
x, y = self.canvas.coords(c)[:2]
dx_upper, dy = event.x - x, event.y - y - CIRCLE_RADIUS
dx_lower = dx_upper
if event.state & STATE_CTRL:
# move peak
if i > 0:
c = self.ramp[i - 1].canvas_id
self.drag_ids.append(c)
x = self.canvas.coords(c)[0]
dx_lower = event.x - x
i_lower = i - 2
if i < len(self.ramp) - 1:
c = self.ramp[i + 1].canvas_id
self.drag_ids.append(c)
x = self.canvas.coords(c)[0]
dx_upper = event.x - x
i_upper = i + 2
self.allowed_y = (event.y, event.y)
else:
# move single point
self.allowed_y = (
min(event.y, self.pad_top + dy),
max(event.y, self.pad_top + dy + self.canvas_height))
# set up allowed x range
x_lower = x_upper = None
if i_lower > -1:
x = self.canvas.coords(self.ramp[i_lower].canvas_id)[0]
x_lower = x + dx_lower
else:
x_lower = self.pad_left + dx_lower - CIRCLE_RADIUS
if i_upper < len(self.ramp):
x = self.canvas.coords(self.ramp[i_upper].canvas_id)[0]
x_upper = x + dx_upper
else:
x_upper = canvas_right + dx_lower - CIRCLE_RADIUS
self.allowed_x = (min(event.x, x_lower), max(event.x, x_upper))
# middle-click or SHIFT-click remove
if event.num == 2 or event.num == 1 and event.state & STATE_SHIFT:
for c in self.drag_ids:
self.canvas_remove_point(c)
self.ramp_changed()
self.drag_ids = ()
def onmouseup(self, event):
'''
Mouse up event handler
'''
if not self.drag_ids:
return
if self.dragging:
self.canvas.itemconfig(self.tooltip_id, text='')
if not self.instant_update.get():
self.update_va_from_canvas(self.drag_ids)
else:
points = [self.canvas_ids[ID] for ID in self.drag_ids]
chooser = ColorChooser(self, points)
self.drag_ids = ()
def ramp_changed(self):
self.plot_ramp()
def clamp_xy_allowed(self, x, y):
'''
Clamp x and y to allowed dragging range
If dragging with right mouse button, drag in x or y direction
only (choose axis closer to pointer).
'''
if self.allowed_x[0] is not None and self.allowed_x[0] > x:
x = self.allowed_x[0]
if self.allowed_x[1] is not None and self.allowed_x[1] < x:
x = self.allowed_x[1]
if self.allowed_y[0] is not None and self.allowed_y[0] > y:
y = self.allowed_y[0]
if self.allowed_y[1] is not None and self.allowed_y[1] < y:
y = self.allowed_y[1]
if self.drag_button == 3:
if abs(x - self.drag_start_x) < abs(y - self.drag_start_y):
x = self.drag_start_x
else:
y = self.drag_start_y
return x, y
def ondrag(self, event):
'''
Mouse dragging event handler
'''
if not self.drag_ids:
return
self.dragging = True
x, y = self.clamp_xy_allowed(event.x, event.y)
dx = x - self.dragging_x
dy = y - self.dragging_y
self.dragging_x = x
self.dragging_y = y
# move points
for c in self.drag_ids:
self.canvas.move(c, dx, dy)
# tooltip
x, y = self.canvas_get_position(self.drag_ids[0])
v = self.canvasToValue(x)
a = self.canvasToAlpha(y)
dx = dy = CIRCLE_RADIUS
if y < 40:
anchor = 'n'
else:
anchor = 's'
dy *= -1
if x > self.pad_left + self.canvas_width - 80:
anchor += 'e'
dx *= -1
else:
anchor += 'w'
self.canvas.itemconfig(self.tooltip_id, anchor=anchor,
text='value: %G\nalpha: %.2f' % (v, a))
self.canvas.coords(self.tooltip_id, (x + dx, y + dy))
# colorline
self.plot_colorline()
if self.instant_update.get():
self.update_va_from_canvas(self.drag_ids)
def pack(self, cnf={}, **kw):
'''
Tkinter: pack in parent widget
'''
c = {'expand': 1, 'fill': 'both'}
c.update(cnf)
c.update(kw)
self.frame.pack(c)
def text_dialog(parent, text, title=''):
'''
Simple Text dialog
'''
import Pmw
dialog = Pmw.TextDialog(parent, title=title)
dialog.insert('end', text)
class VolumePanel(VRGBACanvas):
'''
Editable volume ramp graph
'''
def __init__(self, parent, name, _self=cmd):
'''
@type parent: Tkinter.Widget
@type name: name of volume in PyMOL
'''
self._super = super(VolumePanel, self)
self._super.__init__(parent)
self.cmd = _self
self.name = name
# set data from volume
hist = _self.get_volume_histogram(name)
ramp = _self.volume_color(name)
self.set_hist(hist)
self.set_flat(ramp)
def update_volume(self):
'''
Update volume colors in PyMOL
'''
ramp = self.get_flat()
self.cmd.volume_color(self.name, ramp, _guiupdate=False)
def update_va_from_canvas(self, *args):
self._super.update_va_from_canvas(*args)
self.update_volume()
def ramp_changed(self):
self._super.ramp_changed()
self.update_volume()
if __name__ == '__main__':
ramp = [-2.4, 0.0, 0.0, 1.0, 0.0,
1.7, 0.0, 0.0, 1.0, 0.0,
1.9, 1.0, 0.0, 0.2,
0.2, 2.12, 0.0, 0.0, 1.0, 0.0,
4.99, 0.0, 0.0, 1.0, 0.0]
hist = [-20.0, 20.0, -2.63597939920146e-07, 0.9998476505279541,
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 25.0,
80.0, 189.0, 895.0, 2571.0, 7627.0, 14798.0, 26494.0,
32283.0, 46630.0, 51979.0, 49263.0, 42430.0, 32592.0,
23094.0, 15904.0, 11534.0, 8759.0, 5858.0, 6157.0,
5183.0, 4335.0, 4264.0, 3498.0, 2845.0, 2863.0, 2334.0,
2085.0, 1719.0, 1314.0, 1047.0, 819.0, 687.0, 599.0,
335.0, 319.0, 197.0, 142.0, 99.0, 60.0, 32.0, 32.0,
26.0, 4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
window = Tkinter.Tk()
canvasramp = VRGBACanvas(window)
canvasramp.set_hist(hist)
canvasramp.set_flat(ramp)
canvasramp.pack()