Differences between revisions 7 and 8
|Deletions are marked like this.||Additions are marked like this.|
|Line 1:||Line 1:|
'''''Note:''''' Some of the matplotlib code in this cookbook entry may be deprecated or obsolete. For example, the file '''anim.py''' mentioned below no longer exists in matplotlib. Examples of animation in matplotlib are at http://matplotlib.sourceforge.net/examples/animation/index.html, and the main animation API documentation is http://matplotlib.sourceforge.net/api/animation_api.html.
Note: Some of the matplotlib code in this cookbook entry may be deprecated or obsolete. For example, the file anim.py mentioned below no longer exists in matplotlib. Examples of animation in matplotlib are at http://matplotlib.sourceforge.net/examples/animation/index.html, and the main animation API documentation is http://matplotlib.sourceforge.net/api/animation_api.html.
matplotlib supports animated plots, and provides a number of demos. An important question when considering whether to use matplotlib for animation is what kind of speed you need. matplotlib is not the fastest plotting library in the west, and may be too slow for some animation applications. Nonetheless, it is fast enough for many if not most, and this tutorial is geared to showing you how to make it fast enough for you. In particular, the section *Animating selected plot elements* shows you how to get top speed out of matplotlib animations.
matplotlib supports 5 different graphical user interfaces (GTK, WX, Qt, Tkinter, FLTK) and for some of those GUIs, there are various ways to draw to the canvas. For example, for GTK, you can use native GDK drawing, antigrain, or cairo. A GUI toolkit combined with some method of drawing comprises a backend. For example, drawing to a GTK canvas with the antigrain drawing toolkit is called the GTKAgg backend. This is important because different backends have different performance characteristics, and the difference can be considerable.
When considering performance, the typical measure is frames per second. Television is 30 frames per second, and for many application if you can get 10 or more frames per second the animation is smooth enough to "look good". Monitors refresh at 75-80 frames per second typically, and so this is an upper limit for performance. Any faster is probably wasted CPU cycles.
Here are some numbers for the animated script anim.py, which simply updates a sine wave on various backends, run under linux on a 3GHz Pentium IV. To profile a script under different backends, you can use the "GUI neutral" animation technique described below and then run it with the -dBackend flag, as in:
> python anim.py -dWX > python anim.py -dGTKAgg
Here are the results. Note that these should be interpreted cautiously, because some GUIs might call a drawing operation in a separate thread and return before it is complete, or drop a drawing operation while one is in the queue. The most important assessment is qualitative.
Backend Frames/second GTK 43 GTKAgg 36 TkAgg 20 WX 11 WXAgg 27
GUI neutral animation in pylab
The pylab interface supports animation that does not depend on a specific GUI toolkit. This is not recommended for production use, but is often a good way to make a quick-and-dirty, throw away animation. After importing pylab, you need to turn interaction on with the ion command. You can then force a draw at any time with draw. In interactive mode, a new draw command is issued after each pylab command, and you can also temporarily turn off this behavior for a block of plotting commands in which you do not want an update with the ioff commands. This is described in more detail on the interactive page.
Here is the anim.py script that was used to generate the profiling numbers across backends in the table above
1 from pylab import * 2 import time 3 4 ion() 5 6 tstart = time.time() # for profiling 7 x = arange(0,2*pi,0.01) # x-array 8 line, = plot(x,sin(x)) 9 for i in arange(1,200): 10 line.set_ydata(sin(x+i/10.0)) # update the data 11 draw() # redraw the canvas 12 13 print 'FPS:' , 200/(time.time()-tstart)
Note the technique of creating a line with the call to plot:
1 line, = plot(x,sin(x))
and then setting its data with the set_ydata method and calling draw:
1 line.set_ydata(sin(x+i/10.0)) # update the data 2 draw() # redraw the canvas
This can be much faster than clearing the axes and/or creating new objects for each plot command.
To animate a pcolor plot use:
1 p = pcolor(XI,YI,C) 2 3 for i in range(1,len(C)): 4 p.set_array(C[i,0:-1,0:-1].ravel()) 5 p.autoscale() 6 draw()
This assumes C is a 3D array where the first dimension is time and that XI,YI and C[i,:,:] have the same shape. If C[i,:,:] is one row and one column smaller simply use C.ravel().
Using the GUI timers or idle handlers
If you are doing production code or anything semi-serious, you are advised to use the GUI event handling specific to your toolkit for animation, because this will give you more control over your animation than matplotlib can provide through the GUI neutral pylab interface to animation. How you do this depends on your toolkit, but there are examples for several of the backends in the matplotlib examples directory, eg, anim_tk.py for Tkinter, dynamic_image_gtkagg.py for GTKAgg and dynamic_image_wxagg.py for WXAgg.
The basic idea is to create your figure and a callback function that updates your figure. You then pass that callback to the GUI idle handler or timer. A simple example in GTK looks like
1 def callback(*args): 2 line.set_ydata(get_next_plot()) 3 canvas.draw() # or simply "draw" in pylab 4 5 gtk.idle_add(callback)
A simple example in WX or WXAgg looks like
1 def callback(*args): 2 line.set_ydata(get_next_plot()) 3 canvas.draw() 4 wx.WakeUpIdle() # ensure that the idle event keeps firing 5 6 wx.EVT_IDLE(wx.GetApp(), callback)
Animating selected plot elements
One limitation of the methods presented above is that all figure elements are redrawn with every call to draw, but we are only updating a single element. Often what we want to do is draw a background, and animate just one or two elements on top of it. As of matplotlib-0.87, GTKAgg, TkAgg, WXAgg, and FLTKAgg support the methods discussed here.
The basic idea is to set the 'animated' property of the Artist you want to animate (all figure elements from Figure to Axes to Line2D to Text derive from the base class Artist). Then, when the standard canvas draw operation is called, all the artists except the animated one will be drawn. You can then use the method background = canvas.copy_from_bbox(bbox) to copy a rectangular region (eg the axes bounding box) into a a pixel buffer. In animation, you restore the background with canvas.restore_region(background), and then call ax.draw_artist(something) to draw your animated artist onto the clean background, and canvas.blit(bbox) to blit the updated axes rectangle to the figure. When I run the example below in the same environment that produced 36 FPS for GTKAgg above, I measure 327 FPS with the techniques below. See the caveats on performance numbers mentioned above. Suffice it to say, quantitatively and qualitiatively it is much faster.
1 import sys 2 import gtk, gobject 3 import pylab as p 4 import matplotlib.numerix as nx 5 import time 6 7 ax = p.subplot(111) 8 canvas = ax.figure.canvas 9 10 # for profiling 11 tstart = time.time() 12 13 # create the initial line 14 x = nx.arange(0,2*nx.pi,0.01) 15 line, = p.plot(x, nx.sin(x), animated=True) 16 17 # save the clean slate background -- everything but the animated line 18 # is drawn and saved in the pixel buffer background 19 background = canvas.copy_from_bbox(ax.bbox) 20 21 def update_line(*args): 22 # restore the clean slate background 23 canvas.restore_region(background) 24 # update the data 25 line.set_ydata(nx.sin(x+update_line.cnt/10.0)) 26 # just draw the animated artist 27 ax.draw_artist(line) 28 # just redraw the axes rectangle 29 canvas.blit(ax.bbox) 30 31 if update_line.cnt==50: 32 # print the timing info and quit 33 print 'FPS:' , update_line.cnt/(time.time()-tstart) 34 sys.exit() 35 36 update_line.cnt += 1 37 return True 38 update_line.cnt = 0 39 40 gobject.idle_add(update_line) 41 p.show()
matplotlib 0.83.2 introduced a cursor class which can utilize these blit methods for no lag cursoring. The class takes a useblit=True|False argument in the constructor. For backends that support the new API (GTKAgg) set useblit=True:
1 from matplotlib.widgets import Cursor 2 import pylab 3 4 fig = pylab.figure(figsize=(8,6)) 5 ax = fig.add_axes([0.075, 0.25, 0.9, 0.725], axisbg='#FFFFCC') 6 7 x,y = 4*(pylab.rand(2,100)-.5) 8 ax.plot(x,y,'o') 9 ax.set_xlim(-2,2) 10 ax.set_ylim(-2,2) 11 12 # set useblit = True on gtkagg for enhanced performance 13 cursor = Cursor(ax, useblit=True, color='red', linewidth=2 ) 14 15 pylab.show()
The 'blit' animation methods
As noted above, only the GTKAgg supports the methods above to to the animations of selected actors. The following are needed
Figure canvas methods
background = canvas.copy_from_bbox(ax.bbox) - copy the region in ax.bbox into a pixel buffer and return it in an object type of your choosing. bbox is a matplotlib BBox instance from the transforms module. background is not used by the matplotlib frontend, but it stores it and passes it back to the backend in the restore_region method. You will probably want to store not only the pixel buffer but the rectangular region of the canvas from whence it came in the background object.
canvas.restore_region(background) - restore the region copied above to the canvas.
canvas.blit(bbox) - transfer the pixel buffer in region bounded by bbox to the canvas.
For *Agg backends, there is no need to implement the first two as Agg will do all the work (FigureCanvasAgg defines them). Thus you only need to be able to blit the agg buffer from a selected rectangle. One thing that might make this easier for backends using string methods to transfer the agg pixel buffer to their respective canvas is to define a to_rgba_str(bbox) method in agg. If you are working on this and need help, please contact the matplotlib-devel list.
Once all/most of the backends have implemented these methods, the matplotlib front end can do all the work of managing the background/restore/blit opertations, and userland animated code can look like
1 line, = plot(something, animated=True) 2 draw() 3 def callback(*args): 4 line.set_ydata(somedata) 5 ax.draw_animated()
and the rest will happen automagically. Since some backends do not currently implement the required methods, I am making them available to the users to manage themselves but am not assuming them in the axes drawing code.