python matplotlib: unable to call FuncAnimation from inside a function
Asked Answered
B

4

14

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

Blondellblondelle answered 13/1, 2014 at 18:56 Comment(1)
Both codes work fine for me. Please also post the code calling a.Gybe
M
19

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.

Manchester answered 14/1, 2014 at 14:52 Comment(7)
But doesn't plt.show() block until the window is closed? The function a() is only left after plt.show() returns. Before that garbage collection of ani can not happen.Gybe
plt.show() can be non-blocking, for example if you are running the code interactively in a shellManchester
With matplotlib 1.3.0 and python 3.2.5 plt.show() is blocking in any case for me except if I provide block=False as argument, but if I do so the plot does show up only in interactive mode and keeps animating only while the shell is idle waiting for user input. Even in this case both code blocks from the question still work fine for me. Is there maybe a version dependence of this issue or do I miss something obvious?Gybe
It is probably also backend dependent. I normally use 2.7 so I could also believe that the gc logic varies (and I don't think there are any guarantees as to when gc runs anyway). Getting the event loops for interactive backends to work is still a bit of a black box for me (particularity anything that is not QT). Further, I normally work in ipython which does some magic on start up, which I think includes making show non-blocking by default (I would have to dig into the code to sort out the mechanism though).Manchester
Thank you very much, it worked when returning ani. It was my fault that I did not specificate the code I use to run it. Efectively, it was called from iPython, and the first code worked fine for vanilla PythonBlondellblondelle
If this solved your problem please accept the answer (big check Mark on the left)Manchester
@JonathanMarch Comments are not stable and can't get you rep. You really should post a version of those comments as an answer here (or edit my answer).Manchester
R
3

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

Rubel answered 19/7, 2020 at 13:47 Comment(0)
R
1

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.)

Ramage answered 29/8, 2014 at 17:53 Comment(0)
T
0

As Fixed 1656: Animation is Garbage Collected if not explicitly stored #8214 said, Python's garbage collection will remove your animation if it is not explicitly stored.
If you want to get it in the main function, you can do it just like @tacaswell .However, this fails when you want to generate your animation in another function.
We can define a global var to store the return value of FuncAnimation.

global anim

def return_anim():
  anim = animation.FuncAnimation(fig, animate, blit=True)
  return anim

def use_ani():
  global anim
  anim = return_anim

use_ani()

Please must remember to set blit=True, otherwise Python won't redraw your animation.

Translate answered 29/7, 2019 at 8:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.