I'm pretty new with matplotlib and I tried to write a class to open and close image through matplotlib, here is the code:
import matplotlib
matplotlib.use("TkAgg")
import matplotlib.pyplot as plt
class ptlShow():
def __init__(self, file, pos):
plt.rcParams['toolbar'] = 'None'
fig, ax = plt.subplots(figsize=(1, 1.4))
fig.subplots_adjust(0, 0, 1, 1)
ax.axis("off")
im = plt.imread(file)
ax.imshow(im)
fig.canvas.manager.window.overrideredirect(1)
plt.get_current_fig_manager().window.wm_geometry(pos)#
plt.show()
def close(self):
plt.close
a = ptlShow('1.jpg', '+700+100')
b = ptlShow('2.jpg', '+500+100')
a.close()
b.close()
but finally I have only one instance of image and close doesn't work, what I'm doing wrong ! Thanks!
plt.show() is meant to be called exactly once at the end of the script as it takes over the event loop. Code coming after that is not executed until all figures are closed.
You would probably want to close a figure by clicking on it, so you can register the close method to a button_press_event. Note that plt.close is just the function - you would want to call it: plt.close().
import matplotlib
matplotlib.use("TkAgg")
import matplotlib.pyplot as plt
class ptlShow():
def __init__(self, file, pos):
plt.rcParams['toolbar'] = 'None'
self.fig, ax = plt.subplots(figsize=(1, 1.4))
self.fig.subplots_adjust(0, 0, 1, 1)
ax.axis("off")
im = plt.imread(file)
ax.imshow(im)
self.fig.canvas.manager.window.overrideredirect(1)
self.fig.canvas.manager.window.wm_geometry(pos)#
self.fig.canvas.mpl_connect("button_press_event", self.close)
def close(self, event=None):
plt.close(self.fig)
a = ptlShow('1.jpg', '+700+100')
b = ptlShow('2.jpg', '+500+100')
plt.show()
In order to close the window after some time t, you can use tkinters .after method, .after(t, func):
import matplotlib
matplotlib.use("TkAgg")
import matplotlib.pyplot as plt
class ptlShow():
def __init__(self, file, pos):
plt.rcParams['toolbar'] = 'None'
self.fig, ax = plt.subplots(figsize=(1, 1.4))
self.fig.subplots_adjust(0, 0, 1, 1)
ax.axis("off")
im = plt.imread(file)
ax.imshow(im)
self.fig.canvas.manager.window.overrideredirect(1)
self.fig.canvas.manager.window.wm_geometry(pos)#
self.fig.canvas.mpl_connect("button_press_event", self.close)
self.fig.canvas.mpl_connect("draw_event", self.delayed_close)
def delayed_close(self,event=None):
self.fig.canvas.manager.window.after(1000, self.close)
def close(self, event=None):
plt.close(self.fig)
a = ptlShow('house.png', '+700+100')
b = ptlShow('house.png', '+500+100')
plt.show()
Related
I have a troubleing bug that i just could not understands it's origin. Several days of attempts and still no luck.
I'm trying to create a line cursor that correspond to played audio with FuncAnimation and for some reason, the animation is created twice ONLY when the callback (line_select_callback) that activates the function is triggered from RectangleSelector widget after drawing wiith the mouse. when I use a standard TK button to activate the SAME function (line_select_callback), it operates well.
some debugging code with reevant prints is present.
I've created minimal working example.
My guess is it has something to do with the figure that is not attached to the tk window, and is silently activated in addition to the embedded figure, I'm not really sure.
Any help will be very much appreciated, Thanks! :)
import os
import threading
import tkinter as tk
from matplotlib.backends.backend_tkagg import (
FigureCanvasTkAgg)
from matplotlib.widgets import RectangleSelector
import numpy as np
import matplotlib.pyplot as plt
import matplotlib
from matplotlib import animation
class LineAnimation:
def __init__(self, fig, ax):
print(' enter LineAnimation ctor')
# Parameters
self.ax = ax
self.fig = fig
self.xdata, self.ydata = [], []
self.ln, = plt.plot([], [], 'ro')
# Print figures list
figures = [manager.canvas.figure
for manager in matplotlib._pylab_helpers.Gcf.get_all_fig_managers()]
print('figures BEFORE animation: ', figures)
self.animation = animation.FuncAnimation(fig=self.fig,
func=self.update,
init_func=self.init,
frames=np.linspace(0, 2 * np.pi, 128),
interval=25,
blit=True, repeat=False,
cache_frame_data=False)
self.fig.canvas.draw()
# Print figures list
figures = [manager.canvas.figure
for manager in matplotlib._pylab_helpers.Gcf.get_all_fig_managers()]
print('figures AFTER animation: ', figures, '\n')
def init(self):
# Prints for debugging
print('\nenter init animate')
print('Thread id: ', threading.get_ident())
print('Process id: ', os.getpid(), '\n')
# Init
self.ax.set_xlim(0, 2*np.pi)
self.ax.set_ylim(-1, 1)
return self.ln,
def update(self, frame):
self.xdata.append(frame)
self.ydata.append(np.sin(frame))
self.ln.set_data(self.xdata, self.ydata)
return self.ln,
class Example:
def __init__(self):
# init window
self.root = tk.Tk(className=' Species segmentation')
self.fig, self.ax = plt.subplots()
# init sine audio file
self.fs = 44100
self.dur = 2
self.freq = 440
self.x = np.sin(2*np.pi*np.arange(self.fs*self.dur)*self.freq/self.fs)
# plt.ion()
# Embedd in tk
self.canvas = FigureCanvasTkAgg(self.fig, master=self.root) # A tk.DrawingArea.
self.canvas.draw()
self.canvas.get_tk_widget().grid()
# Plot something
self.N = 100000
self.xp = np.linspace(0, 10, self.N)
self.ax.plot(self.xp, np.sin(2*np.pi*self.xp))
self.ax.set_title(
"Plot for demonstration purpuse")
# init Rectangle Selector
self.RS = RectangleSelector(self.ax, self.line_select_callback,
drawtype='box', useblit=True,
button=[1, 3], # avoid using middle button
minspanx=5, minspany=5,
spancoords='pixels', interactive=True,
rectprops={'facecolor': 'yellow', 'edgecolor': 'black', 'alpha': 0.15, 'fill': True})
self.canvas.draw()
# plt.show()
tk.mainloop()
def line_select_callback(self, eclick, erelease):
print('enter line_select_callback')
self.anim = LineAnimation(
self.fig,
self.ax)
self.fig.canvas.draw()
# plt.show()
Example()
I managed to isolate the cause for this issue: The presence of the
rectangle selector (which uses blitting) and the use of animation (which also uses blitting) on the same axes.
I've managed to create the animation properly, but only when I disabled the rectangle selector
self.RS.set_active(False)
self.RS.update()
self.canvas.flush_events()
and removed his artists (i needed to do that manually in my code) using:
for a in self.RS.artists:
a.set_visible(False)
after that, The animation worked properly.
I have code for an interactive plot, which allows viewing a 3D image through scrolling slice-wise with the mouse roll. It includes also a slide bar to adjust contrast.
I have been trying to embed this into a Tkinter GUI, for example with help of this sample code: https://matplotlib.org/examples/user_interfaces/embedding_in_tk.html
But I don't really understand where my code is supposed to go in there.
This is the application I currently have:
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.widgets import Slider
class IndexTracker(object):
def __init__(self, ax, X):
self.ax = ax
ax.set_title('use scroll wheel to navigate images')
self.X = X
rows, cols, self.slices = X.shape
self.ind = self.slices//2
self.im = ax.imshow(self.X[:, :, self.ind], cmap='gray')
self.update()
def onscroll(self, event):
print("%s %s" % (event.button, event.step))
if event.button == 'up':
self.ind = (self.ind + 1) % self.slices
else:
self.ind = (self.ind - 1) % self.slices
self.update()
def contrast(self, event):
print('Changing contrast')
print(smax.val)
self.im.set_clim([0,smax.val])
self.update()
def update(self):
self.im.set_data(self.X[:, :, self.ind])
self.ax.set_ylabel('slice %s' % self.ind)
self.im.axes.figure.canvas.draw()
##### Create some random volumetric data
im = np.array(np.random.rand(10,10,10))
##### Initialize Tracker object with the data and Slider
fig, ax = plt.subplots(1,1)
axmax = fig.add_axes([0.25, 0.01, 0.65, 0.03])
smax = Slider(axmax, 'Max', 0, np.max(im), valinit=50)
tracker = IndexTracker(ax, im)
fig.canvas.mpl_connect('scroll_event', tracker.onscroll)
smax.on_changed(tracker.contrast)
plt.show()
I don't understand what is exacty that I need to embed into the Tkinter application, is it fig, or IndexTracker ? How do I replace fig.canvas.mpl_connect('scroll_event', tracker.onscroll) so it works within the TKinter GUI?
There is nothing special embedding this into tkinter - you create a FigureCanvasTkAgg object first, and then do the rest. The only thing you need to change is instead of plt, you need to use Figure which is shown in the sample you quoted.
import numpy as np
from matplotlib.widgets import Slider
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
class IndexTracker(object):
...
import tkinter as tk
root = tk.Tk()
fig = Figure()
canvas = FigureCanvasTkAgg(fig, root)
canvas.get_tk_widget().pack(fill="both", expand=True)
im = np.array(np.random.rand(10,10,10))
ax = fig.subplots(1,1)
axmax = fig.add_axes([0.25, 0.01, 0.65, 0.03])
smax = Slider(axmax, 'Max', 0, np.max(im), valinit=50)
tracker = IndexTracker(ax, im)
canvas.mpl_connect('scroll_event', tracker.onscroll)
canvas.mpl_connect('button_release_event', tracker.contrast) #add this for contrast change
root.mainloop()
I'm writing a script using tkinter and matplotlib for data processing, some parts of the code requires polygon selector to choose a region of interest. However, PolygonSelector fails to detect the motion of cursor.
It should be noted that this issue occurs when the interactive mode of matplotlib figure is on.
Simplified code and result are shown below:
#!/usr/bin/env python3
import matplotlib
matplotlib.use("TkAgg")
import tkinter as tk
import matplotlib.pyplot as plt
from matplotlib.widgets import PolygonSelector
root = tk.Tk()
def draw():
fig = plt.figure()
ax = fig.add_subplot(111)
plt.ion() # interactive mode is on
plt.show()
def onselect(data_input):
print(data_input)
PS = PolygonSelector(ax, onselect)
tk.Button(root, text='draw', command=draw).pack()
root.mainloop()
This is the plot after clicking 'draw' button on tkinter GUI, the starting point of polygon stucks at (0,0), it is expected to move with cursor:
When I call draw() outside of tkinter, PolygonSelector works fine:
def draw():
fig = plt.figure()
ax = fig.add_subplot(111)
plt.ion() # interactive mode is on
plt.show()
def onselect(data_input):
print(data_input)
PS = PolygonSelector(ax, onselect)
a = input() # prevent window from closing when execution is done
draw()
The simple solution would be to make sure you make your Polygon Selector a global variable. This will keep the selector visually updating.
#!/usr/bin/env python3
import tkinter as tk
import matplotlib
import matplotlib.pyplot as plt
from matplotlib.widgets import PolygonSelector
matplotlib.use("TkAgg")
root = tk.Tk()
ps = None
def draw():
global ps
fig = plt.figure()
ax = fig.add_subplot(111)
plt.ion()
plt.show()
ps = PolygonSelector(ax, on_select)
def on_select(data_input):
print(data_input)
tk.Button(root, text='draw', command=draw).pack()
root.mainloop()
If you build this into a class then you can avoid the use of global and get the behavior you want by apply the Polygon Selector as a class attribute.
#!/usr/bin/env python3
import tkinter as tk
import matplotlib
import matplotlib.pyplot as plt
from matplotlib.widgets import PolygonSelector
matplotlib.use("TkAgg")
class GUI(tk.Tk):
def __init__(self):
super().__init__()
self.ps = None
tk.Button(self, text='draw', command=self.draw).pack()
def draw(self):
fig = plt.figure()
ax = fig.add_subplot(111)
plt.ion()
plt.show()
self.ps = PolygonSelector(ax, self.on_select)
def on_select(self, data_input):
print(data_input)
if __name__ == "__main__":
GUI().mainloop()
Results:
I am a newbie into wx python. The following is the code to plot live graph from a text file which can be updated live. Can anybody please help me to embed this code into a wx frame. I desperately need it for my project.
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import time
fig= plt.figure()
ax1=fig.add_subplot(1,1,1)
def animate(i):
pullData= open('C:/test/e.txt','r').read()
dataArray= pullData.split('\n')
xar=[]
yar=[]
for eachLine in dataArray:
if len(eachLine)>1:
x,y= eachLine.split(',')
xar.append(int(x))
yar.append(int(y))
ax1.clear()
ax1.plot(xar,yar)
ani= animation.FuncAnimation(fig,animate, interval=1000)
plt.show()
Here I'll give you an example but you need to change the plotting part for your needs:
import wx
import numpy as np
import matplotlib.figure as mfigure
import matplotlib.animation as manim
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg
class MyFrame(wx.Frame):
def __init__(self):
super(MyFrame,self).__init__(None, wx.ID_ANY, size=(800, 600))
self.fig = mfigure.Figure()
self.ax = self.fig.add_subplot(111)
self.canv = FigureCanvasWxAgg(self, wx.ID_ANY, self.fig)
self.values = []
self.animator = manim.FuncAnimation(self.fig,self.anim, interval=1000)
def anim(self,i):
if i%10 == 0:
self.values = []
else:
self.values.append(np.random.rand())
self.ax.clear()
self.ax.set_xlim([0,10])
self.ax.set_ylim([0,1])
return self.ax.plot(np.arange(1,i%10+1),self.values,'d-')
wxa = wx.PySimpleApp()
w = MyFrame()
w.Show(True)
wxa.MainLoop()
Is there a way to preserve the interactive navigation settings of a figure such that the next time the figure is updated the Zoom/Pan characteristics don't go back to the default values? To be more specific, if a zoom in a figure, and then I update the plot, is it possible to make the new figure appear with the same zoom settings of the previous one? I am using Tkinter.
You need to update the image instead of making a new image each time. As an example:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Button
class DummyPlot(object):
def __init__(self):
self.imsize = (10, 10)
self.data = np.random.random(self.imsize)
self.fig, self.ax = plt.subplots()
self.im = self.ax.imshow(self.data)
buttonax = self.fig.add_axes([0.45, 0.9, 0.1, 0.075])
self.button = Button(buttonax, 'Update')
self.button.on_clicked(self.update)
def update(self, event):
self.data += np.random.random(self.imsize) - 0.5
self.im.set_data(self.data)
self.im.set_clim([self.data.min(), self.data.max()])
self.fig.canvas.draw()
def show(self):
plt.show()
p = DummyPlot()
p.show()
If you want to plot the data for the first time when you hit "update", one work-around is to plot dummy data first and make it invisible.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Button
class DummyPlot(object):
def __init__(self):
self.imsize = (10, 10)
self.data = np.random.random(self.imsize)
self.fig, self.ax = plt.subplots()
dummy_data = np.zeros(self.imsize)
self.im = self.ax.imshow(dummy_data)
self.im.set_visible(False)
buttonax = self.fig.add_axes([0.45, 0.9, 0.1, 0.075])
self.button = Button(buttonax, 'Update')
self.button.on_clicked(self.update)
def update(self, event):
self.im.set_visible(True)
self.data += np.random.random(self.imsize) - 0.5
self.im.set_data(self.data)
self.im.set_clim([self.data.min(), self.data.max()])
self.fig.canvas.draw()
def show(self):
plt.show()
p = DummyPlot()
p.show()
Alternately, you could just turn auto-scaling off, and create a new image each time. This will be significantly slower, though.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Button
class DummyPlot(object):
def __init__(self):
self.imsize = (10, 10)
self.fig, self.ax = plt.subplots()
self.ax.axis([-0.5, self.imsize[1] - 0.5,
self.imsize[0] - 0.5, -0.5])
self.ax.set_aspect(1.0)
self.ax.autoscale(False)
buttonax = self.fig.add_axes([0.45, 0.9, 0.1, 0.075])
self.button = Button(buttonax, 'Update')
self.button.on_clicked(self.update)
def update(self, event):
self.ax.imshow(np.random.random(self.imsize))
self.fig.canvas.draw()
def show(self):
plt.show()
p = DummyPlot()
p.show()