I would like to pick lines that have been drawn on an image by using cv2.line and do something with them. To realise that I had a look on matplotlibs picker and even found a good example here. As I am going to use tkinter for GUI I added it to my MWE.
Code from example which works fine:
import sys
if sys.version_info[0] < 3:
import Tkinter as Tk
else:
import tkinter as Tk
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import matplotlib.pyplot as plt
import numpy as np
root = Tk.Tk()
root.iconify()
x = np.linspace(0, 10, 100)
fig, ax = plt.subplots()
for i in range(1, 10):
ax.plot(x, i * x + x, picker=5)
canvas = FigureCanvasTkAgg(fig, master=root)
canvas.get_tk_widget().pack(side=Tk.TOP, fill=Tk.BOTH, expand=1)
canvas._tkcanvas.pack(side=Tk.TOP, fill=Tk.BOTH, expand=1)
def on_pick(event):
event.artist.set_visible(not event.artist.get_visible())
fig.canvas.draw()
fig.canvas.callbacks.connect('pick_event', on_pick)
root.mainloop()
Now my code using OpenCV which does not work:
import sys
if sys.version_info[0] < 3:
import Tkinter as Tk
else:
import tkinter as Tk
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import numpy as np
import cv2
root = Tk.Tk()
root.iconify()
img = np.zeros([100,100,3],dtype=np.uint8)
img.fill(255)
cv2.line(img,(10,10),(60,90),(100,149,237),2)
fig = Figure()
ax = fig.add_subplot(111)
ax.set_aspect('equal')
ax.plot()
ax.imshow(img)
canvas = FigureCanvasTkAgg(fig, master=root)
canvas.get_tk_widget().pack(side=Tk.TOP, fill=Tk.BOTH, expand=1)
canvas._tkcanvas.pack(side=Tk.TOP, fill=Tk.BOTH, expand=1)
def onpick1(event):
event.artist.set_visible(not event.artist.get_visible())
fig.canvas.draw()
fig.canvas.callbacks.connect('pick_event', onpick1)
root.mainloop()
In both examples onpick1 should set the lines visibility on or off by a single mouse click but it doesn't. My assumption is that it has something to do with the way it is plotted or how I draw the lines (ax.plt vs cv2.line). I would be very happy about any help. Thanks!
Related
I am trying to display a GUI with a heatmap and scales/sliders with the scales/sliders changing the values in the heatmap.
I can display the heatmap and sliders and can read from the sliders but I cannot get the heat map to update after I have moved the sliders.
I have tried putting the code (I think) updates the heatmap in a function which is called whenever the scale/slider is moved but I am clearly missing something.
import tkinter
from tkinter import ttk
from tkinter import *
from tkinter.ttk import *
from tkinter import messagebox
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
def myFunc(value):
print (mySlider.get())
array[1][2]=mySlider.get()
#I think I need to put something here to update the heatmap when the
#scale/slider is changed but do not know what
figure, ax = plt.subplots()
ax.imshow(array)
canvas.get_tk_widget().pack()
root = tkinter.Tk()
root.title("Something")
array = ([[1,2,3,4],
[3,9,1,5],
[8,4,1,7],
[2,4,9,1]])
figure, ax = plt.subplots()
ax.imshow(array)
canvas = plt.Figure()
canvas = FigureCanvasTkAgg(figure, root)
canvas.get_tk_widget().pack()
mySlider = tkinter.Scale(root, from_=0, to=15, orient=HORIZONTAL, command=myFunc)
mySlider.pack()
Like this:
import tkinter as tk
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
def myFunc(value):
print (mySlider.get())
array[1][2]=mySlider.get()
im.set_array(array)
canvas.draw()
root = tk.Tk()
root.title("Something")
array = ([[1,2,3,4],
[3,9,1,5],
[8,4,1,7],
[2,4,9,1]])
figure, ax = plt.subplots()
im = ax.imshow(array)
canvas = FigureCanvasTkAgg(figure, root)
canvas.get_tk_widget().pack()
mySlider = tk.Scale(root, from_=0, to=15, orient=tk.HORIZONTAL, command=myFunc)
mySlider.pack()
root.mainloop()
However tkinter is not needed here. matplotlib has a slider built in (I assume you know since you imported it) which is a lot easier to implement:
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider
def myFunc(value):
array[1][2]=value
im.set_array(array)
array = ([[1,2,3,4],
[3,9,1,5],
[8,4,1,7],
[2,4,9,1]])
figure, ax = plt.subplots()
im = ax.imshow(array)
ax_slider = plt.axes([0.1, 0.1, 0.8, 0.03]) # [left, bottom, width, height]
slide = Slider(ax_slider, '', 0, 15, valinit=0)
slide.on_changed(myFunc)
plt.show()
So I am saving a lot of plots.
Olde code:
import matplotlib.pyplot as plt
for args in lots_of_things_to_make:
fig = plt.figure()
do_the_fancy_graphing(fig, *args)
fig.savefig(out_path)
plt.close()
other parts of my code are using Tkinter so I can not use pyplot.
New code:
import matplotlib
matplotlib.use('TkAgg')
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
for args in lots_of_things_to_make:
fig = Figure()
do_the_fancy_graphing(fig, *args)
canvas = FigureCanvasTkAgg(fig, master=root)
fig.savefig(out_path)
this leads to _tkinter.TclError: not enough free memory for image buffer
How can I close a Figure when using tk backend?
FigureCanvasTkAgg does not have a destroy method. So I tried:
for args in lots_of_things_to_make:
fig = Figure()
frame = Frame(root)
do_the_fancy_graphing(fig, *args)
canvas = FigureCanvasTkAgg(fig, master=frame)
fig.savefig(out_path)
frame.destroy()
but no luck, turns out FigureCanvasTkAgg.__init__ binds to the toplevel it is placed in, so:
for args in lots_of_things_to_make:
fig = Figure()
top = Toplevel(root)
do_the_fancy_graphing(fig, *args)
canvas = FigureCanvasTkAgg(fig, master=top)
fig.savefig(out_path)
top.destroy()
Seems to be working for me.
I have embedded my matplotlib plot in tkinter according to http://matplotlib.org/examples/user_interfaces/embedding_in_tk2.html successfully, but I also want to save my plot. How do I do this? I tried figure.savefig (as below); I thought this would work from looking at http://matplotlib.org/api/figure_api.html:
#!/usr/bin/env python
import matplotlib
matplotlib.use('TkAgg')
from numpy import arange, sin, pi
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
from matplotlib.figure import Figure
import sys
if sys.version_info[0] < 3:
import Tkinter as Tk
else:
import tkinter as Tk
def destroy(e): sys.exit()
root = Tk.Tk()
root.wm_title("Embedding in TK")
#root.bind("<Destroy>", destroy)
f = Figure(figsize=(5,4), dpi=100)
a = f.add_subplot(111)
t = arange(0.0,3.0,0.01)
s = sin(2*pi*t)
a.plot(t,s)
a.set_title('Tk embedding')
a.set_xlabel('X axis label')
a.set_ylabel('Y label')
f.savefig('hello.png')
# a tk.DrawingArea
canvas = FigureCanvasTkAgg(f, master=root)
canvas.show()
canvas.get_tk_widget().pack(side=Tk.TOP, fill=Tk.BOTH, expand=1)
#toolbar = NavigationToolbar2TkAgg( canvas, root )
#toolbar.update()
canvas._tkcanvas.pack(side=Tk.TOP, fill=Tk.BOTH, expand=1)
button = Tk.Button(master=root, text='Quit', command=sys.exit)
button.pack(side=Tk.BOTTOM)
Tk.mainloop()
The Figure needs a FigureCanvas to draw on. In other words, don't call savefig until after the FigureCanvas has been established:
# a tk.DrawingArea
canvas = FigureCanvasTkAgg(f, master=root)
canvas.show()
canvas.get_tk_widget().pack(side=Tk.TOP, fill=Tk.BOTH, expand=1)
f.savefig('hello.png')
I have a simple graph with a Navigation Tool Bar. When I zoom or pan, the graph is correctly updated, but the axis labels get messed up. Its as if it does not clear the old text out before drawing the new text. So you see the new text written over the old. If I resize the window, it seems to do a full re-draw and fixes the labels. Here is my simple example:
import matplotlib
matplotlib.use('TkAgg')
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
from matplotlib.backend_bases import key_press_handler
from matplotlib.figure import Figure
from Tix import *
from Tkconstants import *
root = Tk()
f = Figure(figsize=(12,5), dpi=100, frameon=False)
s = f.add_subplot(111, title="test")
x = [0,1,2,3,4,5]
y = [5,2,6,7,3,6]
s.plot(x,y,label="Test")
canvas = FigureCanvasTkAgg(f, master=root)
canvas.show()
canvas.get_tk_widget().pack(side=TOP, fill=BOTH, expand=1)
canvas._tkcanvas.pack(side=TOP, fill=BOTH, expand=1)
NavigationToolbar2TkAgg(canvas, root)
root.mainloop()
Here is a before:
Here is after panning:
As suggested by Oblivion:
Removing the frameon=False option from f = Figure(figsize=(12,5), dpi=100, frameon=False) solved the issue.
For a demonstration of a Graph algorithm i need to draw a networkx graph to a Tkinter Canvas and be able to modify that graph (and the plot) at runtime.
I have pieced together the following code (I hope it is the minimal code leading to my problem, but I'm new to this so I'm not sure):
import matplotlib
matplotlib.use('TkAgg')
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import matplotlib.pyplot as plt
import Tkinter as Tk
import networkx as nx
from tkMessageBox import showinfo
root = Tk.Tk()
root.wm_title("Animated Graph embedded in TK")
root.wm_protocol('WM_DELETE_WINDOW', root.quit())
f = plt.figure(figsize=(5,4))
a = f.add_subplot(111)
plt.axis('off')
# the networkx part
G=nx.complete_graph(5)
nx.draw_networkx(G,pos=nx.spring_layout(G),ax=a)
# a tk.DrawingArea
canvas = FigureCanvasTkAgg(f, master=root)
canvas.show()
canvas.get_tk_widget().pack(side=Tk.TOP, fill=Tk.BOTH, expand=1)
def next_graph():
if G.order():
a.cla()
G.remove_node(G.nodes()[-1])
nx.draw(G, pos=nx.circular_layout(G), ax=a)
canvas.draw()
b = Tk.Button(root, text="next",command=next_graph)
b.pack()
Tk.mainloop()
My problem now is this:
The first display of the graph is like I want it (backgroundcolor-wise), but after you first click 'Next' the backgroundcolor of the graph changes to white. I have tried changing the background color of the figure and the canvas.
I don't even know what brings that change about, I think it is simply drawing to the same canvas twice.
How can I modify the code to have the graph always have the same background color?
On an unrelated note: the root.quit() I added does not help in ending the application properly. This might be stupid on my side, but what did go wrong here?
I think you are very close. If you use nx.draw_networkx() in your event loop then it works (turn off the axis there too).
Here is your example with those modifications and also with a single layout computed at the beginning that is reused in the loop:
import matplotlib
matplotlib.use('TkAgg')
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import matplotlib.pyplot as plt
import Tkinter as Tk
import networkx as nx
from tkMessageBox import showinfo
root = Tk.Tk()
root.wm_title("Animated Graph embedded in TK")
# Quit when the window is done
root.wm_protocol('WM_DELETE_WINDOW', root.quit)
f = plt.figure(figsize=(5,4))
a = f.add_subplot(111)
plt.axis('off')
# the networkx part
G=nx.complete_graph(5)
pos=nx.circular_layout(G)
nx.draw_networkx(G,pos=pos,ax=a)
xlim=a.get_xlim()
ylim=a.get_ylim()
# a tk.DrawingArea
canvas = FigureCanvasTkAgg(f, master=root)
canvas.show()
canvas.get_tk_widget().pack(side=Tk.TOP, fill=Tk.BOTH, expand=1)
def next_graph():
if G.order():
a.cla()
G.remove_node(G.nodes()[-1])
nx.draw_networkx(G, pos, ax=a)
a.set_xlim(xlim)
a.set_ylim(ylim)
plt.axis('off')
canvas.draw()
b = Tk.Button(root, text="next",command=next_graph)
b.pack()
Tk.mainloop()