EDIT/TL;DR: It looks like there is a matplotlib.backends.backend_qt4.TimerQT
object that hold a reference to my FuncAnimation object. How can I remove it to free the FuncAnimation object?
1 - A little context
I'm trying to animate a plot generated with matplotlib. I use matplotlib.animation.FuncAnimation. This animated plot is contained in a FigureCanvasQTAgg (matplotlib.backends.backend_qt4agg), ie. a PyQt4 widget.
class ViewerWidget(FigureCanvasQTAgg):
def __init__(self, viewer, parent):
# viewer is my FuncAnimation, encapsulated in a class
self._viewer = viewer
FigureCanvasQTAgg.__init__(self, viewer.figure)
When a change of configuration occure in the GUI, the Figure is cleared (figure.clf()
) and its subplots (axes and lines) replaced by new ones.
2 - Source code from Class Viewer
(encapsulating FuncAnimation
)
This is the most relevant part of my method Viewer.show(...)
, that instanciate the FuncAnimation
2.a - First, I tried:
animation.FuncAnimation(..., blit=True)
Of course, the instance was garbage collected immediatly
2.b - Then, I stored it in a class variable:
self._anim = animation.FuncAnimation(..., blit=True)
It worked for the first animation, but as soon as the configuration changed, I had artifacts from previous animations all over the new ones
2.c - So I manually added a del
:
# Delete previous FuncAnimation if any
if self._anim:
del self._anim
self._anim = animation.FuncAnimation(..., blit=True)
Nothing changed
2.d - After some debugging, I checked the garbage collector:
# DEBUG: check garbage collector
def objects_by_id(id_):
for obj in gc.get_objects():
if id(obj) == id_:
return obj
self._id.remove(id_)
return "garbage collected"
# Delete previous FuncAnimation if any
if self._anim:
del self._anim
# DEBUG
print "-"*10
for i in self._id.copy():
print i, objects_by_id(i)
print "-"*10
self._anim = animation.FuncAnimation(self._figure_handler.figure,
update,
init_func=init,
interval=self._update_anim,
blit=True)
# DEBUG: store ids only, to enable object being garbage collected
self._anim_id.add(id(anim))
After 3 configuration changes, it showed:
----------
140488264081616 <matplotlib.animation.FuncAnimation object at 0x7fc5f91360d0>
140488264169104 <matplotlib.animation.FuncAnimation object at 0x7fc5f914b690>
140488145151824 <matplotlib.animation.FuncAnimation object at 0x7fc5f1fca750>
140488262315984 <matplotlib.animation.FuncAnimation object at 0x7fc5f8f86fd0>
----------
So, it confirmed that none of the FuncAnimation were garbage collected
2.e - Last try, with weakref:
# DEBUG: check garbage collector
def objects_by_id(id_):
for obj in gc.get_objects():
if id(obj) == id_:
return obj
self._id.remove(id_)
return "garbage collected"
# Delete previous FuncAnimation if any
if self._anim_ref:
anim = self._anim_ref()
del anim
# DEBUG
print "-"*10
for i in self._id.copy():
print i, objects_by_id(i)
print "-"*10
anim = animation.FuncAnimation(self._figure_handler.figure,
update,
init_func=init,
interval=self._update_anim,
blit=True)
self._anim_ref = weakref.ref(anim)
# DEBUG: store ids only, to enable object being garbage collected
self._id.add(id(anim))
This time, logs where confusing, I'm not sure what's going on.
----------
140141921353872 <built-in method alignment>
----------
----------
140141921353872 <built-in method alignment>
140141920643152 Bbox('array([[ 0., 0.],\n [ 1., 1.]])')
----------
----------
140141921353872 <built-in method alignment>
140141920643152 <viewer.FftPlot object at 0x7f755565e850>
140141903645328 Bbox('array([[ 0., 0.],\n [ 1., 1.]])')
----------
(...)
Where are my <matplotlib.animation.FuncAnimation object at 0x...>
?
There were no more previous animation artifacts, so far so good, but... FuncAnimation is no longer able to execute the "update". Only the "init" part. My guess is the FuncAnimation is garbage collected as soon as the method Viewer.show(...)
returns, sinces anim
ids are already recycled.
3 - Help
I don't know where to look from here. Any suggestion?
EDIT: I installed objgraph to visualize all back references to FuncAnimation, I got this:
import objgraph, time
objgraph.show_backrefs([self._anim],
max_depth=5,
filename="/tmp/debug/func_graph_%d.png"
% int(time.time()))
So, there is a matplotlib.backends.backend_qt4.TimerQT
that still hold a reference. Any way to remove it?