NMech
NMech

Reputation: 600

Animation stops working when upgrading to matplotlib 3.3.2

I have a piece of Python code for an animated visualisation that stopped working recently. I have tracked the problem to an upgrade from matplotlib 3.2.2 to 3.3.2. I compared the documentations of matplotlib 3.2.2 and 3.3.2, and the main changes are the HMTLAnimationWriter and errata in the examples. I see no reason why my code should stop working.

I have raised a bug report at matplotlib

A) Expected behaviour

The code at the end of the post moves a line up and down in the y direction. It does that successfully with matplotlib 3.2.2, but not with 3.3.2. This is reproducible in different settings.

enter image description here

B) Problem

When I use matplotlib 3.3.2, an empty figure opens.

output with matplotlib 3.3.2

When I run the code from the command line (eg. python test.py) with matplotlib 3.2.2., I get no Depreciation Warning, or any other error messages. Similar for matplotlib 3.3.2. This has been tested with

In my two cases, when I upgraded the problem appeared, and when I downgraded the problem disappeared. So, my guess is that the problem lies with matplotlib 3.3.2.

An interesting point, is that if I use Pillowriter to create an animated gif, the resulting gif performs as expected in all versions of matplotlib.

Questions

  1. Is this a bug or a feature of the API?
  2. If its a feature, what is the new recommended approach for animations (preferable with backwards compatibility). Seemingly, not all FuncAnimation() commands are affected. This works with matplotlib 3.3.2.
  3. If its a bug, is there a workaround?

CODE

I am including an MCVE code that reproduces the main problem and works with matplotlib 3.2.2, but not with 3.3.2.

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

def create_animated_plot(xs, niter, xlim =(-1.5, 1.5), ylim = [-3, 3]):

    line_coords = np.vstack((  np.array([-1,1]), np.array([0,0]) ))
    
    boxData = [np.array([np.zeros(niter), xs])]

    fig, ax1 = plt.subplots(1,1)
    line, = ax1.plot([], [], lw=2)
    ax1.set_ylabel('position')
    ax1.set_ylim(ylim)
    ax1.set_xlim(xlim)

    lines = [ax1.plot([],[],lw=2,color="black")[0]]

    def init():
        lines[0].set_data([],[])
        return lines

    def animate(i):
        xs = [boxData[0][0, i]]
        ys = [boxData[0][1, i]]

        lines[0].set_data(line_coords[0,:]+xs[0], line_coords[1,:]+ys[0]) 
        return lines

    anim = animation.FuncAnimation(fig, animate, init_func=init,
                                frames=niter, interval=10, blit=True)


niter = 1001
t = np.linspace(0,10,niter)

create_animated_plot(xs =np.sin(t), niter=niter)
plt.show()


Additional info

The following are images from my original code (which I reduced to the MCVE code in this post):

- [animated gif or working example of original code]( https://i.sstatic.net/SLYn7.gif)

- [png of the output with matplotlib 3.3.2]( https://i.sstatic.net/wRO90.png)

Please note that the MCVE code posted here just creates the upper frame of the two graphs and shows a bouncing line with matplotlib ≤3.2.2, but an empty frame with matplotlib 3.3.2.

Upvotes: 4

Views: 562

Answers (1)

NMech
NMech

Reputation: 600

The problem was that the gc now collects more actively the references to animation objects. This is related to bug

An easy workaround is to pass out the reference like:

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

def create_animated_plot(xs, niter, xlim =(-1.5, 1.5), ylim = [-3, 3]):

    line_coords = np.vstack((  np.array([-1,1]), np.array([0,0]) ))
    
    boxData = [np.array([np.zeros(niter), xs])]

    fig, ax1 = plt.subplots(1,1)
    line, = ax1.plot([], [], lw=2)
    ax1.set_ylabel('position')
    ax1.set_ylim(ylim)
    ax1.set_xlim(xlim)

    lines = [ax1.plot([],[],lw=2,color="black")[0]]

    def init():
        lines[0].set_data([],[])
        return lines

    def animate(i):
        xs = [boxData[0][0, i]]
        ys = [boxData[0][1, i]]

        lines[0].set_data(line_coords[0,:]+xs[0], line_coords[1,:]+ys[0]) 
        return lines

    anim = animation.FuncAnimation(fig, animate, init_func=init,
                                frames=niter, interval=10, blit=True)
    return anim  # return anim object reference ########################################################

niter = 1001
t = np.linspace(0,10,niter)

anim = create_animated_plot(xs =np.sin(t), niter=niter)   # assign to variable ###########################
plt.show()

If you have a class, you could also assign the animation object to self.

NOTE: I could not have come up with this answer without the help and patience of Mr. T

Upvotes: 2

Related Questions