9

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

cel
  • 30,017
  • 18
  • 97
  • 117
nicoguaro
  • 3,629
  • 1
  • 32
  • 57
  • Did you see this http://blog.adrian.pw/posts/2016/06/matplotlib-transparent-animation/ ? It seems that what you are looking for is `fig.patch.set_alpha(0.)` and something like `ani.save('circle_anim.mov', codec="png", dpi=100, bitrate=-1, savefig_kwargs={'transparent': True, 'facecolor': 'none'})`. I can't try right now but will give it a go later. – Jacques Gaudin Sep 27 '16 at 12:15
  • @JacquesGaudin, I just checked it. It is cool, but it does not export the image as GIF. Indeed, ``savefig_kwargs`` are ignored when using the GIF writer. – nicoguaro Sep 27 '16 at 12:59
  • @JacquesGaudin, just tried: does not work. – nicoguaro Sep 27 '16 at 14:01
  • You may want to read this https://github.com/matplotlib/matplotlib/issues/5335. It seems that transparency for gif animation was removed all together. – Jacques Gaudin Sep 27 '16 at 14:30
  • @JacquesGaudin, in that case it is a bug. Because the first frame still has transparency – nicoguaro Sep 27 '16 at 15:00
  • 1
    "I believe matplotlib saves each frame as a png and then sends those pngs to be saved as an animation through convert (imagemagick) or ffmpeg. I believe the issue here is that while gifs properly support transparency, they only support either full alpha or no alpha, which forces imagemagick to decide between alpha or no alpha for each intermediate alpha which gives a pixelated appearance." and "Perhaps we can just disable transparency for gif files all together (I don't think matplotlib can properly handle boolean alpha levels) and then set the default frame format to gif files for animations" – Jacques Gaudin Sep 27 '16 at 15:08

1 Answers1

6

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.

ImportanceOfBeingErnest
  • 321,279
  • 53
  • 665
  • 712
  • I was thinking about this option, but was hoping that someone could do it the other way. To make work your answer I changed the ``args`` line to ``args = [path_to_convert, "drum_*.png", "-delay", "10", "-loop" , "0", "-dispose", "Background", "output.gif"]``. – nicoguaro Sep 27 '16 at 19:35
  • It would be great if we could add something like ``subprocess.call(["rm", "drum_*.png"], shell=False)`` to erase the PNG files and just keep the GIF. – nicoguaro Sep 27 '16 at 19:36
  • I added a line to get rid of the png files. Concerning the change in order of the arguments to convert.exe, I have no idea. It does work on my machine the way it's written above. – ImportanceOfBeingErnest Sep 27 '16 at 19:56