Creating a matplotlib interactive plotting window for an existing figure - python

I am writing a program that fits curves to large sets of xy coordinate data. It is often helpful to watch the progress of the algorithm by plotting and displaying each iteration as the fitting progresses. I'm using matplotlib for plotting.
What I'd like to do is create the figure in the main thread, then pass it into a child thread that displays it. That way I have access to all the figure's methods and attributes in the main thread. I can plot by calling fig.gca().plot() and draw by calling fig.canvas.draw().
I can't figure out how to create an interactive plotting window that shows only the figure I pass to it. Right now I'm using matplotlib.pyplot.show(), which does display my figure, but it also displays any other figures that may have been defined in the program. Is there an object oriented way to create an interactive window for a specific figure? I am looking for a solution that does not rely on unsupported interfaces in matplotlib.
Here is a post that's similar, but it still doesn't answer my question: Interactive figure with OO Matplotlib
I've never understood why matplotlib always seems to use current objects (current figure, current axes, etc.) rather than specific objects (for example, why not have matplotlib.pyplot.show(fig) rather than just show()?) I think I'm missing something. If anyone could shed some light on why matplotlib is designed this way, or how I'm misunderstanding and/or misusing it, that would also be appreciated.
Here's my code:
import matplotlib.pyplot
import threading
import time
class Plotter():
def __init__(self,fig):
t = threading.Thread(target=self.PlottingThread,args=(fig,))
t.start()
def PlottingThread(self,fig):
#This line shows fig1 AND fig2 from below. I want it to show fig ONLY.
matplotlib.pyplot.show()
if __name__ == "__main__":
fig1 = matplotlib.pyplot.figure()
fig2 = matplotlib.pyplot.figure()
Plotter(fig1)
fig1.gca().clear()
fig1.gca().plot([1,2,3])
fig1.canvas.draw()

I think I got it:
import Tkinter
import threading
import matplotlib.backends.backend_tkagg
root = Tkinter.Tk()
class Plotter():
def __init__(self,fig):
t = threading.Thread(target=self.PlottingThread,args=(fig,))
t.start()
def PlottingThread(self,fig):
canvas = matplotlib.backends.backend_tkagg.FigureCanvasTkAgg(fig, master=root)
canvas.show()
canvas.get_tk_widget().pack(side=Tkinter.TOP, fill=Tkinter.BOTH, expand=1)
toolbar = matplotlib.backends.backend_tkagg.NavigationToolbar2TkAgg(canvas, root)
toolbar.update()
canvas._tkcanvas.pack(side=Tkinter.TOP, fill=Tkinter.BOTH, expand=1)
Tkinter.mainloop()
if __name__ == "__main__":
import time
fig1 = matplotlib.figure.Figure(figsize=(5,4), dpi=100)
fig1.gca().plot([1,2,3])
fig2 = matplotlib.figure.Figure(figsize=(5,4), dpi=100)
fig2.gca().plot([3,2,1])
#Shows fig1 and not fig2, just like it's supposed to
Plotter(fig1)
time.sleep(1)
#I can still plot to fig1 from my main thread
fig1.gca().clear()
fig1.gca().plot([5,2,7])
fig1.canvas.draw()
The only thing is if you try to create two instances of Plotter the whole thing crashes. That isn't too important for my application, but it probably means I'm using Tkinter wrong. Suggestions/corrections are welcome.

Related

Matplotlib: how to show a figure that has been closed

I have a function which returns a Figure created with pyplot. This function closes the figure before returning it. If I didn't close it, showing it would be very easy with just plt.show(), but let us assume I cannot do that.
I can easily save the returned Figure to a file, but I cannot find the way to display it (i.e.: have a popped window showing the figure).
from matplotlib import pyplot as plt
def new_figure():
fig = plt.figure()
plt.plot([0, 1], [2, 3])
plt.close(fig)
return fig
fig = new_figure()
fig.savefig('output.svg')
fig.show()
How could I show the figure?
When plt.close is called on a figure instance, what is actually destroyed is the graphical interface (the FigureManager) that is used to show the figure on-screen (see comment by JoeKington at Matplotlib: re-open a closed figure?). So the figure instance still exists and has not been destroyed. To show the figure on-screen again, we would have to reconstruct, in some way, an interface to replace the one that has been destroyed when calling plt.close(fig).
This can be done by simply creating a new figure with plt.figure(), "stealing" its manager, and use it to display the figure that we want to show on-screen. Alternatively, it is possible to reconstruct manually an interface to display the figure with a GUI Toolkit. I provide an example with PySide using the Qt4Agg backend. Moreover, there is a nice example that shows how this can be done with Tkinter (TkAgg) here : http://matplotlib.org/examples/user_interfaces/embedding_in_tk.html (I've tested this approach also and it works).
Dummy figure approach:
This solution is based on how to close a show() window but keep the figure alive? and Obtaining the figure manager via the OO interface in Matplotlib. The GUI toolkit that is used to construct the graphical interface for showing the figure on-screen depends on the backend that is used by matplotlib. If the backend used is TkAgg, TkInter will give some warning in Python 2.7 that can be ignored (see this post on python bug tracker).
import matplotlib.pyplot as plt
def new_figure():
fig = plt.figure()
plt.plot([0, 1], [2, 3])
plt.close(fig)
return fig
def show_figure(fig):
# create a dummy figure and use its
# manager to display "fig"
dummy = plt.figure()
new_manager = dummy.canvas.manager
new_manager.canvas.figure = fig
fig.set_canvas(new_manager.canvas)
if __name__ == '__main__':
fig = new_figure()
show_figure(fig)
plt.show()
Pyside approach:
This consists in reconstructing a GUI with a new canvas and toolbar to display the fig instance on-screen.
Important Note: The code below must be executed in a new dedicated Python console (press F6) if run from Spyder, since Spyder is also a Qt application that starts it's own QApplication (see PySide Qt script doesn't launch from Spyder but works from shell).
import matplotlib
matplotlib.use('Qt4Agg')
matplotlib.rcParams['backend.qt4']='PySide'
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg
from matplotlib.backends.backend_qt4agg import NavigationToolbar2QT
import matplotlib.pyplot as plt
from PySide import QtGui
import sys
def new_figure():
fig = plt.figure()
plt.plot([0, 1], [2, 3])
plt.close(fig)
return fig
class myFigCanvas(QtGui.QWidget):
def __init__(self, fig, parent=None):
super(myFigCanvas, self).__init__(parent)
#---- create new canvas and toolbar --
canvas = FigureCanvasQTAgg(fig)
toolbar = NavigationToolbar2QT(canvas, self)
#---- setup layout of GUI ----
grid = QtGui.QGridLayout()
grid.addWidget(canvas, 0, 0)
grid.addWidget(toolbar, 1, 0)
self.setLayout(grid)
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
fig = new_figure()
new_canvas = myFigCanvas(fig)
new_canvas.show()
sys.exit(app.exec_())
which results in:

Why does matplotlib.figure.Figure behave so different than matplotlib.pyplot.figure

A fellow programmer alerted me to a problem where matplotlib.pyplot and Tkinter don't behave well together, as demonstrated by this question Tkinter/Matplotlib backend conflict causes infinite mainloop
We changed our code to prevent potential problems as mentioned in the linked question, as follows:
Old
import matplotlib.pyplot as plt
self.fig = plt.figure(figsize=(8,6))
if os.path.isfile('./UI.png'):
image = plt.imread('./UI.png')
plt.axis('off')
plt.tight_layout()
im = plt.imshow(image)
# The Canvas
self.canvas = FigureCanvasTkAgg(self.fig, master = master)
self.toolbar = NavigationToolbar2TkAgg(self.canvas, root)
self.canvas.get_tk_widget().pack(fill=BOTH,expand=YES)
self.canvas.draw()
Intermediate (UI.png not being shown)
import matplotlib.pyplot as plt
import matplotlib
self.fig = matplotlib.figure.Figure(figsize=(8, 6))
if os.path.isfile('./UI.png'):
image = matplotlib.image.imread('./UI.png')
plt.axis('off')
plt.tight_layout()
plt.imshow(image)
# The Canvas
self.canvas = FigureCanvasTkAgg(self.fig, master=master)
self.toolbar = NavigationToolbar2TkAgg(self.canvas, root)
self.canvas.get_tk_widget().pack(fill=BOTH, expand=YES)
self.canvas.draw()
The changed code did not display the 'background' image anymore and I have been mostly just trying random things (as I am quite lost in the difference between the two options) to get the figure displaying again. The changes involved switching from tight_layout to set_tight_layout to avoid a warning, as mentioned on https://github.com/matplotlib/matplotlib/issues/1852 . The resulting code is as follows:
Potential Fix
import matplotlib.pyplot as plt
import matplotlib
self.fig = matplotlib.figure.Figure(figsize=(8, 6))
background_image = self.fig.add_subplot(111)
if os.path.isfile('./UI.png'):
image = matplotlib.image.imread('./UI.png')
background_image.axis('off')
#self.fig.tight_layout() # This throws a warning and falls back to Agg renderer, 'avoided' by using the line below this one.
self.fig.set_tight_layout(True)
background_image.imshow(image)
# The Canvas
self.canvas = FigureCanvasTkAgg(self.fig, master=master)
self.toolbar = NavigationToolbar2TkAgg(self.canvas, root)
self.canvas.get_tk_widget().pack(fill=BOTH, expand=YES)
self.canvas.draw()
The question therefore is, why do we need to use a subplot now (using matplotlib.figure.Figure) while before we did not (using matplotlib.pyplot)?
PS: I am sorry if this is a silly question but almost everything that I can find on the subject seems to use the matplotlib.pyplot variant. Therefore, I am having trouble finding any good documentation for the matplotlib.figure.Figure variant.
TL;DR
The question therefore is, why do we need to use a subplot now (using matplotlib.figure.Figure) while before we did not (using matplotlib.pyplot)?
subplot creates an Axes object. You did have one before, but the pyplot API "hid" it from you under the covers so you didn't realise it. You are now trying to use the objects directly, so have to handle it yourself.
More detailed reason
The reason you see this behaviour is because of how matplotlib.pyplot works. To quote the tutorial a little:
matplotlib.pyplot is a collection of command style functions that make matplotlib work like MATLAB.... matplotlib.pyplot is stateful, in that it keeps track of the current figure and plotting area, and the plotting functions are directed to the current axes
The crucial bit is that pyplot is stateful. It is keeping track of state "under the covers" and hiding the object model from you to some extent. It also does some implicit things. So - if you simply call, e.g., plt.axis(), under the covers pyplot calls plt.gca() and that in turn calls gcf() which will return a new figure, because you haven't set up a figure via pyplot yet. This is true for most calls to plt.some_function() - if pyplot doesn't have a figure object in it's own state yet, it will create one.
So, in your intermediate example, you've created your own Figure object - fiven it a name self.fig (I'm not sure what your class structure is, so I don't know what self is, but I'm guessing it's your tk.Frame object or something similar).
The punchline
pyplot doesn't know anything about self.fig. So in your intermediate code, you're calling imshow() on the Figure object in pyplot state, but displaying a different figure (self.fig) on your canvas.
The problem is not that you need to use subplot as such, but that you need to change the background image on the correct Figure object. The way you've used subplot in your potential fix code will do that - although I suggest an alternative below which maybe makes the intent clearer.
How to fix
Change
plt.axis('off')
plt.tight_layout()
plt.imshow(image)
to
self.fig.set_tight_layout(True)
ax = self.fig.gca() # You could use subplot here to get an Axes object instead
ax.axis('off')
ax.imshow(image)
A note on root cause: pyplot API vs direct use of objects
This a bit of an opinion, but might help. I tend to use the pyplot interface when I need to quickly get things prototyped and want to use one of the fairly standard cases. Often, that is enough.
As soon as I need to do more complicated things, I start to use the object model directly - maintaining my own named Figure and Axes objects etc.
Mixing the two is possible, but often confusing. You've found this with your intermediate solution. So I recommend doing one or the other.

Matplotlib figure not updating on data change

I'm implementing an image viewer using matplotlib. The idea is that changes being made to the image (such as filter application) will update automatically.
I create a Figure to show the inital image and have added a button using pyQt to update the data. The data does change, I have checked, but the Figure does not. However, if after I've pressed the filter application button, I move the image using matplotlib's standard tool bar, the image is then updated.
I assume I'm doing something wrong when updating the image, but since the fact of moving it actually forces the update, it then shows the data change. I would like for this to happen when I press the button, though.
Below is some of the code. This is the initial figure initialization, which shows the original image:
self.observableFig = Figure((4.0, 4.0), dpi=100)
self.canvas = FigureCanvas(self.observableFig)
self.canvas.setParent(self.observableWindow)
self.canvas.setFocusPolicy(Qt.StrongFocus)
self.canvas.setFocus()
self.canvas.mpl_connect('button_press_event', self.on_click)
# Showing initial data on Window
self.observableFig.clear()
self.observableAxes = self.observableFig.add_subplot(1, 1, 1)
min, max = self.min, self.max
self.observableAxes.imshow(
self.data,
vmin=min,
vmax=max,
origin='lower'
)
And this is the event for when the button that changes the data is pressed:
self.observableAxes.imshow(self.data/2, origin='lower')
# plt.clf()
# plt.draw()
# plt.show()
I have tried draw(), show(), basically anything I've found on pyplot about this. I have also tried both with and without plt.ion() at the beginning, but it hasn't made a difference in this.
Thanks in advance.
The reason that nothing is updating is that you're trying to use pyplot methods for a figure that's not a part of the pyplot state machine. plt.draw() won't draw this figure, as plt doesn't know the figure exists.
Use fig.canvas.draw() instead.
Regardless, it's better to use fig.canvas.draw() that plt.draw(), as it's clear which figure you're drawing (the former draws one, the latter draws all, but only if they're tracked by pyplot).
Try something along these lines:
import numpy as np
import matplotlib.pyplot as plt
data = np.random.random((10,10))
# To make a standalone example, I'm skipping initializing the
# `Figure` and `FigureCanvas` and using `plt.figure()` instead...
# `plt.draw()` would work for this figure, but the rest is identical.
fig, ax = plt.subplots()
ax.set(title='Click to update the data')
im = ax.imshow(data)
def update(event):
im.set_data(np.random.random((10,10)))
fig.canvas.draw()
fig.canvas.mpl_connect('button_press_event', update)
plt.show()

Tkinter keeps creating embedded plots instead of updating the current plot

I am aware that there are couple of questions on this around the web but unfortunately none of those helped me with this. My relevant matplotlib imports are:
import matplotlib
matplotlib.use("TkAgg")
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
Below is how I call for new plots:
self.myFig1 = Figure(figsize = (10, 4), tight_layout = True)
self.myPlot1 = self.myFig1.add_subplot(111)
self.myPlot1.plot([blah],[blah])
canvas1 = FigureCanvasTkAgg(self.myFig1, self.myFrame)
canvas1.show()
canvas1.get_tk_widget().grid()
I have a refresh button that sort of keeps calling the function that ends up creating this plot, but I need one embedded plot which is updated after every button click, not recreated. I have tried things like Figure.close(), Figure.clf() but none worked. I'd appreciate your help with this.
Additional info: The reason it keeps creating a new plot is because I keep .grid-ing it over and over again. So I deleted the .grid() part of the code and I tried something like this below which did not work:
self.myFig1.clf()
self.myFig1 = Figure(figsize = (10, 4), tight_layout = True)
self.myPlot1 = self.myFig1.add_subplot(111)
self.myPlot1.plot([blah],[blah])
canvas1 = FigureCanvasTkAgg(self.myFig1, self.myFrame)
canvas1.draw()
This just destroys the figure, canvas, everything and doesn't plot anything.
self.myFig.clear()
self.myPlot = self.myFig.add_subplot(111, **self.nadalkwArgs)
self.myPlot.plot([series1], [series2])
self.myFig.canvas.draw()
Is what solved my problem. Your "refresh plot" button or method should have this in order to keep the canvas, clear the old plot, make the new plot and keep plot style elements such as xlabel, ylabel etc. So basically, first you clear the figure(not the subplot, self.myPlot.clear() would clear the plot but you can't have your kwargs that way), and then you recreate the subplot with kwargs and then you plot, and finally you .canvas.draw()
There is no need to recreate a new canvas or fig.
After grid your FigureCanvasTkAgg, you can reset line in button callback only by:
self.myPlot1.set_data(xdata,ydata)
self.myFig.canvas.draw()

Plot not updaing correctly using draw() (duplicated axes) in wxPython

I am new to Python and wxPython I am trying to simulate particles in a box with random velocities in random directions.
I created a simple GUI in wxFormBuilder where I have a panel to show a plot of the paricles. The particles are set to a position and plotted onto the panel, then I start the simulation and update the x and y positons of the particles. When redrawing the positions The axes appear 'thicker' as before, it looks like if there are several axes ontop of eachother.
I cant find anything about this problem, I hope somebody could help me with this?
This is the code that creates the Plot:
import wx
import particles
import random
import matplotlib
matplotlib.use('WXAgg')
from matplotlib.figure import Figure
from matplotlib.backends.backend_wxagg import \
FigureCanvasWxAgg as FigCanvas, \
NavigationToolbar2WxAgg as NavigationToolbar
matplotlib.rcParams.update({'font.size': 8})
class MyFrameSub( particles.GUI_MainFrame ):
def __init__( self, parent ):
particles.GUI_MainFrame.__init__( self, parent )
def InitData(self):
self.npart = int(self.m_npart.GetValue())
self.nsteps = int(self.m_steps.GetValue())
self.ndt = float(self.m_dt.GetValue())
self.x= [random.random() for I in range(self.npart)]
self.y= [2*random.random()-1 for I in range(self.npart)]
self.vx= [self.ndt*(2*random.random()-1) for I in range(self.npart)]
self.vy= [self.ndt*(2*random.random()-1) for I in range(self.npart)]
return
def CreatePlot(self):
panelsize = self.m_PlotPanel.GetClientSize()
self.figure = Figure(figsize=(panelsize[0]/100.0,panelsize[1]/100.0), dpi=100, frameon=False)
self.canvas = FigCanvas(self.m_PlotPanel, wx.ID_ANY, self.figure)
self.axes = self.figure.add_subplot(111)
self.axes.axis((-1,1,-1,1))
self.partplot, = self.axes.plot(self.x, self.y, 'ro')
self.canvas.draw()
return
def UpdateData(self):
for i in range(self.nsteps):
for j in range(self.npart):
self.x[j]=self.x[j]+self.vx[j]
self.y[j]=self.y[j]+self.vy[j]
if abs(self.x[j])>1:
self.vx[j]=-self.vx[j]
if abs(self.y[j])>1:
self.vy[j]=-self.vy[j]
self.partplot.set_xdata(self.x)
self.partplot.set_ydata(self.y)
self.canvas.draw()
return
followed by the button definitions, it looks like this:
Before running the simulation: www.merlinvs.de/before.jpg
and after running the simulation: www.merlinvs.de/after.jpg
As you see the axes got ugly and I have no idea why.
Another question I was thinking about is the following:
When I run a loop that takes a while the UI is unresponsive, is it possible to have the UI active to cancel a loop if desired?
As for the unresponsive UI, I used greenlets for my Matplotlib stuff while updating it
from gevent.greenlet import Greenlet
from gevent import sleep
import matplotlib.plot as plt
# Plot stuff
def event_handler():
# Can update plot
sleep(1) # Simulate handling or number crunching (numpy WILL block)
g = Greenlet(event_handler)
g.start()
plt.plot(0,0) # Works immediately and updates
Some things of note is that for serious applications you need to add some protection against race coditions with the plot. Numpy and external science libraries typically cause the entire application to become unresponsive (in my experience) because they are blocking system calls outside of the greenlet context switcher's reach. For something simple though the above pattern works well.

Categories