Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

python matplotlib: unable to call FuncAnimation from inside a function

I'm trying to implement a function which outputs an animated plot.

If I take simple_anim.py (from matplotlib examples) as a base:

"""
 A simple example of an animated plot
"""
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation

fig, ax = plt.subplots()

x = np.arange(0, 2*np.pi, 0.01)        # x-array
line, = ax.plot(x, np.sin(x))

def animate(i):
    line.set_ydata(np.sin(x+i/10.0))  # update the data
    return line,

#Init only required for blitting to give a clean slate.
def init():
    line.set_ydata(np.ma.array(x, mask=True))
    return line,

ani = animation.FuncAnimation(fig, animate, np.arange(1, 200), init_func=init,
    interval=25, blit=True)
plt.show()

Effectively it works.

BUT, if I close this code inside a function (in order to provide changing parameters, and avoid doing an explicit file for each possible parameter value):

"""
 A simple example of an animated plot
"""
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation

def a():
    fig, ax = plt.subplots()

    x = np.arange(0, 2*np.pi, 0.01)        # x-array
    line, = ax.plot(x, np.sin(x))

    def animate(i):
        line.set_ydata(np.sin(x+i/10.0))  # update the data
        return line,

    #Init only required for blitting to give a clean slate.
    def init():
        line.set_ydata(np.ma.array(x, mask=True))
        return line,

    ani = animation.FuncAnimation(fig, animate, np.arange(1, 200), init_func=init,
        interval=25, blit=True)
    plt.show()

and then call the function, the figure plot remains white. In fact, it never arrives to enter into the animate function.

I know that I'm missing some information, and that's why it does not work. Does anybody can give me some hints?

Thank you very much,

Andrés

like image 583
Andres Perez-Lopez Avatar asked Jan 13 '14 18:01

Andres Perez-Lopez


3 Answers

The reason that this happens is that the timers and call backs which update the window are attributes of the object ani. If you do not keep a reference to it around, then ani in garbage collected and your timers/callbacks go away.

The solution is to have your function return ani and keep a reference to it in your code:

def a(...):
    # stuff
    ani = animation.FuncAnimation(...)
    # more stuff
    return ani

outer_ani = a(...)

This issue (see github #1656) has been discussed, but not resolved.

like image 98
tacaswell Avatar answered Nov 12 '22 06:11

tacaswell


FuncAnimation should not be scraped off as garbage so just assign it to a global variable

    global anim
    
    def callfuncanimation():
        global anim
        anim = FuncAnimation(....)

as anim is a global variable, it persists and will not be scraped

like image 2
Hold My Stack Avatar answered Nov 12 '22 05:11

Hold My Stack


As tcaswell's good answer implies, the behavior of the problem code is undefined because it relies on an object which has been deleted and is available for garbage collection.

In practice, this undefined behavior manifests differently with different GUI backends. For some users (e.g. this "duplicate" question), using the Wx backend in IDLE or in the default Pylab shortcut on Windows, the undefined code seems to work (I say "seems to" because it's really not working, but rather producing the desired results by luck). When running in the Canopy GUI with its default Qt backend, the code does not work. Qt and Wx have very different architectures and garbage collection. (In Canopy, the GUI backend can be changed in the Python tab of the Preferences menu; if it is changed to Wx, then the undefined code also seems to work, but again, that doesn't make it correct.)

like image 1
Jonathan March Avatar answered Nov 12 '22 04:11

Jonathan March