How to animate a time-ordered sequence of matplotlib plots - python

I want to plot a sequence of .png images in matplotlib. The goal is to plot them rapidly to simulate the effect of a movie, but I have additional reasons for wanting to avoid actually creating an .avi file or saving matplotlib figures and then viewing them in sequence outside of Python.
I'm specifically trying to view the image files in sequence inside a for-loop in Python. Assuming I have imported matplotlib correctly, and I have my own functions 'new_image()' and 'new_rect()', here's some example code that fails to work because of the blocking effect of the show() function's call to the GUI mainloop:
for index in index_list:
img = new_image(index)
rect = new_rect(index)
plt.imshow(img)
plt.gca().add_patch(rect)
plt.show()
#I also tried pausing briefly and then closing, but this doesn't
#get executed due to the GUI mainloop from show()
time.sleep(0.25)
plt.close()
The above code works to show only the first image, but then the program just hangs and waits for me to manually close the resultant figure window. Once I do close it, the program then just hangs and doesn't re-plot with the new image data. What should I be doing? Also note that I have tried replacing the plt.show() command with a plt.draw() command, and then adding the plt.show() outside of the for-loop. This doesn't display anything and just hangs.

Based on http://matplotlib.sourceforge.net/examples/animation/simple_anim_tkagg.html:
import time
import numpy as np
import matplotlib
matplotlib.use('TkAgg') # do this before importing pylab
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.add_subplot(111)
def animate():
tstart = time.time() # for profiling
data=np.random.randn(10,10)
im=plt.imshow(data)
for i in np.arange(1,200):
data=np.random.randn(10,10)
im.set_data(data)
fig.canvas.draw() # redraw the canvas
print 'FPS:' , 200/(time.time()-tstart)
win = fig.canvas.manager.window
fig.canvas.manager.window.after(100, animate)
plt.show()
plt.imshow can accept a float array, uint8 array, or a PIL image.
So if you have a directory of PNG files, you could open them as PIL images and animate them like this:
import matplotlib
matplotlib.use('TkAgg') # do this before importing pylab
import matplotlib.pyplot as plt
import Image
import glob
fig = plt.figure()
ax = fig.add_subplot(111)
def animate():
filenames=sorted(glob.glob('*.png'))
im=plt.imshow(Image.open(filenames[0]))
for filename in filenames[1:]:
image=Image.open(filename)
im.set_data(image)
fig.canvas.draw()
win = fig.canvas.manager.window
fig.canvas.manager.window.after(100, animate)
plt.show()

The best way I have found for this was with the command pylab.ion() after you import pylab.
Here is a script that does use show(), but which displays the different plots each time pylab.draw() is called, and which leaves the plot windows showing indefinitely. It uses simple input logic to decide when to close the figures (because using show() means pylab won't process clicks on the windows x button), but that should be simple to add to your gui as another button or as a text field.
import numpy as np
import pylab
pylab.ion()
def get_fig(fig_num, some_data, some_labels):
fig = pylab.figure(fig_num,figsize=(8,8),frameon=False)
ax = fig.add_subplot(111)
ax.set_ylim([0.1,0.8]); ax.set_xlim([0.1, 0.8]);
ax.set_title("Quarterly Stapler Thefts")
ax.pie(some_data, labels=some_labels, autopct='%1.1f%%', shadow=True);
return fig
my_labels = ("You", "Me", "Some guy", "Bob")
# To ensure first plot is always made.
do_plot = 1; num_plots = 0;
while do_plot:
num_plots = num_plots + 1;
data = np.random.rand(1,4).tolist()[0]
fig = get_fig(num_plots,data,my_labels)
fig.canvas.draw()
pylab.draw()
print "Close any of the previous plots? If yes, enter its number, otherwise enter 0..."
close_plot = raw_input()
if int(close_plot) > 0:
pylab.close(int(close_plot))
print "Create another random plot? 1 for yes; 0 for no."
do_plot = raw_input();
# Don't allow plots to go over 10.
if num_plots > 10:
do_plot = 0
pylab.show()
By modifying the basic logic here, I can have it close windows and plot images consecutively to simulate playing a movie, or I can maintain keyboard control over how it steps through the movie.
Note: This has worked for me across platforms and seems strictly superior to the window canvas manager approach above, and doesn't require the 'TkAgg' option.

I have implemented a handy script that just suits your need. Try it out here
Below is a example that show images together with its bounding box:
import os
import glob
from scipy.misc import imread
from matplotlib.pyplot import Rectangle
video_dir = 'YOUR-VIDEO-DIRECTORY'
img_files = glob.glob(os.path.join(video_dir, '*.jpg'))
box_files = glob.glob(os.path.join(video_dir, '*.txt'))
def redraw_fn(f, axes):
img = imread(img_files[f])
box = bbread(box_files[f]) # Define your own bounding box reading utility
x, y, w, h = box
if not redraw_fn.initialized:
im = axes.imshow(img, animated=True)
bb = Rectangle((x, y), w, h,
fill=False, # remove background
edgecolor="red")
axes.add_patch(bb)
redraw_fn.im = im
redraw_fn.bb = bb
redraw_fn.initialized = True
else:
redraw_fn.im.set_array(img)
redraw_fn.bb.set_xy((x, y))
redraw_fn.bb.set_width(w)
redraw_fn.bb.set_height(h)
redraw_fn.initialized = False
videofig(len(img_files), redraw_fn, play_fps=30)

Related

MatPlotLib with ion() does not show window

If I run the following code:
import matplotlib.pyplot as plt
import numpy as np
#plt.ion()
while True:
print('loop')
x = range(10)
y = np.random.rand(10)
plt.scatter(x, y)
plt.show()
Then I see a scatter plot displayed on my screen. Then each time I close the window for the plot, it displays a new plot with new data.
However, if I uncomment the line plt.ion(), nothing is displayed at all. There is no window created, and the program just continues through the loop, printing out 'loop'.
I want to be able to display a graph, and then return to the code automatically, with the graph still displayed. How can I do this?
If you want to plot on top of the same figure window, rather than generating a new window at every iteration the following will work:
import matplotlib.pyplot as plt
import numpy as np
plt.ion()
fig, ax = plt.subplots(1, 1)
while True:
# If wanting to see an "animation" of points added, add a pause to allow the plotting to take place
plt.pause(1)
x = range(10)
y = np.random.rand(10)
ax.scatter(x, y)
The result you see will depend on the which matplotlib backend you are using. If you're wanting to see the new points being added you should use Qt4 or Qt5

Updating pyplot graph in real time

I'm trying to plot a 2D grid of data and map them to colors. Then I want to update the values and have the graph update with the new values. Currently the graph only shows the final result, not all the middle phases the graph should go through.
MY CODE::
import matplotlib.pyplot as pyplot
import matplotlib as mpl
import numpy as np
import time
import matplotlib.animation as animation
thing=0
NUM_COL=10
NUM_ROW=10
zvals=np.full((NUM_ROW,NUM_COL),-5.0)
def update_graph(zvals):
zvals+=1
pyplot.clf()
img = pyplot.imshow(zvals,interpolation='nearest',
cmap = cmap,norm=norm)
time.sleep(1)
pyplot.draw()
# make a color map of fixed colors
cmap = mpl.colors.ListedColormap(['blue','black','red'])
bounds=[-6,-2,2,6]
norm = mpl.colors.BoundaryNorm(bounds, cmap.N)
# tell imshow about color map so that only set colors are used
img = pyplot.imshow(zvals,interpolation='nearest',
cmap = cmap,norm=norm)
# make a color bar
pyplot.colorbar(img,cmap=cmap,norm=norm,boundaries=bounds,ticks=[-5,0,5])
pyplot.draw()
for i in range(5):
update_graph(zvals)
pyplot.show()
pyplot does not generally show anything until pyplot.show() is called, unless matplotlib runs in 'interactive' mode. The interactive mode is entered by calling pyplot.ion() and can exited again by calling pyplot.ioff().
Thus it should be possible for you to see all your updates by calling pyplot.ion() somewhere before doing anything you want to be directly updated and then end your program with pyplot.ioff() to get back to the standard pyplot way when done.
However, it may not look very smooth, depending on your system and what updates you are doing.
So I'm not sure if this a good answer or not, I have only used updating plots once before. But this is a way to achieve what you want.
import matplotlib.animation as animation
import matplotlib.pyplot as plt
import matplotlib as mpl
import numpy as np
NUM_COL = 10
NUM_ROW = 10
zvals = np.full((NUM_ROW,NUM_COL),-5.0)
cmap = mpl.colors.ListedColormap(['blue','black','red'])
bounds = [-6,-2,2,6]
norm = mpl.colors.BoundaryNorm(bounds, cmap.N)
fig = plt.figure() # Create the figure
img = plt.imshow(zvals,interpolation='nearest', cmap=cmap,norm=norm) # display the first image
plt.colorbar(img,cmap=cmap,norm=norm,boundaries=bounds,ticks=[-5,0,5]) # create your colour bar
# If we dont have this, then animation.FuncAnimation will call update_graph upon initialization
def init():
pass
# animation.FuncAnimation will use this function to update the plot. This is where we update what we want displayed
def update_graph(frame):
global zvals # zvals is a global variable
zvals+=1
img.set_data(zvals) # This sets the data to the new, updated values
print("Frame Update {}".format(frame)) # this is for debugging to help you see whats going on
return img
# This is what will run the animations
anim = animation.FuncAnimation(fig, update_graph, init_func = init,
interval = 1000, # update every 1000ms
frames = 8, # Update 8 times
repeat=False) # After 8 times, don't repeat the animation
plt.show() # show our plot

How to keep matplotlib (python) window in background?

I have a python / matplotlib application that frequently updates a plot with new data coming in from a measurement instrument. The plot window should not change from background to foreground (or vice versa) with respect to other windows on my desktop when the plot is updated with new data.
This worked as desired with Python 3 on a machine running Ubuntu 16.10 with matplotlib 1.5.2rc. However, on a different machine with Ubuntu 17.04 and matplotlib 2.0.0, the figure window pops to the front every time the plot is updated with new data.
How can I control the window foreground/background behavior and keep the window focus when updating the plot with new data?
Here's a code example illustrating my plotting routine:
import matplotlib
import matplotlib.pyplot as plt
from time import time
from random import random
print ( matplotlib.__version__ )
# set up the figure
fig = plt.figure()
plt.xlabel('Time')
plt.ylabel('Value')
plt.ion()
# plot things while new data is generated:
t0 = time()
t = []
y = []
while True:
t.append( time()-t0 )
y.append( random() )
fig.clear()
plt.plot( t , y )
plt.pause(1)
matplotlib was changed somewhere from version 1.5.2rc to 2.0.0 such that pyplot.show() brings the window to the foreground (see here). The key is therefore to avoid calling pyplot.show() in the loop. The same goes for pyplot.pause().
Below is a working example. This will still bring the window to the foreground at the beginning. But the user may move the window to the background, and the window will stay there when the figure is updated with new data.
Note that the matplotlib animation module might be a good choice to produce the plot shown in this example. However, I couldn't make the animation work with interactive plot, so it blocks further execution of other code. That's why I could not use the animation module in my real-life application.
import matplotlib
matplotlib.use('TkAgg')
import matplotlib.pyplot as plt
import time
from random import random
print ( matplotlib.__version__ )
# set up the figure
plt.ion()
fig = plt.figure()
ax = plt.subplot(1,1,1)
ax.set_xlabel('Time')
ax.set_ylabel('Value')
t = []
y = []
ax.plot( t , y , 'ko-' , markersize = 10 ) # add an empty line to the plot
fig.show() # show the window (figure will be in foreground, but the user may move it to background)
# plot things while new data is generated:
# (avoid calling plt.show() and plt.pause() to prevent window popping to foreground)
t0 = time.time()
while True:
t.append( time.time()-t0 ) # add new x data value
y.append( random() ) # add new y data value
ax.lines[0].set_data( t,y ) # set plot data
ax.relim() # recompute the data limits
ax.autoscale_view() # automatic axis scaling
fig.canvas.flush_events() # update the plot and take care of window events (like resizing etc.)
time.sleep(1) # wait for next loop iteration
For the tkinter backend (matplotlib.use("TkAgg")), using flush_events is not sufficient: you also need to call fig.canvas.draw_idle() before each fig.canvas.flush_events(). As #samlaf wrote, the same holds for the Qt5Agg backend.

how do I redraw an image using python's matplotlib?

What I am trying to do seems to be fairly straightforward, but I'm having a heck of a time trying to get it to work. I am simply trying to draw an image using imshow and then re-draw it periodically as new data arrives.
I've started out with this:
fig = figure()
ax = plt.axes(xlim=(0,200),ylim=(0,200))
myimg = ax.imshow(zeros((200,200),float))
Then I'm assuming I can call set_data like this to update the image:
myimg.set_data(newdata)
I've tried many other things, for example I've called ax.imshow(newdata) instead or I've tried using figure.show() after set_data().
You can simply call figure.canvas.draw() each time you append something new to the figure. This will refresh the plot.
from matplotlib import pyplot as plt
from builtins import input
fig = plt.figure()
ax = fig.gca()
fig.show()
block = False
for i in range(10):
ax.plot(i, i, 'ko')
fig.canvas.draw()
if block:
input('pause : press any key ...')
else:
plt.pause(0.1)
plt.close(fig)

How to update matplotlib's imshow() window interactively?

I'm working on some computer vision algorithm and I'd like to show how a numpy array changes in each step.
What works now is that if I have a simple imshow( array ) at the end of my code, the window displays and shows the final image.
However what I'd like to do is to update and display the imshow window as the image changes in each iteration.
So for example I'd like to do:
import numpy as np
import matplotlib.pyplot as plt
import time
array = np.zeros( (100, 100), np.uint8 )
for i in xrange( 0, 100 ):
for j in xrange( 0, 50 ):
array[j, i] = 1
#_show_updated_window_briefly_
plt.imshow( array )
time.sleep(0.1)
The problem is that this way, the Matplotlib window doesn't get activated, only once the whole computation is finished.
I've tried both native matplotlib and pyplot, but the results are the same. For plotting commands I found an .ion() switch, but here it doesn't seem to work.
Q1. What is the best way to continuously display updates to a numpy array (actually a uint8 greyscale image)?
Q2. Is it possible to do this with an animation function, like in the dynamic image example? I'd like to call a function inside a loop, thus I don't know how to achieve this with an animation function.
You don't need to call imshow all the time. It is much faster to use the object's set_data method:
myobj = imshow(first_image)
for pixel in pixels:
addpixel(pixel)
myobj.set_data(segmentedimg)
draw()
The draw() should make sure that the backend updates the image.
UPDATE: your question was significantly modified. In such cases it is better to ask another question. Here is a way to deal with your second question:
Matplotlib's animation only deals with one increasing dimension (time), so your double loop won't do. You need to convert your indices to a single index. Here is an example:
import numpy as np
from matplotlib import pyplot as plt
from matplotlib import animation
nx = 150
ny = 50
fig = plt.figure()
data = np.zeros((nx, ny))
im = plt.imshow(data, cmap='gist_gray_r', vmin=0, vmax=1)
def init():
im.set_data(np.zeros((nx, ny)))
def animate(i):
xi = i // ny
yi = i % ny
data[xi, yi] = 1
im.set_data(data)
return im
anim = animation.FuncAnimation(fig, animate, init_func=init, frames=nx * ny,
interval=50)
I struggled to make it work because many post talk about this problem, but no one seems to care about providing a working example. In this case however, the reasons were different :
I couldn't use Tiago's or Bily's answers because they are not in the
same paradigm as the question. In the question, the refresh is
scheduled by the algorithm itself, while with funcanimation or
videofig, we are in an event driven paradigm. Event driven
programming is unavoidable for modern user interface programming, but
when you start from a complex algorithm, it might be difficult to
convert it to an event driven scheme - and I wanted to be able to do
it in the classic procedural paradigm too.
Bub Espinja reply suffered another problem : I didn't try it in the
context of jupyter notebooks, but repeating imshow is wrong since it
recreates new data structures each time which causes an important
memory leak and slows down the whole display process.
Also Tiago mentioned calling draw(), but without specifying where to get it from - and by the way, you don't need it. the function you really need to call is flush_event(). sometime it works without, but it's because it has been triggered from somewhere else. You can't count on it. The real tricky point is that if you call imshow() on an empty table, you need to specify vmin and vmax or it will fail to initialize it's color map and set_data will fail too.
Here is a working solution :
IMAGE_SIZE = 500
import numpy as np
import matplotlib.pyplot as plt
plt.ion()
fig1, ax1 = plt.subplots()
fig2, ax2 = plt.subplots()
fig3, ax3 = plt.subplots()
# this example doesn't work because array only contains zeroes
array = np.zeros(shape=(IMAGE_SIZE, IMAGE_SIZE), dtype=np.uint8)
axim1 = ax1.imshow(array)
# In order to solve this, one needs to set the color scale with vmin/vman
# I found this, thanks to #jettero's comment.
array = np.zeros(shape=(IMAGE_SIZE, IMAGE_SIZE), dtype=np.uint8)
axim2 = ax2.imshow(array, vmin=0, vmax=99)
# alternatively this process can be automated from the data
array[0, 0] = 99 # this value allow imshow to initialise it's color scale
axim3 = ax3.imshow(array)
del array
for _ in range(50):
print(".", end="")
matrix = np.random.randint(0, 100, size=(IMAGE_SIZE, IMAGE_SIZE), dtype=np.uint8)
axim1.set_data(matrix)
fig1.canvas.flush_events()
axim2.set_data(matrix)
fig1.canvas.flush_events()
axim3.set_data(matrix)
fig1.canvas.flush_events()
print()
UPDATE : I added the vmin/vmax solution based on #Jettero's comment (I missed it at first).
If you are using Jupyter, maybe this answer interests you.
I read in this site that the emmbebed function of clear_output can make the trick:
%matplotlib inline
from matplotlib import pyplot as plt
from IPython.display import clear_output
plt.figure()
for i in range(len(list_of_frames)):
plt.imshow(list_of_frames[i])
plt.title('Frame %d' % i)
plt.show()
clear_output(wait=True)
It is true that this method is quite slow, but it can be used for testing purposes.
I implemented a handy script that just suits your needs. Try it out here
An example that shows images in a custom directory is like this:
import os
import glob
from scipy.misc import imread
img_dir = 'YOUR-IMAGE-DIRECTORY'
img_files = glob.glob(os.path.join(video_dir, '*.jpg'))
def redraw_fn(f, axes):
img_file = img_files[f]
img = imread(img_file)
if not redraw_fn.initialized:
redraw_fn.im = axes.imshow(img, animated=True)
redraw_fn.initialized = True
else:
redraw_fn.im.set_array(img)
redraw_fn.initialized = False
videofig(len(img_files), redraw_fn, play_fps=30)
I had a similar problem - want to update image, don't want to repeatedly replace the axes, but plt.imshow() (nor ax.imshow()) was not updating the figure displayed.
I finally discovered that some form of draw() was required. But fig.canvas.draw(), ax.draw() ... all did not work. I finally found the solution here:
%matplotlib notebook #If using Jupyter Notebook
import matplotlib.pyplot as plt
import numpy as np
imData = np.array([[1,3],[3,1]])
# Setup and plot image
fig = plt.figure()
ax = plt.subplot(111)
im = ax.imshow(imData)
# Change image contents
newImData = np.array([[2,2],[2,2]])
im.set_data( newImData )
im.draw()
import numpy as np
import matplotlib.pyplot as plt
k = 10
plt.ion()
array = np.zeros((k, k))
for i in range(k):
for j in range(k):
array[i, j] = 1
plt.imshow(array)
plt.show()
plt.pause(0.001)
plt.clf()

Categories

Resources