Preserve zoom settings in interactive navigation of matplotlib figure - python

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()

Related

Matplotlib FuncAnimation Created twice - duplicate when embbeded in tkinter

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.

Embedding an interactive Matplotlib plot into Tkinter?

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()

Use Matplotlib Plot and Widgets in function and return user input

My problem is the following:
I create a Matplotlib figure including some widget sliders and a button to close the figure. This works. What can I do to use this code inside a function which returns e.g. the values of the slides AFTER clicking the "close figure" button?
Here is the code (im3d is a 3d image numpy array):
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider, Button
class IndexTracker(object):
def __init__(self, ax, data3d, title):
self.ax = ax
ax.set_title(title)
self.data3d = data3d
rows, cols, self.slices = data3d.shape
self.ind = self.slices//2
self.im = ax.imshow(self.data3d[:, :, self.ind])
self.update()
def update(self):
self.im.set_data(self.data3d[:, :, self.ind])
ax.set_ylabel('slice %s' % self.ind)
self.im.axes.figure.canvas.draw()
#
fig = plt.figure(figsize=(18, 8), dpi=80, facecolor='w', edgecolor='b')
ax = fig.add_subplot(1,2,1)
ax2 = fig.add_subplot(1,2,2)
tracker1 = IndexTracker(ax, im3d, 'Select First Image')
tracker2 = IndexTracker(ax2, im3d, 'Select Last Image')
def slider_changed(value, tracker):
numb = int(round(value))
tracker.ind = numb
tracker.update()
max0 = im3d.shape[2] -1
ax_start = fig.add_axes([0.1, 0.85, 0.35, 0.03])
sl_start = Slider(ax_start, 'START', 0, max0, valinit=0, valfmt="%i")
ax_end = fig.add_axes([0.6, 0.85, 0.35, 0.03])
sl_end = Slider(ax_end, 'END', 0, max0, valinit=0, valfmt="%i")
def sl_start_changed(val):
slider_changed(sl_start.val,tracker1)
def sl_end_changed(val):
slider_changed(sl_end.val,tracker2)
sl_start.on_changed(sl_start_changed)
sl_end.on_changed(sl_end_changed)
class Index(object):
def close_figure(self, event):
plt.close(fig)
callback = Index()
ax_button = fig.add_axes([0.7, 0.06, 0.15, 0.075])
button = Button(ax_button, 'DONE')
button.on_clicked(callback.close_figure)
fig.canvas.manager.window.raise_()
plt.plot()
My first idea was to run a while loop after plt.plot(), something like this:
while not_done:
time.sleep(0.5)
and change not_done to False inside the function close_figure. But in this case, the plot doesn't show.
The slider is still available after the figure is closed. Hence you can just access its val attribute after closing the figure
fig, ax = plt.subplots()
slider = Slider(...)
# .. callbacks
plt.show() # you may use plt.show(block=True) when this is run in interactive mode
print(slider.val)
Edit after clarifications:
You are running spyder and use the IPython console within. This has several implications.
You need to turn interactive mode off, plt.ioff()
You need to deactivate "support" for matplotlib
Then the following code runs fine and only prints the values after the figure is closed.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider, Button
plt.ioff()
im3d = np.random.rand(20,20,10)
class IndexTracker(object):
def __init__(self, ax, data3d, title):
self.ax = ax
ax.set_title(title)
self.data3d = data3d
rows, cols, self.slices = data3d.shape
self.ind = self.slices//2
self.im = ax.imshow(self.data3d[:, :, self.ind])
self.update()
def update(self):
self.im.set_data(self.data3d[:, :, self.ind])
ax.set_ylabel('slice %s' % self.ind)
self.im.axes.figure.canvas.draw()
#
fig = plt.figure(figsize=(18, 8), dpi=80, facecolor='w', edgecolor='b')
ax = fig.add_subplot(1,2,1)
ax2 = fig.add_subplot(1,2,2)
tracker1 = IndexTracker(ax, im3d, 'Select First Image')
tracker2 = IndexTracker(ax2, im3d, 'Select Last Image')
def slider_changed(value, tracker):
numb = int(round(value))
tracker.ind = numb
tracker.update()
max0 = im3d.shape[2] -1
ax_start = fig.add_axes([0.1, 0.85, 0.35, 0.03])
sl_start = Slider(ax_start, 'START', 0, max0, valinit=0, valfmt="%i")
ax_end = fig.add_axes([0.6, 0.85, 0.35, 0.03])
sl_end = Slider(ax_end, 'END', 0, max0, valinit=0, valfmt="%i")
def sl_start_changed(val):
slider_changed(sl_start.val,tracker1)
def sl_end_changed(val):
slider_changed(sl_end.val,tracker2)
sl_start.on_changed(sl_start_changed)
sl_end.on_changed(sl_end_changed)
class Index(object):
def close_figure(self, event):
plt.close(fig)
callback = Index()
ax_button = fig.add_axes([0.7, 0.06, 0.15, 0.075])
button = Button(ax_button, 'DONE')
button.on_clicked(callback.close_figure)
fig.canvas.manager.window.raise_()
plt.show()
print(sl_start.val, sl_end.val)
Alternatively, you may just run the complete code in external command line, without the need for plt.ioff()

python matplotlib how to open an image with class

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()

Dynamically update multiple axis in matplotlib

I want to display sensor data on a PyQT GUI with a matplotlib animation.
I already have a working Plot which gets updates every time I receive new sensor value from an external source with this code:
def __init__(self):
self.fig = Figure(figsize=(width, height), dpi=dpi)
self.axes = self.fig.add_subplot(111)
self.axes.grid()
self.xdata = []
self.ydata = []
self.entry_limit = 50
self.line, = self.axes.plot([0], [0], 'r')
def update_figure_with_new_value(self, xval: float, yval: float):
self.xdata.append(xval)
self.ydata.append(yval)
if len(self.xdata) > self.entry_limit:
self.xdata.pop(0)
self.ydata.pop(0)
self.line.set_data(self.xdata, self.ydata)
self.axes.relim()
self.axes.autoscale_view()
self.fig.canvas.draw()
self.fig.canvas.flush_events()
I want now to extend the plot to show another data series with the same x-axis. I tried to achieve this with the following additions to the init-code above:
self.axes2 = self.axes.twinx()
self.y2data = []
self.line2, = self.axes2.plot([0], [0], 'b')
and in the update_figure_with_new_value() function (for test purpose I just tried to add 1 to yval, I will extend the params of the function later):
self.y2data.append(yval+1)
if len(self.y2data) > self.entry_limit:
self.y2data.pop(0)
self.line2.set_data(self.xdata, self.ydata)
self.axes2.relim()
self.axes2.autoscale_view()
But instead of getting two lines in the plot which should have the exact same movement but just shifted by one I get vertical lines for the second plot axis (blue). The first axis (red) remains unchanged and is ok.
How can I use matplotlib to update multiple axis so that they display the right values?
I'm using python 3.4.0 with matplotlib 2.0.0.
Since there is no minimal example available, it's hard to tell the reason for this undesired behaviour. In principle ax.relim() and ax.autoscale_view() should do what you need.
So here is a complete example which works fine and updates both scales when being run with python 2.7, matplotlib 2.0 and PyQt4:
import numpy as np
import matplotlib.pyplot as plt
from PyQt4 import QtGui, QtCore
from matplotlib.figure import Figure
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt4agg import NavigationToolbar2QT as NavigationToolbar
class Window(QtGui.QMainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self)
self.widget = QtGui.QWidget()
self.setCentralWidget(self.widget)
self.widget.setLayout(QtGui.QVBoxLayout())
self.widget.layout().setContentsMargins(0,0,0,0)
self.widget.layout().setSpacing(0)
self.fig = Figure(figsize=(5,4), dpi=100)
self.axes = self.fig.add_subplot(111)
self.axes.grid()
self.xdata = [0]
self.ydata = [0]
self.entry_limit = 50
self.line, = self.axes.plot([], [], 'r', lw=3)
self.axes2 = self.axes.twinx()
self.y2data = [0]
self.line2, = self.axes2.plot([], [], 'b')
self.canvas = FigureCanvas(self.fig)
self.canvas.draw()
self.nav = NavigationToolbar(self.canvas, self.widget)
self.widget.layout().addWidget(self.nav)
self.widget.layout().addWidget(self.canvas)
self.show()
self.ctimer = QtCore.QTimer()
self.ctimer.timeout.connect(self.update)
self.ctimer.start(150)
def update(self):
y = np.random.rand(1)
self.update_figure_with_new_value(self.xdata[-1]+1,y)
def update_figure_with_new_value(self, xval,yval):
self.xdata.append(xval)
self.ydata.append(yval)
if len(self.xdata) > self.entry_limit:
self.xdata.pop(0)
self.ydata.pop(0)
self.y2data.pop(0)
self.line.set_data(self.xdata, self.ydata)
self.axes.relim()
self.axes.autoscale_view()
self.y2data.append(yval+np.random.rand(1)*0.17)
self.line2.set_data(self.xdata, self.y2data)
self.axes2.relim()
self.axes2.autoscale_view()
self.fig.canvas.draw()
self.fig.canvas.flush_events()
if __name__ == "__main__":
qapp = QtGui.QApplication([])
a = Window()
exit(qapp.exec_())
You may want to test this and report back if it is working or not.

Categories

Resources