Matplotlib with PyQt - axes.clear() is very slow - python

I just want to plot a chart on my program. I need to use axes.clear() to draw a new chart for many times.
from PyQt4 import QtGui
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
class MplCanvas(FigureCanvas):
def __init__(self):
self.fig = Figure()
self.axes = self.fig.add_subplot(111)
# do something...
FigureCanvas.__init__(self, self.fig)
FigureCanvas.setSizePolicy(self, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding)
FigureCanvas.updateGeometry(self)
# do something...
def refresh(self):
# FIXME: This method is very, very slow!!!
self.axes.clear()
# do something...
But it is very slow, It will hang my program for about 0.3s. Is it normal?

I know this is a rather old question but for all who are looking at this (like me) and still search a solution:
class MplCanvas(FigureCanvas):
def __init__(self):
#...
# get a reference to the line you want to plot
self.line = self.axes.plot(xdata, ydata)
def resfresh(self, xdata, ydata):
# set the new data on one or more lines
self.line.set_data(xdata, ydata)
# redraw the axis (and re-limit them)
axes.relim()
axes.autoscale_view()
# redraw the figure
self.fig.canvas.draw()
I am using this with multiple subplots and multiple axes in one figure, this speeds up the process for me already quite a lot. I can't go further with stripping down my code because my plots are resizing the axis and the title is changing.
If you know that you only want the data to be repainted so the axis, the title and the range will stay the same you can strip this down even more:
def refresh(self, xdata, ydata):
# "delete" the background contents, this is only repainting an empty
# background which will look a little bit differently but I'm
# fine with it
self.axes.draw_artist(self.axes.patch)
# set the new data, could be multiple lines too
self.line.set_data(xdata, ydata)
self.axes.draw_artist(self.line)
# update figure
self.fig.canvas.update()
self.fig.canvas.flush_events()
I took a lot of information from here. If you still want to improve the speed there may be a chance by using blitting but in the link posted before there is written:
As it turns out, fig.canvas.blit(ax.bbox) is a bad idea since it leaks memory like crazy. What you should use instead is fig.canvas.update(), which is equally fast but does not leak memory.
I was also checking out the matplotlib.animation module but I have some buttons triggering the repaint (just like asked in the question) and I could not find out how to use the animations in my specific case.

Related

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

`matplotlib`: what is the purpose of an artist's animated state?

Artists in matplotlib have methods to set/get their animated state (a boolean). I can't seem to find documentation to explain the purpose of the "animated state" variable. Can you explain, or point me to an appropriate resource?
I'm not sure it's thoroughly documented anywhere, but the animated state of an artist controls whether or not it's included when drawing the plot.
If animated is True, then the artist will not be drawn when fig.draw() is called. Instead, it will only be drawn when you manually call draw_artist(artist_with_animated_set). This allows for simplification of blitting functions.
Note: This does not apply to all backends! I think it applies to almost all interactive backends, but it doesn't apply to non-interactive backends. It's intended to be used in combination with blitting, so backends that don't support blitting don't support the animated flag.
For example, if we do something similar to this:
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
ax.plot(range(10), animated=True)
plt.show()
We'll get a blank plot -- the line will not be drawn. (Note: If you save this figure, the line will show up. See the caveat about non-interactive backends above. Matplotlib temporarily switches to a non interactive backend to save a figure.)
To get a sense of why this is useful, suppose you were making an animation or interactive gui (and weren't using the new animation framework). You'll want to use blitting to make the animation appear "smooth".
However, whenever the figure is resized, etc, the animation's background will need to be updated. The best way to handle this is to connect a callback to the draw event. Without the animated flag, you'll have to redraw the plot inside your draw callback, which will cause an infinite loop. (The workaround is to disconnect and reconnect the draw event callback, but that's a bit of a pain.)
At any rate, the animated flag simplifies this process quite a bit. For example, you might use the animated flag in something similar to the following:
import numpy as np
import matplotlib.pyplot as plt
class AnimatedSinWave(object):
def __init__(self, speed=0.1, interval=25):
self.fig, self.ax = plt.subplots()
self.x = np.linspace(0, 6 * np.pi, 200)
self.i, self.speed = 0.0, speed
self.line, = self.ax.plot(self.x, np.sin(self.x), animated=True)
self.ax.set_title('Try resizing the figure window')
self.fig.canvas.mpl_connect('draw_event', self.update_background)
self.t = self.fig.canvas.new_timer(interval, [(self.update, [], {})])
self.t.start()
def update_background(self, event):
self._background = self.fig.canvas.copy_from_bbox(self.ax.bbox)
def update(self):
self.fig.canvas.restore_region(self._background)
self.line.set_ydata(np.sin(self.i * self.speed + self.x))
self.ax.draw_artist(self.line)
self.i += 1
self.fig.canvas.blit(self.ax.bbox)
def show(self):
plt.show()
AnimatedSinWave().show()
Note: For various reasons, you'll have some odd things happen on the OSX and qt*Agg backends. If the background is black when the window first pops up, move or focus the window, and it should fix itself.
At any rate, without the animated flag (or the newer animation framework), that example becomes much more complex.

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.

Creating a matplotlib interactive plotting window for an existing figure

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.

Categories