Search code examples
pythonmatplotlibanimated-gifmplot3d

mplot3d animation with transparent background


I want to generate some gifs with transparent background using matplotlib. I tried different options but I can't get my files with transparent background. With the current setup I get the first frame like that but not the rest. The following is my code

from __future__ import division
from numpy import pi, sin, cos, mgrid
from scipy.special import jn, jn_zeros
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from matplotlib import rcParams


# In Windows the next line should provide the full path to convert.exe
# since convert is a Windows command
rcParams['animation.convert_path'] = "C:\Program Files\ImageMagick-6.9.3\convert.exe"
rcParams['savefig.transparent'] = True
rcParams['savefig.dpi'] = 130
rcParams['savefig.pad_inches'] = 0
plot_args = {'rstride': 1, 'cstride': 1, 'cmap':"RdYlBu",
             'linewidth': 0.5, 'antialiased': True, 'color': '#1e1e1e',
             'shade': True, 'alpha': 1.0, 'vmin': -1, 'vmax':1}


def data_gen(num):
    ax.cla()
    m, n = 1, 2
    lam = jn_zeros(m, n)[-1]
    dt = 2*pi/(30*lam)
    z = cos(m*t)*jn(m, lam*r)*sin(lam*num*dt)
    surf = ax.plot_surface(x, y, z, **plot_args)
    ax.view_init(elev=30, azim=45)
    ax.set_xlim(-0.6, 0.6)
    ax.set_ylim(-0.6, 0.6)
    ax.set_zlim(-1, 1)
    plt.axis("off")
    return surf


r, t = mgrid[0:1:20j, 0:2*pi:40j]
x, y = r*cos(t), r*sin(t)
fig = plt.figure(facecolor=None)
ax = fig.add_subplot(111, projection='3d')
ani = animation.FuncAnimation(fig, data_gen, range(30), blit=False)
ani.save("Drum vibration mode.gif", writer='imagemagick')

That gives as a result (if you click on it, you can see that just one frame is transparent)

Question: Is there a way to get the animation with transparent background using mplot3d?

enter image description here


Solution

  • I think it's really a bug. However, if you rather care about the result than the way to get there, the following would do the job. Instead of calling animation, you can save each image separately and then call imageMagick to convert them to an animated gif. See the code below and mind the arguments to convert.exe.

    from __future__ import division
    from numpy import pi, sin, cos, mgrid
    from scipy.special import jn, jn_zeros
    from mpl_toolkits.mplot3d import Axes3D
    import matplotlib.pyplot as plt
    
    from matplotlib import rcParams
    import subprocess
    
    
    # In Windows the next line should provide the full path to convert.exe
    # since convert is a Windows command
    path_to_convert =  "C:\Program Files\ImageMagick-6.9.3\convert.exe"
    #rcParams['animation.convert_path'] = path_to_convert
    rcParams['savefig.transparent'] = True
    rcParams['savefig.dpi'] = 130
    rcParams['savefig.pad_inches'] = 0
    plot_args = {'rstride': 1, 'cstride': 1, 'cmap':"RdYlBu",
                 'linewidth': 0.5, 'antialiased': True, 'color': '#1e1e1e',
                 'shade': True, 'alpha': 1.0, 'vmin': -1, 'vmax':1}
    
    
    def data_gen(num):
        ax.cla()
        ax.clear()
        m, n = 1, 2
        lam = jn_zeros(m, n)[-1]
        dt = 2*pi/(30*lam)
        z = cos(m*t)*jn(m, lam*r)*sin(lam*num*dt)
        surf = ax.plot_surface(x, y, z, **plot_args)
        ax.view_init(elev=30, azim=45)
        ax.set_xlim(-0.6, 0.6)
        ax.set_ylim(-0.6, 0.6)
        ax.set_zlim(-1, 1)
        ax.axis("off")
        fig.patch.set_visible(False)
        ax.patch.set_visible(False)
        ax.set_axis_off()
        ax._axis3don = False
        return surf
    
    
    r, t = mgrid[0:1:20j, 0:2*pi:40j]
    x, y = r*cos(t), r*sin(t)
    fig = plt.figure(facecolor=None, frameon=False)
    ax = fig.add_subplot(111, projection='3d')
    for i in range(30):
        data_gen(i)
        plt.savefig("drum_{n:02d}.png".format(n=i), transparent=True,  frameon=False)
        print i, 
    
    args = [path_to_convert, "-delay", "10", "-loop" , "0", "-dispose", "Background", "drum_*.png", "output.gif"]
    subprocess.call(args, shell=True)
    subprocess.call(["del", "/Q", "drum_*.png"], shell=True)
    print "\ndone"
    

    Mind that the calls to imageMagick and the delete command might be system dependend. This script has been tested with imageMagick 7.0.3 (Q16) under Windows 8.