Matplotlib animation repeating even whit repeat set to False

Viewed 147

I'm trying to create an animation with matplotlib. I have the following code:

import math
import numpy as np
import matplotlib as mpl
from matplotlib import pyplot as plt
from matplotlib import animation as ani
import ffmpeg

xValues = np.empty((4, 100))              # Initialize the vectors
yValues = np.empty((4, 100))

for i in np.arange(100):                    # Populate the vectors
    xValues[:,i] = np.random.random(4)
    yValues[:,i] = np.random.random(4)

k = 0                                      # Initialisation of k
myFig, ax = plt.subplots(figsize=(8,5))
p, = ax.plot([],[])

def animFunc(j):                           # Animation function
    global k, p
    j = int(j)
    p.remove()
    p, = ax.plot(xValues[:,k],yValues[:,k])
    p.set_data(xValues[:,k],yValues[:,k])
    k += 1
    return p,

animator = ani.FuncAnimation(myFig, animFunc, interval=1, blit=True, repeat=False, frames=100)
animator.save('my_video.mp4', writer='ffmpeg', fps=50, bitrate=1000)

I am basically plotting two vectors xValues,yValues which have size 100 along axis 1.

When I run the code, the video is created, but I get the following error:

Traceback (most recent call last):
  File "C:\...\...\my_code.py", line 30, in <module>
    animator.save('my_video.mp4', writer='ffmpeg', fps=50, bitrate=1000)
  File "C:\Users\User\AppData\Local\Programs\Python\Python37\lib\site-packages\matplotlib\animation.py", line 1152, in save
    anim._draw_next_frame(d, blit=False)
  File "C:\Users\User\AppData\Local\Programs\Python\Python37\lib\site-packages\matplotlib\animation.py", line 1192, in _draw_next_frame
    self._draw_frame(framedata)
  File "C:\Users\User\AppData\Local\Programs\Python\Python37\lib\site-packages\matplotlib\animation.py", line 1755, in _draw_frame
    self._drawn_artists = self._func(framedata, *self._args)
  File "C:\...\...\my_code.py", line 24, in animFunc
    p, = ax.plot(xValues[:,k],yValues[:,k])
IndexError: index 100 is out of bounds for axis 1 with size 100

Seemingly, the function is trying to repeat the animation after the last frame (then the repetition starts at the index k = 100, which is too large for xValues, yValues). But why does it do so? I have set repeat=False in the declaration.

Also, how would you tune the bitrate parameter? Is 1000 a good value?


EDIT 1: JohanC's solution works fine, I tried to implement a dummy InitFunction (the ax objects are initialised once, out of any function) and to substitute k with j and now there's no additional calls to the animation!

1 Answers

When a global variable is only read inside a function, the global declaration is not needed. If, however, you call p, = ax.plot(...) inside the function with a global declaration, a local variable with the same name is created. This will cause the "referenced before assignment" error.

Calling p, = ax.plot(...) inside the update function isn't recommended, as that slows down the execution.

In your case, you seem to want to draw 4 curves simultaneously, while most funcAnimation examples only draw 1 curve. With 4 curves, ax.plot returns, well, 4 curves. In the simple case, ax.plot returns a list with one curve, and often the unintuitive comma notation is used to extract the first (and only) element of that list. With 4 curves, you do want to receive the full list (therefore, no comma is used).

The setup will need 4 dummy curves, which you could create via a 1-by-4 array of nan values. To avoid that the animation keeps moving the limits around, usually some fixed limits are set from the start.

Here is some example code:

import numpy as np
from matplotlib import pyplot as plt
from matplotlib import animation as ani

xValues = np.empty((4, 100))  # Initialize the vectors
yValues = np.empty((4, 100))

for i in np.arange(100):  # Populate the vectors
    xValues[:, i] = np.random.random(4)
    yValues[:, i] = np.random.random(4)

myFig, ax = plt.subplots(figsize=(8, 5))
empty_values = np.empty((1, 4))
empty_values[:] = np.nan
lines = ax.plot(empty_values, empty_values)
ax.set_xlim(0, 1)
ax.set_ylim(0, 1)

def initFunc():
    return lines

def animFunc(j):  # Animation function
    for i, line in enumerate(lines):
        line.set_data(xValues[i, :j], yValues[i, :j])
    return lines

animator = ani.FuncAnimation(myFig, animFunc, init_func=initFunc, interval=1, blit=True, repeat=False, frames=100)
plt.show()
Related