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

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.

Related

Cancelling displaying a Matplotlib plot [duplicate]

Matplotlib offers these functions:
cla() # Clear axis
clf() # Clear figure
close() # Close a figure window
When should I use each function and what exactly does it do?
They all do different things, since matplotlib uses a hierarchical order in which a figure window contains a figure which may consist of many axes. Additionally, there are functions from the pyplot interface and there are methods on the Figure class. I will discuss both cases below.
pyplot interface
pyplot is a module that collects a couple of functions that allow matplotlib to be used in a functional manner. I here assume that pyplot has been imported as import matplotlib.pyplot as plt.
In this case, there are three different commands that remove stuff:
See matplotlib.pyplot Functions:
plt.cla() clears an axis, i.e. the currently active axis in the current figure. It leaves the other axes untouched.
plt.clf() clears the entire current figure with all its axes, but leaves the window opened, such that it may be reused for other plots.
plt.close() closes a window, which will be the current window, if not specified otherwise.
Which functions suits you best depends thus on your use-case.
The close() function furthermore allows one to specify which window should be closed. The argument can either be a number or name given to a window when it was created using figure(number_or_name) or it can be a figure instance fig obtained, i.e., usingfig = figure(). If no argument is given to close(), the currently active window will be closed. Furthermore, there is the syntax close('all'), which closes all figures.
methods of the Figure class
Additionally, the Figure class provides methods for clearing figures.
I'll assume in the following that fig is an instance of a Figure:
fig.clf() clears the entire figure. This call is equivalent to plt.clf() only if fig is the current figure.
fig.clear() is a synonym for fig.clf()
Note that even del fig will not close the associated figure window. As far as I know the only way to close a figure window is using plt.close(fig) as described above.
There is just a caveat that I discovered today.
If you have a function that is calling a plot a lot of times you better use plt.close(fig) instead of fig.clf() somehow the first does not accumulate in memory. In short if memory is a concern use plt.close(fig) (Although it seems that there are better ways, go to the end of this comment for relevant links).
So the the following script will produce an empty list:
for i in range(5):
fig = plot_figure()
plt.close(fig)
# This returns a list with all figure numbers available
print(plt.get_fignums())
Whereas this one will produce a list with five figures on it.
for i in range(5):
fig = plot_figure()
fig.clf()
# This returns a list with all figure numbers available
print(plt.get_fignums())
From the documentation above is not clear to me what is the difference between closing a figure and closing a window. Maybe that will clarify.
If you want to try a complete script there you have:
import numpy as np
import matplotlib.pyplot as plt
x = np.arange(1000)
y = np.sin(x)
for i in range(5):
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
ax.plot(x, y)
plt.close(fig)
print(plt.get_fignums())
for i in range(5):
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
ax.plot(x, y)
fig.clf()
print(plt.get_fignums())
If memory is a concern somebody already posted a work-around in SO see:
Create a figure that is reference counted
plt.cla() means clear current axis
plt.clf() means clear current figure
also, there's plt.gca() (get current axis) and plt.gcf() (get current figure)
Read more here: Matplotlib, Pyplot, Pylab etc: What's the difference between these and when to use each?

Python matploblib memory usage and speed up options? [duplicate]

It seems that the standard way of creating a figure in matplotlib doesn't behave as I'd expect in python: by default calling fig = matplotlib.figure() in a loop will hold on to all the figures created, and eventually run out of memory.
There are quite a few posts which deal with workarounds, but requiring explicit calls to matplotlib.pyplot.close(fig) seems a bit hackish. What I'd like is a simple way to make fig reference counted, so I won't have to worry about memory leaks. Is there some way to do this?
If you create the figure without using plt.figure, then it should be reference counted as you expect. For example (This is using the non-interactive Agg backend, as well.)
from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas
from matplotlib.figure import Figure
# The pylab figure manager will be bypassed in this instance.
# This means that `fig` will be garbage collected as you'd expect.
fig = Figure()
canvas = FigureCanvas(fig)
ax = fig.add_subplot(111)
If you're only going to be saving figures rather than displaying them, you can use:
def savefig(*args, **kwargs):
plt.savefig(*args, **kwargs)
plt.close(plt.gcf())
This is arguably no less hacky, but whatever.
If you want to profit from the use of pyplot and have the possibility to wrap the figure in a class of your own, you can use the __del__ method of your class to close the figure.
Something like:
import matplotlib.pyplot as plt
class MyFigure:
"""This is my class that just wraps a matplotlib figure"""
def __init__(self):
# Get a new figure using pyplot
self.figure = plt.figure()
def __del__(self):
# This object is getting deleted, close its figure
plt.close(self.figure)
Whenever the garbage collector decides to delete your figure because it is inaccessible, the matplotlib figure will be closed.
However note that if someone has grabbed the figure (but not your wrapper) they might be annoyed that you closed it. It probably can be solved with some more thought or imposing some restrictions on usage.

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

Animating a Quadmesh from pcolormesh with matplotlib

As a result of a full day of trial and error, I'm posting my findings as a help to anyone else who may come across this problem.
For the last couple days, I've been trying to simulate a real-time plot of some radar data from a netCDF file to work with a GUI I'm building for a school project. The first thing I tried was a simple redrawing of the data using the 'interactive mode' of matplotlib, as follows:
import matplotlib.pylab as plt
fig = plt.figure()
plt.ion() #Interactive mode on
for i in range(2,155): #Set to the number of rows in your quadmesh, start at 2 for overlap
plt.hold(True)
print i
#Please note: To use this example you must compute X, Y, and C previously.
#Here I take a slice of the data I'm plotting - if this were a real-time
#plot, you would insert the new data to be plotted here.
temp = plt.pcolormesh(X[i-2:i], Y[i-2:i], C[i-2:i])
plt.draw()
plt.pause(.001) #You must use plt.pause or the figure will freeze
plt.hold(False)
plt.ioff() #Interactive mode off
While this technically works, it also disables the zoom functions, as well as pan, and well, everything!
For a radar display plot, this was unacceptable. See my solution to this below.
So I started looking into the matplotlib animation API, hoping to find a solution. The animation did turn out to be exactly what I was looking for, although its use with a QuadMesh object in slices was not exactly documented. This is what I eventually came up with:
import matplotlib.pylab as plt
from matplotlib import animation
fig = plt.figure()
plt.hold(True)
#We need to prime the pump, so to speak and create a quadmesh for plt to work with
plt.pcolormesh(X[0:1], Y[0:1], C[0:1])
anim = animation.FuncAnimation(fig, animate, frames = range(2,155), blit = False)
plt.show()
plt.hold(False)
def animate( self, i):
plt.title('Ray: %.2f'%i)
#This is where new data is inserted into the plot.
plt.pcolormesh(X[i-2:i], Y[i-2:i], C[i-2:i])
Note that blit must be False! Otherwise it will yell at you about a QuadMesh object not being 'iterable'.
I don't have access to the radar yet, so I haven't been able to test this against live data streams, but for a static file, it has worked great thus far. While the data is being plotted, I can zoom and pan with the animation.
Good luck with your own animation/plotting ambitions!

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