Matplotlib figure not updating on data change - python

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

Related

Jupyter Notebook: duplicated scatter plot using when using ipywidgets

I'm trying to control the display of a scatter plot with a checkbox. When I built it using the interact function it worked as expected. The plot was shown or hidden based on the value in the checkbox.
import matplotlib.pyplot as plt
from ipywidgets import interact, widgets
%matplotlib inline
def on_change(Display):
if Display == True:
plt.scatter(x,y)
plt.show()
return Display
interact(on_change, Display=False);
When I tried to do the same thing using the observe function every time I clicked on the checkbox I get an additional plot displayed below. What do I need to do to get it to redraw the same plot so it works like the example above?
I suppose something in the interact example is clearing the display but it's not clear how to do this manually.
import matplotlib.pyplot as plt
from ipywidgets import interact, widgets
%matplotlib inline
x = [1,2,3,4,5,6,7,8]
y = [5,2,4,2,1,4,5,2]
def on_change(change):
if change['new'] == True:
scat = plt.scatter(x,y)
plt.show()
cb = widgets.Checkbox(False, description = "Display")
cb.observe(on_change, names='value')
display(cb)
A couple of alterations I made to your example to hopefully demonstrate what you want. I have taken a more object-oriented route, not sure if you specifically wanted to avoid it but it helps achieve your desired outcome, it seems like you are moving towards a simple GUI here.
1) Include an Output widget (out) - basically a cell output which you can display like a normal widget. You can use a context manager block (with out:) when you want to print to that specific output widget. You can also clear the widget with out.clear_output()
2) Use the object oriented interface in matplotlib rather than using plt. I find this easier to control which plots are displayed and in which location at the right times.
temporarily suspend the interactive matplotlib with plt.ioff()
Create your figure and axis with fig, ax = plt.subplots(). NB figures can have multiple axes/subplots but we only need one.
'plot' the scatter data to your axis using ax.scatter(x,y), but this won't cause it to appear.
Explicitly display the figure with display(fig).
I'm assuming you want your figure to be replotted each time you check the box, so I have included it in the observe function. If your figure doesn't change, it would make sense to move it outside of the loop.
import matplotlib.pyplot as plt
from ipywidgets import interact, widgets
%matplotlib inline
out = widgets.Output()
x = [1,2,3,4,5,6,7,8]
y = [5,2,4,2,1,4,5,2]
def on_change(change):
if change['new'] == True:
with out:
plt.ioff()
fig,ax = plt.subplots()
ax.scatter(x,y)
display(fig)
else:
out.clear_output()
cb = widgets.Checkbox(False, description = "Display")
cb.observe(on_change, names='value')
display(cb)
display(out)

Update matplotlib image in a function

I've got a loop that's processing images and I want, on every 100th iteration (say) to display the image in a single output window using matplotlib. So I'm trying to write a function which will take a numpy tensor as input and display the corresponding image.
Here's what I have which isn't working:
def display(image):
global im
# If im has been initialized, update it with the current image; otherwise initialize im and update with current image.
try:
im
im.set_array(image)
plt.draw()
except NameError:
im = plt.imshow(image, cmap=plt.get_cmap('gray'), vmin=0, vmax=255)
plt.show(block=False)
plt.draw()
I was trying to pass it through FuncAnimation at first, but that seems designed to have the animation call a function to do the update, rather than having a function call on matplotlib to display the result.
The code above opens a window but it doesn't seem to update. Can anyone point me in the right direction here?
Many thanks,
Justin
Maybe you can use a combination of:
fig.canvas.draw_idle()
and
plt.pause()
The first one will re-draw your figure, while the second one will call the GUI event loop to update the figure.
Also you don't need to call imshow all the times, it's sufficient to call the "set_data" method on your "im" object. Something like that should work:
import matplotlib.pyplot as plt
import numpy
fig,ax = plt.subplots(1,1)
image = numpy.array([[1,1,1], [2,2,2], [3,3,3]])
im = ax.imshow(image)
while True:
image = numpy.multiply(1.1, image)
im.set_data(image)
fig.canvas.draw_idle()
plt.pause(1)
This was adapted from this answer. Hope it helps.

Efficient way to update matplotlib figure in gui?

My application is receiving data over the network at about 30fps, and needs to update a horizontal bar chart dynamically based on this new data.
I am using a matplotlib figure inside a tkinter window for this purpose. Profiling my code has shown that a major bottleneck in my code is the updating of this figure.
A simplified version of the code is given below:
def update_bars(self):
"""
Updates a horizontal bar chart
"""
for bar, new_d in zip(self.bars, self.latest_data):
bar.set_width(new_d)
self.figure.draw()
The lag I am experiencing is significant, and grows quickly over time. Is there a more efficient way to update the matplotlib figure? Any help would be great.
EDIT: I will be looking at this for possible speedup tips. I'll update if I get something working.
You can update the data of the plot objects. But to some extent, you can't change the shape of the plot, you can manually reset the x and y axis limits.
e.g.
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0, 6*np.pi, 100)
y = np.sin(x)
plt.ion()
fig = plt.figure()
ax = fig.add_subplot(111)
line1, = ax.plot(x, y)
for phase in np.linspace(0, 10*np.pi, 500):
line1.set_ydata(np.sin(x + phase))
# render the figure
# re-draw itself the next time
# some GUI backends add this to the GUI frameworks event loop.
fig.canvas.draw()
fig.canvas.flush_events() # flush the GUI events
flush_events
Flush the GUI events for the figure. Implemented only for backends
with GUIs.
flush_events make sure that the GUI framework has a chance to run its event loop and clear any GUI events.Sometimes this needs to be in a try/except block because the default implementation of this method is to raise NotImplementedError.
draw will render the figure,in the above code,maybe remove draw still work.But to some extent they're different.

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!

How can I show figures separately in matplotlib?

Say that I have two figures in matplotlib, with one plot per figure:
import matplotlib.pyplot as plt
f1 = plt.figure()
plt.plot(range(0,10))
f2 = plt.figure()
plt.plot(range(10,20))
Then I show both in one shot
plt.show()
Is there a way to show them separately, i.e. to show just f1?
Or better: how can I manage the figures separately like in the following 'wishful' code (that doesn't work):
f1 = plt.figure()
f1.plot(range(0,10))
f1.show()
Sure. Add an Axes using add_subplot. (Edited import.) (Edited show.)
import matplotlib.pyplot as plt
f1 = plt.figure()
f2 = plt.figure()
ax1 = f1.add_subplot(111)
ax1.plot(range(0,10))
ax2 = f2.add_subplot(111)
ax2.plot(range(10,20))
plt.show()
Alternatively, use add_axes.
ax1 = f1.add_axes([0.1,0.1,0.8,0.8])
ax1.plot(range(0,10))
ax2 = f2.add_axes([0.1,0.1,0.8,0.8])
ax2.plot(range(10,20))
With Matplotlib prior to version 1.0.1, show() should only be called once per program, even if it seems to work within certain environments (some backends, on some platforms, etc.).
The relevant drawing function is actually draw():
import matplotlib.pyplot as plt
plt.plot(range(10)) # Creates the plot. No need to save the current figure.
plt.draw() # Draws, but does not block
raw_input() # This shows the first figure "separately" (by waiting for "enter").
plt.figure() # New window, if needed. No need to save it, as pyplot uses the concept of current figure
plt.plot(range(10, 20))
plt.draw()
# raw_input() # If you need to wait here too...
# (...)
# Only at the end of your program:
plt.show() # blocks
It is important to recognize that show() is an infinite loop, designed to handle events in the various figures (resize, etc.). Note that in principle, the calls to draw() are optional if you call matplotlib.ion() at the beginning of your script (I have seen this fail on some platforms and backends, though).
I don't think that Matplotlib offers a mechanism for creating a figure and optionally displaying it; this means that all figures created with figure() will be displayed. If you only need to sequentially display separate figures (either in the same window or not), you can do like in the above code.
Now, the above solution might be sufficient in simple cases, and for some Matplotlib backends. Some backends are nice enough to let you interact with the first figure even though you have not called show(). But, as far as I understand, they do not have to be nice. The most robust approach would be to launch each figure drawing in a separate thread, with a final show() in each thread. I believe that this is essentially what IPython does.
The above code should be sufficient most of the time.
PS: now, with Matplotlib version 1.0.1+, show() can be called multiple times (with most backends).
I think I am a bit late to the party but...
In my opinion, what you need is the object oriented API of matplotlib. In matplotlib 1.4.2 and using IPython 2.4.1 with Qt4Agg backend, I can do the following:
import matplotlib.pyplot as plt
fig, ax = plt.subplots(1) # Creates figure fig and add an axes, ax.
fig2, ax2 = plt.subplots(1) # Another figure
ax.plot(range(20)) #Add a straight line to the axes of the first figure.
ax2.plot(range(100)) #Add a straight line to the axes of the first figure.
fig.show() #Only shows figure 1 and removes it from the "current" stack.
fig2.show() #Only shows figure 2 and removes it from the "current" stack.
plt.show() #Does not show anything, because there is nothing in the "current" stack.
fig.show() # Shows figure 1 again. You can show it as many times as you want.
In this case plt.show() shows anything in the "current" stack. You can specify figure.show() ONLY if you are using a GUI backend (e.g. Qt4Agg). Otherwise, I think you will need to really dig down into the guts of matplotlib to monkeypatch a solution.
Remember that most (all?) plt.* functions are just shortcuts and aliases for figure and axes methods. They are very useful for sequential programing, but you will find blocking walls very soon if you plan to use them in a more complex way.
Perhaps you need to read about interactive usage of Matplotlib. However, if you are going to build an app, you should be using the API and embedding the figures in the windows of your chosen GUI toolkit (see examples/embedding_in_tk.py, etc).
None of the above solutions seems to work in my case, with matplotlib 3.1.0 and Python 3.7.3. Either both the figures show up on calling show() or none show up in different answers posted above.
Building upon #Ivan's answer, and taking hint from here, the following seemed to work well for me:
import matplotlib.pyplot as plt
fig, ax = plt.subplots(1) # Creates figure fig and add an axes, ax.
fig2, ax2 = plt.subplots(1) # Another figure
ax.plot(range(20)) #Add a straight line to the axes of the first figure.
ax2.plot(range(100)) #Add a straight line to the axes of the first figure.
# plt.close(fig) # For not showing fig
plt.close(fig2) # For not showing fig2
plt.show()
As #arpanmangal, the solutions above do not work for me (matplotlib 3.0.3, python 3.5.2).
It seems that using .show() in a figure, e.g., figure.show(), is not recommended, because this method does not manage a GUI event loop and therefore the figure is just shown briefly. (See figure.show() documentation). However, I do not find any another way to show only a figure.
In my solution I get to prevent the figure for instantly closing by using click events. We do not have to close the figure — closing the figure deletes it.
I present two options:
- waitforbuttonpress(timeout=-1) will close the figure window when clicking on the figure, so we cannot use some window functions like zooming.
- ginput(n=-1,show_clicks=False) will wait until we close the window, but it releases an error :-.
Example:
import matplotlib.pyplot as plt
fig1, ax1 = plt.subplots(1) # Creates figure fig1 and add an axes, ax1
fig2, ax2 = plt.subplots(1) # Another figure fig2 and add an axes, ax2
ax1.plot(range(20),c='red') #Add a red straight line to the axes of fig1.
ax2.plot(range(100),c='blue') #Add a blue straight line to the axes of fig2.
#Option1: This command will hold the window of fig2 open until you click on the figure
fig2.waitforbuttonpress(timeout=-1) #Alternatively, use fig1
#Option2: This command will hold the window open until you close the window, but
#it releases an error.
#fig2.ginput(n=-1,show_clicks=False) #Alternatively, use fig1
#We show only fig2
fig2.show() #Alternatively, use fig1
As of November 2020, in order to show one figure at a time, the following works:
import matplotlib.pyplot as plt
f1, ax1 = plt.subplots()
ax1.plot(range(0,10))
f1.show()
input("Close the figure and press a key to continue")
f2, ax2 = plt.subplots()
ax2.plot(range(10,20))
f2.show()
input("Close the figure and press a key to continue")
The call to input() prevents the figure from opening and closing immediately.

Categories