Matplotlib FigureCanvasTkAgg 在使用 Tk Spinbox 小部件后未检测到按键
Matplotlib FigureCanvasTkAgg not detecting keypress after using Tk Spinbox widget
我正在利用Tkinter 和matplotlib 编写一个图像裁剪工具。使用嵌入在 Tkinter canvas (FigureCanvasTkAgg) 上的 matplotlib 显示图像。然后用户将能够在图像上放置 4 个点。通过按住 Shift 键并单击鼠标左键来放置一个点。按鼠标右键将删除该点。
然后绘制连接这 4 个点的多边形。用户可以通过更改 Tkinter Spinbox 小部件中的值来拉伸多边形的每一侧。
这就是问题所在。如果使用 Spinbox 的输入框部分,这会从 canvas/matplotlib figure/axis 获取焦点。然后我无法放置更多点,因为虽然检测到鼠标左键单击,但不再检测到按下 shift 键!
我已尝试删除大多数不必要的代码。
图像文件的路径在第 205 行进行了硬编码,您需要更改它。
# -*- coding: utf-8 -*-
"""
Created on Wed Jan 18 20:28:52 2017
Sample of a script which is used to crop an image at user placed points.
The user will place 4 points on the displayed image.
A quadrangle will then be created by joining the 4 points.
Each side of the quadrangle can be extruded by changing the 'buffer values'.
Instructions:
Hold shift and left click on image to place a new point.
Right click to delete a point.
"""
from __future__ import division
import Tkinter as Tk
# Matplotlib (mpl) - for plotting data/arrays/images.
import matplotlib
matplotlib.use('TkAgg')
from matplotlib import figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
from matplotlib.backend_bases import key_press_handler
class MyApp(Tk.Frame):
def __init__(self, master=None):
self.master = master
# Drawing points and lines class
self.Vectors = Vectors(parent_class=self)
Tk.Frame.__init__(self, master)
self.master.wm_title("Sample Script!")
# Position of top-left corner of the app. (from left side of screen, from top of screen).
self.master.geometry("+100+10")
self.frame1 = Tk.Frame(self.master) # Canvas 1
self.frame1.pack(side=Tk.LEFT)
self.frame2 = Tk.Frame(self.master) # Buttons
self.frame2.pack(side=Tk.BOTTOM)
self.fig = figure.Figure(dpi=110)#plt.figure()
self.ax1 = self.fig.add_subplot(1,1,1)
self.ax1.set_aspect('equal', 'datalim')
self.ax1.grid(True)
self.ax1.autoscale(True)
self.canvas = FigureCanvasTkAgg(self.fig, master=self.frame1)
self.canvas.show()
self.canvas.get_tk_widget().pack(side=Tk.TOP, fill=Tk.BOTH, expand=1)
# Events
# keyboard events on the canvas (image)
self.canvas.mpl_connect('key_press_event', self.on_canvas_key_press)
# mouse click events on the canvas (image/plot)
self.canvas.mpl_connect("button_press_event", self.on_button_canvas1)
# clicking on drawn artists events:
self.canvas.mpl_connect('pick_event', self.on_pick_canvas1)
# Mouse Events
self.canvas.mpl_connect('figure_enter_event', self.enter_figure)
self.canvas.mpl_connect('figure_leave_event', self.leave_figure)
self.canvas.mpl_connect('axes_enter_event', self.enter_axes)
self.canvas.mpl_connect('axes_leave_event', self.leave_axes)
self.cursor_x = None
self.cursor_y = None
# targest lines on ax1 at cursor position.
self.ax1_hline = None
self.ax1_vline = None
# add the matplotlib navigation toolbar, connected to the canvas.
self.add_toolbar() # add the Navigation toolbar for this canvas
# add the GUI for editing the rectangle buffer.
self.buffer_settings(self.frame2)
# Buttont which displays the test image.
Button_TestImage = Tk.Button(self.frame2,
text="test_disp_image()",
command = self.test_disp_image)
Button_TestImage.pack()
def add_toolbar(self):
""" Add the matplotlib inbuilt Navigation toolbar
http://matplotlib.org/users/navigation_toolbar.html """
#self.toolbar = NavigationToolbar2TkAgg(self.canvas, self.frame1)
self.toolbar = CustomToolbar(self.canvas, self.frame1)
def buffer_settings(self, frame):
""" GUI for setting buffer distance for each line around clipping
quadrangle. """
self.buf_frame = Tk.Frame(frame)
self.buf_frame.pack(side=Tk.LEFT)
self.spinbox_frame = Tk.Frame(frame)
self.spinbox_frame.pack(side=Tk.RIGHT)
# Buffer settings:
self.buf_N = Tk.IntVar()
self.buf_N.set(50)
self.buf_N.trace('w', self.buff_callback)
self.buf_E = Tk.IntVar()
self.buf_E.set(140)
self.buf_E.trace('w', self.buff_callback)
self.buf_S = Tk.IntVar()
self.buf_S.set(50)
self.buf_S.trace('w', self.buff_callback)
self.buf_W = Tk.IntVar()
self.buf_W.set(250)
self.buf_W.trace('w', self.buff_callback)
# spinbox selector for each buffer selection.
n = Tk.Spinbox(self.spinbox_frame, from_=-1000, to=1000,
textvariable=self.buf_N)
n.grid(row=1, column=1)
w = Tk.Spinbox(self.spinbox_frame, from_=-1000, to=1000,
textvariable=self.buf_W)
w.grid(row=2, column=0)
e = Tk.Spinbox(self.spinbox_frame, from_=-1000, to=1000,
textvariable=self.buf_E)
e.grid(row=2, column=2)
s = Tk.Spinbox(self.spinbox_frame, from_=-1000, to=1000,
textvariable=self.buf_S)
s.grid(row=3, column=1)
def buff_callback(self, *args):
print "variable changed!"
self.update_polygon()
def update_polygon(self):
""" update buffered polygon when buffer distance is changed"""
# TODO - update polygon using adjusted bufer values
# Get buffer spinbox values
n, e, s, w = self.get_buffer_settings()
print "n, e, s, w", n, e, s, w
def get_buffer_settings(self):
""" get the buffer spinbox values """
n = self.buf_N.get()
e = self.buf_E.get()
s = self.buf_S.get()
w = self.buf_W.get()
return (n, e, s, w)
def on_canvas_key_press(self, event):
print "on_canvas_key_press()"
key_press_handler(event, self.canvas, self.toolbar)
def on_pick_canvas1(self, event):
"""
This func called when an artist is picked on canvas 1
i.e. clicked on a point.
If multiple overlapping artists are picked, this func will be call
for each one."""
print "on_pick_canvas1()"
if event.mouseevent.inaxes is not None and not hasattr(event, 'already_picked'):
mouseevent = event.mouseevent
if mouseevent.button == 3:
# delete point.
print "delete point!"
for saved_point_artist in self.Vectors.points:
if saved_point_artist.contains(mouseevent)[0] == True:
# remove the point from the canvas
saved_point_artist.remove()
# delete the saved artist from the list
self.Vectors.points.remove(saved_point_artist)
# Update the canvas
self.canvas.draw()
return
def on_button_canvas1(self, event):
""" mouse button events on the canvas.
http://matplotlib.org/api/backend_bases_api.html#matplotlib.backend_bases.MouseEvent"""
print "on_button_canvas1()"
button = event.button
key = event.key
print "button, key", button, key
if event.inaxes is not None:
# image pixel position (centre of pixel)
x, y = (event.xdata, event.ydata)
# offset to corner of pixel
x, y = x + 0.5, y + 0.5
print x,y
print('button=%d, x=%d, y=%d, xdata=%f, ydata=%f' %
(event.button, event.x, event.y, event.xdata, event.ydata, ))
# process left click
if button == 1:
# hold shift to draw a point.
if key == 'shift':
self.Vectors.new_box_point(x, y)
self.canvas.draw()
return
def test_disp_image(self):
""" read and display a .jpg image file."""
img_path = r"C:\Scripts\parade_dog.jpg"
# read image
self.im = matplotlib.image.imread(img_path)
self.ax1.imshow(self.im)
self.canvas.draw()
def enter_axes(self, event):
print('enter_axes', event.inaxes)
def leave_axes(self, event):
print('leave_axes', event.inaxes)
def enter_figure(self, event):
print('enter_figure', event.canvas.figure)
def leave_figure(self, event):
print('leave_figure', event.canvas.figure)
class Vectors:
def __init__(self, parent_class):
self.parent = parent_class # the plotting class
self.points = []
def new_box_point(self, x, y):
""" master new point funtion"""
# draw a point on the canvas, and save point to list.
point_artist = self.draw_point(x, y)
self.points.append(point_artist)
def draw_point(self, x, y):
centre = (x,y)
fluro_green = (0.271, 0.99, 0.271)# equivalent to (70,250, 70)
point = matplotlib.patches.Circle(xy=centre, radius=5,
edgecolor=fluro_green,
facecolor=fluro_green,
fill=True,
visible=True,
picker=15)
self.parent.ax1.add_patch(point)
return point # return the artist.
class CustomToolbar(NavigationToolbar2TkAgg):
def __init__(self,canvas_,parent_):
self.toolitems = (
('Home', "Reset original view\n\
('h' or 'r')", 'home', 'home'),
('Back', "previous view\n\
('c' or 'left arrow' or 'backspace')", 'back', 'back'),
('Forward', "next view\n\
('v' or 'left arrow')", 'forward', 'forward'),
(None, None, None, None),
('Pan', "Pan axis (left mouse), Zoom (right mouse).\n\
('p')", 'move', 'pan'),
('Zoom', "Zoom box\n\
('o')", 'zoom_to_rect', 'zoom'),
(None, None, None, None),
# ('Subplots', 'Configure subplots axis', 'subplots', 'configure_subplots'),
('Save', 'Save plot', 'filesave', 'save_figure'),
)
NavigationToolbar2TkAgg.__init__(self,canvas_,parent_)
if __name__ == "__main__":
app = MyApp()
app.mainloop()
这里缺少的成分是使用 get_tk_widget()
访问 canvas 小部件,然后允许我使用 focus_force()
函数将焦点从 Entry 小部件更改为 canvas.
例如
self.fig.canvas.get_tk_widget().focus_force()
我正在利用Tkinter 和matplotlib 编写一个图像裁剪工具。使用嵌入在 Tkinter canvas (FigureCanvasTkAgg) 上的 matplotlib 显示图像。然后用户将能够在图像上放置 4 个点。通过按住 Shift 键并单击鼠标左键来放置一个点。按鼠标右键将删除该点。 然后绘制连接这 4 个点的多边形。用户可以通过更改 Tkinter Spinbox 小部件中的值来拉伸多边形的每一侧。
这就是问题所在。如果使用 Spinbox 的输入框部分,这会从 canvas/matplotlib figure/axis 获取焦点。然后我无法放置更多点,因为虽然检测到鼠标左键单击,但不再检测到按下 shift 键!
我已尝试删除大多数不必要的代码。 图像文件的路径在第 205 行进行了硬编码,您需要更改它。
# -*- coding: utf-8 -*-
"""
Created on Wed Jan 18 20:28:52 2017
Sample of a script which is used to crop an image at user placed points.
The user will place 4 points on the displayed image.
A quadrangle will then be created by joining the 4 points.
Each side of the quadrangle can be extruded by changing the 'buffer values'.
Instructions:
Hold shift and left click on image to place a new point.
Right click to delete a point.
"""
from __future__ import division
import Tkinter as Tk
# Matplotlib (mpl) - for plotting data/arrays/images.
import matplotlib
matplotlib.use('TkAgg')
from matplotlib import figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
from matplotlib.backend_bases import key_press_handler
class MyApp(Tk.Frame):
def __init__(self, master=None):
self.master = master
# Drawing points and lines class
self.Vectors = Vectors(parent_class=self)
Tk.Frame.__init__(self, master)
self.master.wm_title("Sample Script!")
# Position of top-left corner of the app. (from left side of screen, from top of screen).
self.master.geometry("+100+10")
self.frame1 = Tk.Frame(self.master) # Canvas 1
self.frame1.pack(side=Tk.LEFT)
self.frame2 = Tk.Frame(self.master) # Buttons
self.frame2.pack(side=Tk.BOTTOM)
self.fig = figure.Figure(dpi=110)#plt.figure()
self.ax1 = self.fig.add_subplot(1,1,1)
self.ax1.set_aspect('equal', 'datalim')
self.ax1.grid(True)
self.ax1.autoscale(True)
self.canvas = FigureCanvasTkAgg(self.fig, master=self.frame1)
self.canvas.show()
self.canvas.get_tk_widget().pack(side=Tk.TOP, fill=Tk.BOTH, expand=1)
# Events
# keyboard events on the canvas (image)
self.canvas.mpl_connect('key_press_event', self.on_canvas_key_press)
# mouse click events on the canvas (image/plot)
self.canvas.mpl_connect("button_press_event", self.on_button_canvas1)
# clicking on drawn artists events:
self.canvas.mpl_connect('pick_event', self.on_pick_canvas1)
# Mouse Events
self.canvas.mpl_connect('figure_enter_event', self.enter_figure)
self.canvas.mpl_connect('figure_leave_event', self.leave_figure)
self.canvas.mpl_connect('axes_enter_event', self.enter_axes)
self.canvas.mpl_connect('axes_leave_event', self.leave_axes)
self.cursor_x = None
self.cursor_y = None
# targest lines on ax1 at cursor position.
self.ax1_hline = None
self.ax1_vline = None
# add the matplotlib navigation toolbar, connected to the canvas.
self.add_toolbar() # add the Navigation toolbar for this canvas
# add the GUI for editing the rectangle buffer.
self.buffer_settings(self.frame2)
# Buttont which displays the test image.
Button_TestImage = Tk.Button(self.frame2,
text="test_disp_image()",
command = self.test_disp_image)
Button_TestImage.pack()
def add_toolbar(self):
""" Add the matplotlib inbuilt Navigation toolbar
http://matplotlib.org/users/navigation_toolbar.html """
#self.toolbar = NavigationToolbar2TkAgg(self.canvas, self.frame1)
self.toolbar = CustomToolbar(self.canvas, self.frame1)
def buffer_settings(self, frame):
""" GUI for setting buffer distance for each line around clipping
quadrangle. """
self.buf_frame = Tk.Frame(frame)
self.buf_frame.pack(side=Tk.LEFT)
self.spinbox_frame = Tk.Frame(frame)
self.spinbox_frame.pack(side=Tk.RIGHT)
# Buffer settings:
self.buf_N = Tk.IntVar()
self.buf_N.set(50)
self.buf_N.trace('w', self.buff_callback)
self.buf_E = Tk.IntVar()
self.buf_E.set(140)
self.buf_E.trace('w', self.buff_callback)
self.buf_S = Tk.IntVar()
self.buf_S.set(50)
self.buf_S.trace('w', self.buff_callback)
self.buf_W = Tk.IntVar()
self.buf_W.set(250)
self.buf_W.trace('w', self.buff_callback)
# spinbox selector for each buffer selection.
n = Tk.Spinbox(self.spinbox_frame, from_=-1000, to=1000,
textvariable=self.buf_N)
n.grid(row=1, column=1)
w = Tk.Spinbox(self.spinbox_frame, from_=-1000, to=1000,
textvariable=self.buf_W)
w.grid(row=2, column=0)
e = Tk.Spinbox(self.spinbox_frame, from_=-1000, to=1000,
textvariable=self.buf_E)
e.grid(row=2, column=2)
s = Tk.Spinbox(self.spinbox_frame, from_=-1000, to=1000,
textvariable=self.buf_S)
s.grid(row=3, column=1)
def buff_callback(self, *args):
print "variable changed!"
self.update_polygon()
def update_polygon(self):
""" update buffered polygon when buffer distance is changed"""
# TODO - update polygon using adjusted bufer values
# Get buffer spinbox values
n, e, s, w = self.get_buffer_settings()
print "n, e, s, w", n, e, s, w
def get_buffer_settings(self):
""" get the buffer spinbox values """
n = self.buf_N.get()
e = self.buf_E.get()
s = self.buf_S.get()
w = self.buf_W.get()
return (n, e, s, w)
def on_canvas_key_press(self, event):
print "on_canvas_key_press()"
key_press_handler(event, self.canvas, self.toolbar)
def on_pick_canvas1(self, event):
"""
This func called when an artist is picked on canvas 1
i.e. clicked on a point.
If multiple overlapping artists are picked, this func will be call
for each one."""
print "on_pick_canvas1()"
if event.mouseevent.inaxes is not None and not hasattr(event, 'already_picked'):
mouseevent = event.mouseevent
if mouseevent.button == 3:
# delete point.
print "delete point!"
for saved_point_artist in self.Vectors.points:
if saved_point_artist.contains(mouseevent)[0] == True:
# remove the point from the canvas
saved_point_artist.remove()
# delete the saved artist from the list
self.Vectors.points.remove(saved_point_artist)
# Update the canvas
self.canvas.draw()
return
def on_button_canvas1(self, event):
""" mouse button events on the canvas.
http://matplotlib.org/api/backend_bases_api.html#matplotlib.backend_bases.MouseEvent"""
print "on_button_canvas1()"
button = event.button
key = event.key
print "button, key", button, key
if event.inaxes is not None:
# image pixel position (centre of pixel)
x, y = (event.xdata, event.ydata)
# offset to corner of pixel
x, y = x + 0.5, y + 0.5
print x,y
print('button=%d, x=%d, y=%d, xdata=%f, ydata=%f' %
(event.button, event.x, event.y, event.xdata, event.ydata, ))
# process left click
if button == 1:
# hold shift to draw a point.
if key == 'shift':
self.Vectors.new_box_point(x, y)
self.canvas.draw()
return
def test_disp_image(self):
""" read and display a .jpg image file."""
img_path = r"C:\Scripts\parade_dog.jpg"
# read image
self.im = matplotlib.image.imread(img_path)
self.ax1.imshow(self.im)
self.canvas.draw()
def enter_axes(self, event):
print('enter_axes', event.inaxes)
def leave_axes(self, event):
print('leave_axes', event.inaxes)
def enter_figure(self, event):
print('enter_figure', event.canvas.figure)
def leave_figure(self, event):
print('leave_figure', event.canvas.figure)
class Vectors:
def __init__(self, parent_class):
self.parent = parent_class # the plotting class
self.points = []
def new_box_point(self, x, y):
""" master new point funtion"""
# draw a point on the canvas, and save point to list.
point_artist = self.draw_point(x, y)
self.points.append(point_artist)
def draw_point(self, x, y):
centre = (x,y)
fluro_green = (0.271, 0.99, 0.271)# equivalent to (70,250, 70)
point = matplotlib.patches.Circle(xy=centre, radius=5,
edgecolor=fluro_green,
facecolor=fluro_green,
fill=True,
visible=True,
picker=15)
self.parent.ax1.add_patch(point)
return point # return the artist.
class CustomToolbar(NavigationToolbar2TkAgg):
def __init__(self,canvas_,parent_):
self.toolitems = (
('Home', "Reset original view\n\
('h' or 'r')", 'home', 'home'),
('Back', "previous view\n\
('c' or 'left arrow' or 'backspace')", 'back', 'back'),
('Forward', "next view\n\
('v' or 'left arrow')", 'forward', 'forward'),
(None, None, None, None),
('Pan', "Pan axis (left mouse), Zoom (right mouse).\n\
('p')", 'move', 'pan'),
('Zoom', "Zoom box\n\
('o')", 'zoom_to_rect', 'zoom'),
(None, None, None, None),
# ('Subplots', 'Configure subplots axis', 'subplots', 'configure_subplots'),
('Save', 'Save plot', 'filesave', 'save_figure'),
)
NavigationToolbar2TkAgg.__init__(self,canvas_,parent_)
if __name__ == "__main__":
app = MyApp()
app.mainloop()
这里缺少的成分是使用 get_tk_widget()
访问 canvas 小部件,然后允许我使用 focus_force()
函数将焦点从 Entry 小部件更改为 canvas.
例如
self.fig.canvas.get_tk_widget().focus_force()