Making Efficient Animations in Matplotlib with Blitting

Posted January 20, 2015 by Emily Dolson in Information / 2 Comments

I flip-flop between Python, R, and D3 for my data visualizations depending on what exactly I’m doing. My default, though, is definitely Python. One of the most well-established data visualization libraries in Python is Matplotlib. If you dig deep enough in it, you can find a wide variety of features beyond standard graphs. One of the less well-documented of these features is the animation library. The FuncAnimation class in particular is quite powerful, allowing you to programmatically generate the frames for your animation and compile them together. Jake VanderPlas has a great tutorial on using FuncAnimation which I’m not going to try to duplicate. Here, I’m just going to focus on a small but critical aspect of using FuncAnimation that is glossed over elsewhere: blitting.

Here’s how critical blitting is: My first attempted Matplotlib animation took around an hour to render. That wasn’t going to work. Thanks to blitting, I can now render the same animation in under a minute.

The basic idea behind FuncAnimation is that you pass it a function that it will use to generate new frames. If you aren’t using blitting, this function generates each one of your frames from scratch. Unsurprisingly, that takes a super long time (okay, I’m lying: I was surprised by how long it took at first). Fortunately, if you are making an animation, chances are your frames aren’t 100% different from each other. In fact, it’s likely that there is just one element or set of elements that is changing. Blitting takes advantage of this by just changing the plot elements that need to be changed. However, this completely changes the way that the function you pass to FuncAnimation needs to work.

To demonstrate this, I’ll use my code as an example. Here’s the animation I was trying to make. It has a fixed background with colored circles that appear on top and change color from frame to frame:

First we load in the data and set stuff up:

import matplotlib
import matplotlib.pyplot as plt
from matplotlib import animation

world_size = 59 #The circles appear on a 59 X 59 grid
world = load_environment(filename) #Don't worry about this it's just
#loading a 2D array of numbers to pass to imshow() to make the background

circle_colors = load_color_data(filename) #And this is loading a 3D array
#Each XY coordinate contains a list of RGB colors indicating the series
#of colors the circle should take on in each frame (so the length of
#these lists is equal to the number of frames)

Next, we create a figure object to pass to the FuncAnimation instance:

#Create figure to do plotting on
fig = plt.figure(figsize=(20,20))

Now we need to create a list of objects that will change from frame to frame. These objects need to be “drawables.” The Matplotlib documentation doesn’t clearly define what drawables are, but they seem to at least include all artist objects. Here, we will use Circles (for those keeping track at home, these are matplotlib.patches objects, which inherit from artists), but you could do this with lines, polygons, or a wide variety of other shapes.

#Create list of circles, one for every grid cell in the environment
patches = []

for i in range(world_size):
    for j in range(world_size):
        patches.append(plt.Circle((j,i), radius=.3, lw=2, ec="black", facecolor=None))
        #(j,i) are the (x,y) coordinates, lw is the line width, ec is the color of the
        #outline, setting facecolor to None makes these circles invisible for now

Now we’re ready to start defining functions to pass to FuncAnimation! The first one is the init function. It plots the parts of the image that don’t change between frames, and adds all of the parts that will change to the axes of this image.

def init():
    #Draw background
    plt.imshow(world, interpolation="none", hold=True)
    axes = plt.gca()
    axes.autoscale(False)

    #Add patches to axes
    for p in patches:
        axes.add_patch(p)

At last, it’s time to write the function that actually makes all of the frames. This function will basically loop through all of the circles and assign them the appropriate color. The function will automatically get passed one argument: the frame number currently being generated. Additional arguments can be specified using the fargs argument in FuncAnimation.

def animate(n):
    #Recolor circles
    #Note that this needs to be in the same scope as circle_colors

    for i in range(world_size):
        for j in range(world_size):

        if circle_colors[i][j][n] == 0:
            #Here, I'm using 0 as code for non-existent
            #So I make these circles invisible
            patches[i * world_size + j].set_visible(False)

        else: #Otherwise we set the color to the one indicated by circle_colors
            #and make sure the circle is set to visible
            patches[i*world_size + j].set_facecolor(circle_colors[i][j][n])
            patches[i*world_size + j].set_visible(True)

    #We haven't actually changed patches, but FuncAnimation is
    #still expecting us to return it. Don't forget the comma.
    return patches,

All that’s left now is to finally create the animation object and let it do its (now dramatically faster) rendering.

anim = animation.FuncAnimation(fig, animate, init_func=init,
frames=len(circle_colors[0][0]), blit=True)
#remember to set blit=True

anim.save(filename, writer="mencoder", fps=2) #You can also save your animation!
#Appropriate writer will vary based on your computer

return anim

You should now be able to create spiffy animations that don’t take hours to render! Note that there seems to be a bug with some versions of VLC where the animated parts of the picture don’t show up. This may or may not lead to your adviser being perplexed that you sent them an animation that doesn’t do anything.

Other resources that I found helpful in figuring out how this works include: these stackoverflow posts (although I never did get the PatchCollection class mentioned in the second to work), this sample script, and this tutorial.

Emily Dolson

I'm a doctoral student in the Ofria Lab at Michigan State University, the BEACON Center for Evolution in Action, and the departments of Computer Science and Ecology, Evolutionary Biology, & Behavior. My interests include studying eco-evolutionary dynamics via digital evolution and using evolutionary computation techniques to interpret time series data. I also have a cross-cutting interest in diversity in both biological and computational systems. In my spare time, I enjoy playing board games and the tin whistle.

More Posts - Website - Twitter

2 responses to “Making Efficient Animations in Matplotlib with Blitting

  1. sonia

    I was wondering if you could elaborate on the sentence: “However, this completely changes the way that the function you pass to FuncAnimation needs to work.”
    I see this, is that the extent of it ?
    1 – If blit=True, func and init_func should return an iterable of drawables to clear.

Leave a Reply