4

The animation module in matplotlib usually requires third party modules like FFmpeg, mencoder or imagemagik to be able to save the animation to a file (e.g. here: https://stackoverflow.com/a/25143651/5082048).

Even the MovieWriter class in matplotlib seems to be build in a way that third party modules will be incorporated (starting and closing processes, cummunicating via pipe): http://matplotlib.org/api/animation_api.html#matplotlib.animation.MovieWriter.

I am looking for a way, how I can save a matplotlib.animation.FuncAnimation object frame to frame to png - directly, within python. Afterwards, I want to show the .png files as animation in an iPython notebook using this approach: https://github.com/PBrockmann/ipython_animation_javascript_tool/

Therefore my questions are:

  • How can I save an matplotlib.animation.FuncAnimation object directly to .png files without the need to use third party modules?
  • Is there a writer class implemented for this usecase?
  • How can I get figure objects frame by frame out of the FuncAnimation object (so that I could save them myself)?

Edit: The matplotlib.animation.FuncAnimation object is given, the task is to save it's frames using pure Python. Unfortunately, I cannot change the underlying animation function as ImportanceOfBeingErnest suggested.

Community
  • 1
  • 1
Arco Bast
  • 3,595
  • 2
  • 26
  • 53

3 Answers3

5

Althoough this may seem a bit complicated, saving the frames of an animation may be easily done within the animation itself.

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

def animate(i):
    line.set_ydata(np.sin(2*np.pi*i / 50)*np.sin(x))
    #fig.canvas.draw() not needed see comment by @tacaswell
    plt.savefig(str(i)+".png")
    return line,

fig = plt.figure()
ax = fig.add_subplot(111)
ax.set_xlim(0, 2*np.pi)
ax.set_ylim(-1,1)
x = np.linspace(0, 2*np.pi, 200)
line, = ax.plot(x, np.zeros_like(x))
plt.draw()

ani = matplotlib.animation.FuncAnimation(fig, animate, frames=5, repeat=False)
plt.show()

Mind the repeat = False argument, which will prevent the animation to run continuously and repeat writing the same files to disk.

Note that if you're willing to loosen the restriction of "no external package" you may use imagemagick to save the pngs

ani.save("anim.png", writer="imagemagick")

which will save files anim-1.png, anim-2.png etc.

Finally note that there are of course easier methods to show an animation in a jupyter notebook.

ImportanceOfBeingErnest
  • 321,279
  • 53
  • 665
  • 712
  • Yes, you are absolutely right. The reason why I do not go for this approach is, that I basically only have direct access to the ani object, while everything else is already set up. Therefore I am especially looking for a way to get access to that object directly. – Arco Bast Dec 19 '16 at 21:28
  • I see. That would be an important piece of information to add in the question. Although it should in principle be an easy task because every moviewriter has to save the temporary images somewhere, I'm currently not aware of any other solution. – ImportanceOfBeingErnest Dec 19 '16 at 22:55
  • Again you are right. Sorry, I was not aware that my question was not clear. – Arco Bast Dec 19 '16 at 23:22
  • Don't do that, it is a bad idea. If you really do want to do it this way you don't need the `draw` as the save forces a full re-draw anyway. – tacaswell Dec 21 '16 at 22:42
  • 2
    @tacaswell I don't see why this should be a bad idea. There is nothing wrong with that method, as far as I can see and, more importantly, it's the only way I currently see to save the animation to a bunch of png files. You are very welcome to provide a better method, if there is one. And of course it would be even better to have this method documented somewhere in the matplotlib docs. – ImportanceOfBeingErnest Dec 21 '16 at 22:54
  • Because there is a strait forward way to do this (see my answer). The default behavior is to loop forever, so this will keep re-writing the files over and over which is less than ideal. Just because something _works_ does not mean it is a good idea. – tacaswell Dec 21 '16 at 23:00
  • 2
    @tacaswell Good point, I edited the answer accordingly. Apart from that, I think that you are bending the notions of "straight forward" and "bad idea" a bit too much. In conclusion, let me just say that it would be really helpful to have a well documented method in the sense of `ani.save(some arguments to allow pngs to be saved)`. – ImportanceOfBeingErnest Dec 21 '16 at 23:23
  • That would be to ship a (tested) version of the class I wrote in my answer. – tacaswell Dec 21 '16 at 23:43
2

You want to look at the FileMovieWriter sub-classes (See http://matplotlib.org/2.0.0rc2/api/animation_api.html#writer-classes) You probably want to sub-class FileMoveWriter, something like

import matplotlib.animation as ma


class BunchOFiles(ma.FileMovieWriter):
    def setup(self, fig, dpi, frame_prefix):
        super().setup(fig, dpi, frame_prefix, clear_temp=False)

    def _run(self):
        # Uses subprocess to call the program for assembling frames into a
        # movie file.  *args* returns the sequence of command line arguments
        # from a few configuration options.
        pass

    def grab_frame(self, **savefig_kwargs):
        '''
        Grab the image information from the figure and save as a movie frame.
        All keyword arguments in savefig_kwargs are passed on to the 'savefig'
        command that saves the figure.
        '''

        # Tell the figure to save its data to the sink, using the
        # frame format and dpi.
        with self._frame_sink() as myframesink:
            self.fig.savefig(myframesink, format=self.frame_format,
                             dpi=self.dpi, **savefig_kwargs)

    def cleanup(self):
        # explictily skip a step in the mro
        ma.MovieWriter.cleanup(self)

(this is not tested, might be better to just implement a class that implements saving, grab_frame, finished, and setup)

tacaswell
  • 84,579
  • 22
  • 210
  • 199
2

I could not get tacaswell's answer to work without modification. So, here is my take at it.

from matplotlib.animation import FileMovieWriter


class BunchOFiles(FileMovieWriter):
    supported_formats = ['png', 'jpeg', 'bmp', 'svg', 'pdf']

    def __init__(self, *args, extra_args=None, **kwargs):
        # extra_args aren't used but we need to stop None from being passed
        super().__init__(*args, extra_args=(), **kwargs)

    def setup(self, fig, dpi, frame_prefix):
        super().setup(fig, dpi, frame_prefix, clear_temp=False)
        self.fname_format_str = '%s%%d.%s'
        self.temp_prefix, self.frame_format = self.outfile.split('.')

    def grab_frame(self, **savefig_kwargs):
        '''
        Grab the image information from the figure and save as a movie frame.
        All keyword arguments in savefig_kwargs are passed on to the 'savefig'
        command that saves the figure.
        '''

        # Tell the figure to save its data to the sink, using the
        # frame format and dpi.
        with self._frame_sink() as myframesink:
            self.fig.savefig(myframesink, format=self.frame_format,
                             dpi=self.dpi, **savefig_kwargs)

    def finish(self):
        self._frame_sink().close()

We can save a set of files with:

anim.save('filename.format', writer=BunchOFiles())

and it will save the files in the form 'filename{number}.format'.

tmakaro
  • 151
  • 4