4

I'm having issues with a slow animation in Matplotlib. I'm animating results from a simulation, which is easiest visualized with an array of rectangles that change color with time.

Following recommendations here, I'm using blitting to only draw the (small fraction) of rectangles that change in each frame. I also tried to implement this using FuncAnimation, but when using that with Blit=True, the script runs much slower.

I'm wondering if this is because I'm returning all of the rectangles to FuncAnimation, so it redraws all of them even if they haven't changed. Is there a way to pass different artists at each frame to FuncAnimation? I tried just passing a tuple of the ones that had changed (the commented out block in the "animate" function), but that led to seemingly random animation frames...

Use:

$ python2 [script].py blit
$ python2 [script].py anim

Thanks!

import sys
import numpy as np
import matplotlib
matplotlib.use("TkAgg")
import matplotlib.pyplot as plt
import matplotlib.animation as manim

def animate_data(plot_type):
    """
    Use:
    python2 plot_anim.py [option]
    option = anim OR blit
    """

    # dimension parameters
    Nx = 30
    Ny = 20
    numtimes = 100
    size = 0.5

    if plot_type == "blit":
        # "interactive mode on"
        plt.ion()
    # Prepare to do initial plot
    fig = plt.figure()
    ax = fig.add_subplot(1,1,1)
    ax.set_aspect('equal', 'box')
    ax.xaxis.set_major_locator(plt.NullLocator())
    ax.yaxis.set_major_locator(plt.NullLocator())
    # An array in which to store the rectangle artists
    rects = np.empty((Nx, Ny), dtype=object)
    # Generate initial figure of all green rectangles
    for (i,j),k in np.ndenumerate(rects):
        color = 'green'
        rects[i, j] = plt.Rectangle([i - size / 2, j - size / 2],
                size, size, facecolor=color, edgecolor=color)
        ax.add_patch(rects[i, j])
    ax.autoscale_view()

    # "Old" method using fig.canvas.blit()
    if plot_type == "blit":
        plt.show()
        fig.canvas.draw()
        # Step through time updating the rectangles
        for tind in range(1, numtimes):
            updated_array = update_colors(rects)
            for (i, j), val in np.ndenumerate(updated_array):
                if val:
                    ax.draw_artist(rects[i, j])
            fig.canvas.blit(ax.bbox)

    # New method using FuncAnimate
    elif plot_type == "anim":
        def animate(tind):
            updated_array = update_colors(rects)
#            # Just pass the updated artists to FuncAnimation
#            toupdate = []
#            for (i, j), val in np.ndenumerate(updated_array):
#                if val:
#                    toupdate.append(rects[i, j])
#            return tuple(toupdate)
            return tuple(rects.reshape(-1))
        ani = manim.FuncAnimation(fig, animate, frames=numtimes,
                interval=10, blit=True, repeat=False)
        plt.show()

    return

# A function to randomly update a few rectangles
def update_colors(rects):
    updated_array = np.zeros(rects.shape)
    for (i, j), c in np.ndenumerate(rects):
        rand_val = np.random.rand()
        if rand_val < 0.003:
            rects[i, j].set_facecolor('red')
            rects[i, j].set_edgecolor('red')
            updated_array[i, j] = 1
    return updated_array

if __name__ == "__main__":
    if len(sys.argv) > 1:
        plot_type = sys.argv[1]
    else:
        plot_type = "blit"
    animate_data(plot_type)
Community
  • 1
  • 1
muon
  • 153
  • 1
  • 8
  • 2
    There is too much code here, please reduce it to the _minimum_ that will show your problem. Making in easy for people to understand your question will make it more likely you will get an answer. – tacaswell Nov 07 '13 at 20:59
  • Good point and thanks for the feedback, @tcaswell. I've modified it to try to condense it and also eliminated the need for an external data file. – muon Nov 08 '13 at 02:42
  • This is still way too long. Are all of your modes needed to demonstrate the problem? Do we really need the update color logic (can it be replaced with `rand(3)`?) Also, don't name you function `plot`. That function already exists (in both pyplot and as an axes function). It will work correctly, it is just confusing to readers. – tacaswell Nov 08 '13 at 03:08
  • Again, thanks. Pared down some more. It just compares the "simple" blitting and the FuncAnimation now, and uses randomness for the color changes. – muon Nov 08 '13 at 05:40

1 Answers1

4

Update 600 rectangles every frame is very slow, cbar_blit mode in your code is faster because you only update the rectangles which's color is changed. You can use PatchCollection to speedup drawing, here is the code:

import numpy as np
import matplotlib
matplotlib.use("TkAgg")
import matplotlib.pyplot as plt
import matplotlib.animation as manim
from matplotlib.collections import PatchCollection

Nx = 30
Ny = 20
numtimes = 100

size = 0.5

x, y = np.ogrid[-1:1:30j, -1:1:20j]

data = np.zeros((numtimes, Nx, Ny))

for i in range(numtimes):
    data[i] = (x-i*0.02+1)**2 + y**2

colors = plt.cm.rainbow(data)

fig, ax = plt.subplots()

rects = []
for (i,j),c in np.ndenumerate(data[0]):
    rect = plt.Rectangle([i - size / 2, j - size / 2],size, size)
    rects.append(rect)

collection = PatchCollection(rects, animated=True)

ax.add_collection(collection)
ax.autoscale_view(True)


def animate(tind):
    c = colors[tind].reshape(-1, 4)
    collection.set_facecolors(c)    
    return (collection,)

ani = manim.FuncAnimation(fig, animate, frames=numtimes,
        interval=10, blit=True, repeat=False)

plt.show()        
HYRY
  • 94,853
  • 25
  • 187
  • 187