matplotlib  1.5.3
About: matplotlib is a python 2D plotting library which produces publication quality figures in a variety of hardcopy formats and interactive environments across platforms.
  Fossies Dox: matplotlib-1.5.3.tar.gz  ("inofficial" and yet experimental doxygen-generated source code documentation)  

animation.py
Go to the documentation of this file.
1 # TODO:
2 # * Loop Delay is broken on GTKAgg. This is because source_remove() is not
3 # working as we want. PyGTK bug?
4 # * Documentation -- this will need a new section of the User's Guide.
5 # Both for Animations and just timers.
6 # - Also need to update http://www.scipy.org/Cookbook/Matplotlib/Animations
7 # * Blit
8 # * Currently broken with Qt4 for widgets that don't start on screen
9 # * Still a few edge cases that aren't working correctly
10 # * Can this integrate better with existing matplotlib animation artist flag?
11 # - If animated removes from default draw(), perhaps we could use this to
12 # simplify initial draw.
13 # * Example
14 # * Frameless animation - pure procedural with no loop
15 # * Need example that uses something like inotify or subprocess
16 # * Complex syncing examples
17 # * Movies
18 # * Can blit be enabled for movies?
19 # * Need to consider event sources to allow clicking through multiple figures
20 from __future__ import (absolute_import, division, print_function,
21  unicode_literals)
22 
23 from matplotlib.externals import six
24 from matplotlib.externals.six.moves import xrange, zip
25 
26 import os
27 import platform
28 import sys
29 import itertools
30 try:
31  # python3
32  from base64 import encodebytes
33 except ImportError:
34  # python2
35  from base64 import encodestring as encodebytes
36 import contextlib
37 import tempfile
38 import warnings
39 from matplotlib.cbook import iterable, is_string_like
40 from matplotlib.compat import subprocess
41 from matplotlib import verbose
42 from matplotlib import rcParams, rcParamsDefault, rc_context
43 
44 # Process creation flag for subprocess to prevent it raising a terminal
45 # window. See for example:
46 # https://stackoverflow.com/questions/24130623/using-python-subprocess-popen-cant-prevent-exe-stopped-working-prompt
47 if platform.system() == 'Windows':
48  CREATE_NO_WINDOW = 0x08000000
49  subprocess_creation_flags = CREATE_NO_WINDOW
50 else:
51  # Apparently None won't work here
52  subprocess_creation_flags = 0
53 
54 # Other potential writing methods:
55 # * http://pymedia.org/
56 # * libmng (produces swf) python wrappers: https://github.com/libming/libming
57 # * Wrap x264 API:
58 
59 # (http://stackoverflow.com/questions/2940671/
60 # how-to-encode-series-of-images-into-h264-using-x264-api-c-c )
61 
62 
63 # A registry for available MovieWriter classes
64 class MovieWriterRegistry(object):
65  def __init__(self):
66  self.avail = dict()
67 
68  # Returns a decorator that can be used on classes to register them under
69  # a name. As in:
70  # @register('foo')
71  # class Foo:
72  # pass
73  def register(self, name):
74  def wrapper(writerClass):
75  if writerClass.isAvailable():
76  self.avail[name] = writerClass
77  return writerClass
78  return wrapper
79 
80  def list(self):
81  ''' Get a list of available MovieWriters.'''
82  return list(self.avail.keys())
83 
84  def is_available(self, name):
85  return name in self.avail
86 
87  def __getitem__(self, name):
88  if not self.avail:
89  raise RuntimeError("No MovieWriters available!")
90  return self.avail[name]
91 
93 
94 
95 class MovieWriter(object):
96  '''
97  Base class for writing movies. Fundamentally, what a MovieWriter does
98  is provide is a way to grab frames by calling grab_frame(). setup()
99  is called to start the process and finish() is called afterwards.
100  This class is set up to provide for writing movie frame data to a pipe.
101  saving() is provided as a context manager to facilitate this process as::
102 
103  with moviewriter.saving('myfile.mp4'):
104  # Iterate over frames
105  moviewriter.grab_frame()
106 
107  The use of the context manager ensures that setup and cleanup are
108  performed as necessary.
109 
110  frame_format: string
111  The format used in writing frame data, defaults to 'rgba'
112  '''
113 
114  # Specifies whether the size of all frames need to be identical
115  # i.e. whether we can use savefig.bbox = 'tight'
116  frame_size_can_vary = False
117 
118  def __init__(self, fps=5, codec=None, bitrate=None, extra_args=None,
119  metadata=None):
120  '''
121  Construct a new MovieWriter object.
122 
123  fps: int
124  Framerate for movie.
125  codec: string or None, optional
126  The codec to use. If None (the default) the setting in the
127  rcParam `animation.codec` is used.
128  bitrate: int or None, optional
129  The bitrate for the saved movie file, which is one way to control
130  the output file size and quality. The default value is None,
131  which uses the value stored in the rcParam `animation.bitrate`.
132  A value of -1 implies that the bitrate should be determined
133  automatically by the underlying utility.
134  extra_args: list of strings or None
135  A list of extra string arguments to be passed to the underlying
136  movie utility. The default is None, which passes the additional
137  arguments in the 'animation.extra_args' rcParam.
138  metadata: dict of string:string or None
139  A dictionary of keys and values for metadata to include in the
140  output file. Some keys that may be of use include:
141  title, artist, genre, subject, copyright, srcform, comment.
142  '''
143  self.fps = fps
144  self.frame_format = 'rgba'
145 
146  if codec is None:
147  self.codec = rcParams['animation.codec']
148  else:
149  self.codec = codec
150 
151  if bitrate is None:
152  self.bitrate = rcParams['animation.bitrate']
153  else:
154  self.bitrate = bitrate
155 
156  if extra_args is None:
157  self.extra_args = list(rcParams[self.args_key])
158  else:
159  self.extra_args = extra_args
160 
161  if metadata is None:
162  self.metadata = dict()
163  else:
164  self.metadata = metadata
165 
166  @property
167  def frame_size(self):
168  'A tuple (width,height) in pixels of a movie frame.'
169  width_inches, height_inches = self.fig.get_size_inches()
170  return width_inches * self.dpi, height_inches * self.dpi
171 
172  def setup(self, fig, outfile, dpi, *args):
173  '''
174  Perform setup for writing the movie file.
175 
176  fig: `matplotlib.Figure` instance
177  The figure object that contains the information for frames
178  outfile: string
179  The filename of the resulting movie file
180  dpi: int
181  The DPI (or resolution) for the file. This controls the size
182  in pixels of the resulting movie file.
183  '''
184  self.outfile = outfile
185  self.fig = fig
186  self.dpi = dpi
187 
188  # Run here so that grab_frame() can write the data to a pipe. This
189  # eliminates the need for temp files.
190  self._run()
191 
192  @contextlib.contextmanager
193  def saving(self, *args):
194  '''
195  Context manager to facilitate writing the movie file.
196 
197  ``*args`` are any parameters that should be passed to `setup`.
198  '''
199  # This particular sequence is what contextlib.contextmanager wants
200  self.setup(*args)
201  yield
202  self.finish()
203 
204  def _run(self):
205  # Uses subprocess to call the program for assembling frames into a
206  # movie file. *args* returns the sequence of command line arguments
207  # from a few configuration options.
208  command = self._args()
209  if verbose.ge('debug'):
210  output = sys.stdout
211  else:
212  output = subprocess.PIPE
213  verbose.report('MovieWriter.run: running command: %s' %
214  ' '.join(command))
215  self._proc = subprocess.Popen(command, shell=False,
216  stdout=output, stderr=output,
217  stdin=subprocess.PIPE,
218  creationflags=subprocess_creation_flags)
219 
220  def finish(self):
221  'Finish any processing for writing the movie.'
222  self.cleanup()
223 
224  def grab_frame(self, **savefig_kwargs):
225  '''
226  Grab the image information from the figure and save as a movie frame.
227  All keyword arguments in savefig_kwargs are passed on to the 'savefig'
228  command that saves the figure.
229  '''
230  verbose.report('MovieWriter.grab_frame: Grabbing frame.',
231  level='debug')
232  try:
233  # Tell the figure to save its data to the sink, using the
234  # frame format and dpi.
235  self.fig.savefig(self._frame_sink(), format=self.frame_format,
236  dpi=self.dpi, **savefig_kwargs)
237  except (RuntimeError, IOError) as e:
238  out, err = self._proc.communicate()
239  verbose.report('MovieWriter -- Error '
240  'running proc:\n%s\n%s' % (out,
241  err), level='helpful')
242  raise IOError('Error saving animation to file (cause: {0}) '
243  'Stdout: {1} StdError: {2}. It may help to re-run '
244  'with --verbose-debug.'.format(e, out, err))
245 
246  def _frame_sink(self):
247  'Returns the place to which frames should be written.'
248  return self._proc.stdin
249 
250  def _args(self):
251  'Assemble list of utility-specific command-line arguments.'
252  return NotImplementedError("args needs to be implemented by subclass.")
253 
254  def cleanup(self):
255  'Clean-up and collect the process used to write the movie file.'
256  out, err = self._proc.communicate()
257  self._frame_sink().close()
258  verbose.report('MovieWriter -- '
259  'Command stdout:\n%s' % out, level='debug')
260  verbose.report('MovieWriter -- '
261  'Command stderr:\n%s' % err, level='debug')
262 
263  @classmethod
264  def bin_path(cls):
265  '''
266  Returns the binary path to the commandline tool used by a specific
267  subclass. This is a class method so that the tool can be looked for
268  before making a particular MovieWriter subclass available.
269  '''
270  return rcParams[cls.exec_key]
271 
272  @classmethod
273  def isAvailable(cls):
274  '''
275  Check to see if a MovieWriter subclass is actually available by
276  running the commandline tool.
277  '''
278  if not cls.bin_path():
279  return False
280  try:
281  p = subprocess.Popen(cls.bin_path(),
282  shell=False,
283  stdout=subprocess.PIPE,
284  stderr=subprocess.PIPE,
285  creationflags=subprocess_creation_flags)
286  p.communicate()
287  return True
288  except OSError:
289  return False
290 
291 
293  '`MovieWriter` subclass that handles writing to a file.'
294 
295  # In general, if frames are writen to files on disk, it's not important
296  # that they all be identically sized
297  frame_size_can_vary = True
298 
299  def __init__(self, *args, **kwargs):
300  MovieWriter.__init__(self, *args, **kwargs)
301  self.frame_format = rcParams['animation.frame_format']
302 
303  def setup(self, fig, outfile, dpi, frame_prefix='_tmp', clear_temp=True):
304  '''
305  Perform setup for writing the movie file.
306 
307  fig: `matplotlib.Figure` instance
308  The figure object that contains the information for frames
309  outfile: string
310  The filename of the resulting movie file
311  dpi: int
312  The DPI (or resolution) for the file. This controls the size
313  in pixels of the resulting movie file.
314  frame_prefix: string, optional
315  The filename prefix to use for the temporary files. Defaults
316  to '_tmp'
317  clear_temp: bool
318  Specifies whether the temporary files should be deleted after
319  the movie is written. (Useful for debugging.) Defaults to True.
320  '''
321  self.fig = fig
322  self.outfile = outfile
323  self.dpi = dpi
324  self.clear_temp = clear_temp
325  self.temp_prefix = frame_prefix
326  self._frame_counter = 0 # used for generating sequential file names
327  self._temp_names = list()
328  self.fname_format_str = '%s%%07d.%s'
329 
330  @property
331  def frame_format(self):
332  '''
333  Format (png, jpeg, etc.) to use for saving the frames, which can be
334  decided by the individual subclasses.
335  '''
336  return self._frame_format
337 
338  @frame_format.setter
339  def frame_format(self, frame_format):
340  if frame_format in self.supported_formats:
341  self._frame_format = frame_format
342  else:
343  self._frame_format = self.supported_formats[0]
344 
345  def _base_temp_name(self):
346  # Generates a template name (without number) given the frame format
347  # for extension and the prefix.
348  return self.fname_format_str % (self.temp_prefix, self.frame_format)
349 
350  def _frame_sink(self):
351  # Creates a filename for saving using the basename and the current
352  # counter.
353  fname = self._base_temp_name() % self._frame_counter
354 
355  # Save the filename so we can delete it later if necessary
356  self._temp_names.append(fname)
357  verbose.report(
358  'FileMovieWriter.frame_sink: saving frame %d to fname=%s' %
359  (self._frame_counter, fname),
360  level='debug')
361  self._frame_counter += 1 # Ensures each created name is 'unique'
362 
363  # This file returned here will be closed once it's used by savefig()
364  # because it will no longer be referenced and will be gc-ed.
365  return open(fname, 'wb')
366 
367  def grab_frame(self, **savefig_kwargs):
368  '''
369  Grab the image information from the figure and save as a movie frame.
370  All keyword arguments in savefig_kwargs are passed on to the 'savefig'
371  command that saves the figure.
372  '''
373  # Overloaded to explicitly close temp file.
374  verbose.report('MovieWriter.grab_frame: Grabbing frame.',
375  level='debug')
376  try:
377  # Tell the figure to save its data to the sink, using the
378  # frame format and dpi.
379  myframesink = self._frame_sink()
380  self.fig.savefig(myframesink, format=self.frame_format,
381  dpi=self.dpi, **savefig_kwargs)
382  myframesink.close()
383 
384  except RuntimeError:
385  out, err = self._proc.communicate()
386  verbose.report('MovieWriter -- Error '
387  'running proc:\n%s\n%s' % (out,
388  err), level='helpful')
389  raise
390 
391  def finish(self):
392  # Call run here now that all frame grabbing is done. All temp files
393  # are available to be assembled.
394  self._run()
395  MovieWriter.finish(self) # Will call clean-up
396 
397  # Check error code for creating file here, since we just run
398  # the process here, rather than having an open pipe.
399  if self._proc.returncode:
400  raise RuntimeError('Error creating movie, return code: '
401  + str(self._proc.returncode)
402  + ' Try running with --verbose-debug')
403 
404  def cleanup(self):
405  MovieWriter.cleanup(self)
406 
407  # Delete temporary files
408  if self.clear_temp:
409  verbose.report(
410  'MovieWriter: clearing temporary fnames=%s' %
411  str(self._temp_names),
412  level='debug')
413  for fname in self._temp_names:
414  os.remove(fname)
415 
416 
417 # Base class of ffmpeg information. Has the config keys and the common set
418 # of arguments that controls the *output* side of things.
419 class FFMpegBase(object):
420  exec_key = 'animation.ffmpeg_path'
421  args_key = 'animation.ffmpeg_args'
422 
423  @property
424  def output_args(self):
425  args = ['-vcodec', self.codec]
426  # For h264, the default format is yuv444p, which is not compatible
427  # with quicktime (and others). Specifying yuv420p fixes playback on
428  # iOS,as well as HTML5 video in firefox and safari (on both Win and
429  # OSX). Also fixes internet explorer. This is as of 2015/10/29.
430  if self.codec == 'h264' and '-pix_fmt' not in self.extra_args:
431  args.extend(['-pix_fmt', 'yuv420p'])
432  # The %dk adds 'k' as a suffix so that ffmpeg treats our bitrate as in
433  # kbps
434  if self.bitrate > 0:
435  args.extend(['-b', '%dk' % self.bitrate])
436  if self.extra_args:
437  args.extend(self.extra_args)
438  for k, v in six.iteritems(self.metadata):
439  args.extend(['-metadata', '%s=%s' % (k, v)])
440 
441  return args + ['-y', self.outfile]
442 
443 
444 # Combine FFMpeg options with pipe-based writing
445 @writers.register('ffmpeg')
447  def _args(self):
448  # Returns the command line parameters for subprocess to use
449  # ffmpeg to create a movie using a pipe.
450  args = [self.bin_path(), '-f', 'rawvideo', '-vcodec', 'rawvideo',
451  '-s', '%dx%d' % self.frame_size, '-pix_fmt', self.frame_format,
452  '-r', str(self.fps)]
453  # Logging is quieted because subprocess.PIPE has limited buffer size.
454  if not verbose.ge('debug'):
455  args += ['-loglevel', 'quiet']
456  args += ['-i', 'pipe:'] + self.output_args
457  return args
458 
459 
460 # Combine FFMpeg options with temp file-based writing
461 @writers.register('ffmpeg_file')
463  supported_formats = ['png', 'jpeg', 'ppm', 'tiff', 'sgi', 'bmp',
464  'pbm', 'raw', 'rgba']
465 
466  def _args(self):
467  # Returns the command line parameters for subprocess to use
468  # ffmpeg to create a movie using a collection of temp images
469  return [self.bin_path(), '-i', self._base_temp_name(),
470  '-vframes', str(self._frame_counter),
471  '-r', str(self.fps)] + self.output_args
472 
473 
474 # Base class of avconv information. AVConv has identical arguments to
475 # FFMpeg
477  exec_key = 'animation.avconv_path'
478  args_key = 'animation.avconv_args'
479 
480 
481 # Combine AVConv options with pipe-based writing
482 @writers.register('avconv')
484  pass
485 
486 
487 # Combine AVConv options with file-based writing
488 @writers.register('avconv_file')
490  pass
491 
492 
493 # Base class of mencoder information. Contains configuration key information
494 # as well as arguments for controlling *output*
495 class MencoderBase(object):
496  exec_key = 'animation.mencoder_path'
497  args_key = 'animation.mencoder_args'
498 
499  # Mencoder only allows certain keys, other ones cause the program
500  # to fail.
501  allowed_metadata = ['name', 'artist', 'genre', 'subject', 'copyright',
502  'srcform', 'comment']
503 
504  # Mencoder mandates using name, but 'title' works better with ffmpeg.
505  # If we find it, just put it's value into name
506  def _remap_metadata(self):
507  if 'title' in self.metadata:
508  self.metadata['name'] = self.metadata['title']
509 
510  @property
511  def output_args(self):
512  self._remap_metadata()
513  lavcopts = {'vcodec': self.codec}
514  if self.bitrate > 0:
515  lavcopts.update(vbitrate=self.bitrate)
516  args = ['-o', self.outfile, '-ovc', 'lavc', '-lavcopts',
517  ':'.join(itertools.starmap('{0}={1}'.format,
518  lavcopts.items()))]
519  if self.extra_args:
520  args.extend(self.extra_args)
521  if self.metadata:
522  args.extend(['-info', ':'.join('%s=%s' % (k, v)
523  for k, v in six.iteritems(self.metadata)
524  if k in self.allowed_metadata)])
525  return args
526 
527 
528 # Combine Mencoder options with pipe-based writing
529 @writers.register('mencoder')
531  def _args(self):
532  # Returns the command line parameters for subprocess to use
533  # mencoder to create a movie
534  return [self.bin_path(), '-', '-demuxer', 'rawvideo', '-rawvideo',
535  ('w=%i:h=%i:' % self.frame_size +
536  'fps=%i:format=%s' % (self.fps,
537  self.frame_format))] + self.output_args
538 
539 
540 # Combine Mencoder options with temp file-based writing
541 @writers.register('mencoder_file')
543  supported_formats = ['png', 'jpeg', 'tga', 'sgi']
544 
545  def _args(self):
546  # Returns the command line parameters for subprocess to use
547  # mencoder to create a movie
548  return [self.bin_path(),
549  'mf://%s*.%s' % (self.temp_prefix, self.frame_format),
550  '-frames', str(self._frame_counter), '-mf',
551  'type=%s:fps=%d' % (self.frame_format,
552  self.fps)] + self.output_args
553 
554 
555 # Base class for animated GIFs with convert utility
556 class ImageMagickBase(object):
557  exec_key = 'animation.convert_path'
558  args_key = 'animation.convert_args'
559 
560  @property
561  def delay(self):
562  return 100. / self.fps
563 
564  @property
565  def output_args(self):
566  return [self.outfile]
567 
568  @classmethod
569  def _init_from_registry(cls):
570  if sys.platform != 'win32' or rcParams[cls.exec_key] != 'convert':
571  return
572  from matplotlib.externals.six.moves import winreg
573  for flag in (0, winreg.KEY_WOW64_32KEY, winreg.KEY_WOW64_64KEY):
574  try:
575  hkey = winreg.OpenKeyEx(winreg.HKEY_LOCAL_MACHINE,
576  'Software\\Imagemagick\\Current',
577  0, winreg.KEY_QUERY_VALUE | flag)
578  binpath = winreg.QueryValueEx(hkey, 'BinPath')[0]
579  winreg.CloseKey(hkey)
580  binpath += '\\convert.exe'
581  break
582  except Exception:
583  binpath = ''
584  rcParams[cls.exec_key] = rcParamsDefault[cls.exec_key] = binpath
585 
586 
587 ImageMagickBase._init_from_registry()
588 
589 
590 @writers.register('imagemagick')
592  def _args(self):
593  return ([self.bin_path(),
594  '-size', '%ix%i' % self.frame_size, '-depth', '8',
595  '-delay', str(self.delay), '-loop', '0',
596  '%s:-' % self.frame_format]
597  + self.output_args)
598 
599 
600 @writers.register('imagemagick_file')
602  supported_formats = ['png', 'jpeg', 'ppm', 'tiff', 'sgi', 'bmp',
603  'pbm', 'raw', 'rgba']
604 
605  def _args(self):
606  return ([self.bin_path(), '-delay', str(self.delay), '-loop', '0',
607  '%s*.%s' % (self.temp_prefix, self.frame_format)]
608  + self.output_args)
609 
610 
611 class Animation(object):
612  '''
613  This class wraps the creation of an animation using matplotlib. It is
614  only a base class which should be subclassed to provide needed behavior.
615 
616  *fig* is the figure object that is used to get draw, resize, and any
617  other needed events.
618 
619  *event_source* is a class that can run a callback when desired events
620  are generated, as well as be stopped and started. Examples include timers
621  (see :class:`TimedAnimation`) and file system notifications.
622 
623  *blit* is a boolean that controls whether blitting is used to optimize
624  drawing.
625  '''
626  def __init__(self, fig, event_source=None, blit=False):
627  self._fig = fig
628  # Disables blitting for backends that don't support it. This
629  # allows users to request it if available, but still have a
630  # fallback that works if it is not.
631  self._blit = blit and fig.canvas.supports_blit
632 
633  # These are the basics of the animation. The frame sequence represents
634  # information for each frame of the animation and depends on how the
635  # drawing is handled by the subclasses. The event source fires events
636  # that cause the frame sequence to be iterated.
637  self.frame_seq = self.new_frame_seq()
638  self.event_source = event_source
639 
640  # Instead of starting the event source now, we connect to the figure's
641  # draw_event, so that we only start once the figure has been drawn.
642  self._first_draw_id = fig.canvas.mpl_connect('draw_event', self._start)
643 
644  # Connect to the figure's close_event so that we don't continue to
645  # fire events and try to draw to a deleted figure.
646  self._close_id = self._fig.canvas.mpl_connect('close_event',
647  self._stop)
648  if self._blit:
649  self._setup_blit()
650 
651  def _start(self, *args):
652  '''
653  Starts interactive animation. Adds the draw frame command to the GUI
654  handler, calls show to start the event loop.
655  '''
656  # First disconnect our draw event handler
657  self._fig.canvas.mpl_disconnect(self._first_draw_id)
658  self._first_draw_id = None # So we can check on save
659 
660  # Now do any initial draw
661  self._init_draw()
662 
663  # Add our callback for stepping the animation and
664  # actually start the event_source.
665  self.event_source.add_callback(self._step)
666  self.event_source.start()
667 
668  def _stop(self, *args):
669  # On stop we disconnect all of our events.
670  if self._blit:
671  self._fig.canvas.mpl_disconnect(self._resize_id)
672  self._fig.canvas.mpl_disconnect(self._close_id)
673  self.event_source.remove_callback(self._step)
674  self.event_source = None
675 
676  def save(self, filename, writer=None, fps=None, dpi=None, codec=None,
677  bitrate=None, extra_args=None, metadata=None, extra_anim=None,
678  savefig_kwargs=None):
679  '''
680  Saves a movie file by drawing every frame.
681 
682  *filename* is the output filename, e.g., :file:`mymovie.mp4`
683 
684  *writer* is either an instance of :class:`MovieWriter` or a string
685  key that identifies a class to use, such as 'ffmpeg' or 'mencoder'.
686  If nothing is passed, the value of the rcparam `animation.writer` is
687  used.
688 
689  *dpi* controls the dots per inch for the movie frames. This combined
690  with the figure's size in inches controls the size of the movie.
691 
692  *savefig_kwargs* is a dictionary containing keyword arguments to be
693  passed on to the 'savefig' command which is called repeatedly to save
694  the individual frames. This can be used to set tight bounding boxes,
695  for example.
696 
697  *extra_anim* is a list of additional `Animation` objects that should
698  be included in the saved movie file. These need to be from the same
699  `matplotlib.Figure` instance. Also, animation frames will just be
700  simply combined, so there should be a 1:1 correspondence between
701  the frames from the different animations.
702 
703  These remaining arguments are used to construct a :class:`MovieWriter`
704  instance when necessary and are only considered valid if *writer* is
705  not a :class:`MovieWriter` instance.
706 
707  *fps* is the frames per second in the movie. Defaults to None,
708  which will use the animation's specified interval to set the frames
709  per second.
710 
711  *codec* is the video codec to be used. Not all codecs are supported
712  by a given :class:`MovieWriter`. If none is given, this defaults to the
713  value specified by the rcparam `animation.codec`.
714 
715  *bitrate* specifies the amount of bits used per second in the
716  compressed movie, in kilobits per second. A higher number means a
717  higher quality movie, but at the cost of increased file size. If no
718  value is given, this defaults to the value given by the rcparam
719  `animation.bitrate`.
720 
721  *extra_args* is a list of extra string arguments to be passed to the
722  underlying movie utility. The default is None, which passes the
723  additional arguments in the 'animation.extra_args' rcParam.
724 
725  *metadata* is a dictionary of keys and values for metadata to include
726  in the output file. Some keys that may be of use include:
727  title, artist, genre, subject, copyright, srcform, comment.
728  '''
729  # If the writer is None, use the rc param to find the name of the one
730  # to use
731  if writer is None:
732  writer = rcParams['animation.writer']
733  elif (not is_string_like(writer) and
734  any(arg is not None
735  for arg in (fps, codec, bitrate, extra_args, metadata))):
736  raise RuntimeError('Passing in values for arguments for arguments '
737  'fps, codec, bitrate, extra_args, or metadata '
738  'is not supported when writer is an existing '
739  'MovieWriter instance. These should instead be '
740  'passed as arguments when creating the '
741  'MovieWriter instance.')
742 
743  if savefig_kwargs is None:
744  savefig_kwargs = {}
745 
746  # Need to disconnect the first draw callback, since we'll be doing
747  # draws. Otherwise, we'll end up starting the animation.
748  if self._first_draw_id is not None:
749  self._fig.canvas.mpl_disconnect(self._first_draw_id)
750  reconnect_first_draw = True
751  else:
752  reconnect_first_draw = False
753 
754  if fps is None and hasattr(self, '_interval'):
755  # Convert interval in ms to frames per second
756  fps = 1000. / self._interval
757 
758  # Re-use the savefig DPI for ours if none is given
759  if dpi is None:
760  dpi = rcParams['savefig.dpi']
761  if dpi == 'figure':
762  dpi = self._fig.dpi
763 
764  if codec is None:
765  codec = rcParams['animation.codec']
766 
767  if bitrate is None:
768  bitrate = rcParams['animation.bitrate']
769 
770  all_anim = [self]
771  if extra_anim is not None:
772  all_anim.extend(anim
773  for anim
774  in extra_anim if anim._fig is self._fig)
775 
776  # If we have the name of a writer, instantiate an instance of the
777  # registered class.
778  if is_string_like(writer):
779  if writer in writers.avail:
780  writer = writers[writer](fps, codec, bitrate,
781  extra_args=extra_args,
782  metadata=metadata)
783  else:
784  warnings.warn("MovieWriter %s unavailable" % writer)
785 
786  try:
787  writer = writers[writers.list()[0]](fps, codec, bitrate,
788  extra_args=extra_args,
789  metadata=metadata)
790  except IndexError:
791  raise ValueError("Cannot save animation: no writers are "
792  "available. Please install mencoder or "
793  "ffmpeg to save animations.")
794 
795  verbose.report('Animation.save using %s' % type(writer),
796  level='helpful')
797 
798  # FIXME: Using 'bbox_inches' doesn't currently work with
799  # writers that pipe the data to the command because this
800  # requires a fixed frame size (see Ryan May's reply in this
801  # thread: [1]). Thus we drop the 'bbox_inches' argument if it
802  # exists in savefig_kwargs.
803  #
804  # [1] (http://matplotlib.1069221.n5.nabble.com/
805  # Animation-class-let-save-accept-kwargs-which-
806  # are-passed-on-to-savefig-td39627.html)
807  #
808  if 'bbox_inches' in savefig_kwargs and not writer.frame_size_can_vary:
809  warnings.warn("Warning: discarding the 'bbox_inches' argument in "
810  "'savefig_kwargs' as it not supported by "
811  "{0}).".format(writer.__class__.__name__))
812  savefig_kwargs.pop('bbox_inches')
813 
814  # Create a new sequence of frames for saved data. This is different
815  # from new_frame_seq() to give the ability to save 'live' generated
816  # frame information to be saved later.
817  # TODO: Right now, after closing the figure, saving a movie won't work
818  # since GUI widgets are gone. Either need to remove extra code to
819  # allow for this non-existent use case or find a way to make it work.
820  with rc_context():
821  # See above about bbox_inches savefig kwarg
822  if (not writer.frame_size_can_vary and
823  rcParams['savefig.bbox'] == 'tight'):
824  verbose.report("Disabling savefig.bbox = 'tight', as it is "
825  "not supported by "
826  "{0}.".format(writer.__class__.__name__),
827  level='helpful')
828  rcParams['savefig.bbox'] = None
829  with writer.saving(self._fig, filename, dpi):
830  for anim in all_anim:
831  # Clear the initial frame
832  anim._init_draw()
833  for data in zip(*[a.new_saved_frame_seq()
834  for a in all_anim]):
835  for anim, d in zip(all_anim, data):
836  # TODO: See if turning off blit is really necessary
837  anim._draw_next_frame(d, blit=False)
838  writer.grab_frame(**savefig_kwargs)
839 
840  # Reconnect signal for first draw if necessary
841  if reconnect_first_draw:
842  self._first_draw_id = self._fig.canvas.mpl_connect('draw_event',
843  self._start)
844 
845  def _step(self, *args):
846  '''
847  Handler for getting events. By default, gets the next frame in the
848  sequence and hands the data off to be drawn.
849  '''
850  # Returns True to indicate that the event source should continue to
851  # call _step, until the frame sequence reaches the end of iteration,
852  # at which point False will be returned.
853  try:
854  framedata = next(self.frame_seq)
855  self._draw_next_frame(framedata, self._blit)
856  return True
857  except StopIteration:
858  return False
859 
860  def new_frame_seq(self):
861  'Creates a new sequence of frame information.'
862  # Default implementation is just an iterator over self._framedata
863  return iter(self._framedata)
864 
866  'Creates a new sequence of saved/cached frame information.'
867  # Default is the same as the regular frame sequence
868  return self.new_frame_seq()
869 
870  def _draw_next_frame(self, framedata, blit):
871  # Breaks down the drawing of the next frame into steps of pre- and
872  # post- draw, as well as the drawing of the frame itself.
873  self._pre_draw(framedata, blit)
874  self._draw_frame(framedata)
875  self._post_draw(framedata, blit)
876 
877  def _init_draw(self):
878  # Initial draw to clear the frame. Also used by the blitting code
879  # when a clean base is required.
880  pass
881 
882  def _pre_draw(self, framedata, blit):
883  # Perform any cleaning or whatnot before the drawing of the frame.
884  # This default implementation allows blit to clear the frame.
885  if blit:
886  self._blit_clear(self._drawn_artists, self._blit_cache)
887 
888  def _draw_frame(self, framedata):
889  # Performs actual drawing of the frame.
890  raise NotImplementedError('Needs to be implemented by subclasses to'
891  ' actually make an animation.')
892 
893  def _post_draw(self, framedata, blit):
894  # After the frame is rendered, this handles the actual flushing of
895  # the draw, which can be a direct draw_idle() or make use of the
896  # blitting.
897  if blit and self._drawn_artists:
898  self._blit_draw(self._drawn_artists, self._blit_cache)
899  else:
900  self._fig.canvas.draw_idle()
901 
902  # The rest of the code in this class is to facilitate easy blitting
903  def _blit_draw(self, artists, bg_cache):
904  # Handles blitted drawing, which renders only the artists given instead
905  # of the entire figure.
906  updated_ax = []
907  for a in artists:
908  # If we haven't cached the background for this axes object, do
909  # so now. This might not always be reliable, but it's an attempt
910  # to automate the process.
911  if a.axes not in bg_cache:
912  bg_cache[a.axes] = a.figure.canvas.copy_from_bbox(a.axes.bbox)
913  a.axes.draw_artist(a)
914  updated_ax.append(a.axes)
915 
916  # After rendering all the needed artists, blit each axes individually.
917  for ax in set(updated_ax):
918  ax.figure.canvas.blit(ax.bbox)
919 
920  def _blit_clear(self, artists, bg_cache):
921  # Get a list of the axes that need clearing from the artists that
922  # have been drawn. Grab the appropriate saved background from the
923  # cache and restore.
924  axes = set(a.axes for a in artists)
925  for a in axes:
926  a.figure.canvas.restore_region(bg_cache[a])
927 
928  def _setup_blit(self):
929  # Setting up the blit requires: a cache of the background for the
930  # axes
931  self._blit_cache = dict()
932  self._drawn_artists = []
933  self._resize_id = self._fig.canvas.mpl_connect('resize_event',
934  self._handle_resize)
935  self._post_draw(None, self._blit)
936 
937  def _handle_resize(self, *args):
938  # On resize, we need to disable the resize event handling so we don't
939  # get too many events. Also stop the animation events, so that
940  # we're paused. Reset the cache and re-init. Set up an event handler
941  # to catch once the draw has actually taken place.
942  self._fig.canvas.mpl_disconnect(self._resize_id)
943  self.event_source.stop()
944  self._blit_cache.clear()
945  self._init_draw()
946  self._resize_id = self._fig.canvas.mpl_connect('draw_event',
947  self._end_redraw)
948 
949  def _end_redraw(self, evt):
950  # Now that the redraw has happened, do the post draw flushing and
951  # blit handling. Then re-enable all of the original events.
952  self._post_draw(None, self._blit)
953  self.event_source.start()
954  self._fig.canvas.mpl_disconnect(self._resize_id)
955  self._resize_id = self._fig.canvas.mpl_connect('resize_event',
956  self._handle_resize)
957 
958  def to_html5_video(self):
959  r'''Returns animation as an HTML5 video tag.
960 
961  This saves the animation as an h264 video, encoded in base64
962  directly into the HTML5 video tag. This respects the rc parameters
963  for the writer as well as the bitrate. This also makes use of the
964  ``interval`` to control the speed, and uses the ``repeat``
965  parameter to decide whether to loop.
966  '''
967  VIDEO_TAG = r'''<video {size} {options}>
968  <source type="video/mp4" src="data:video/mp4;base64,{video}">
969  Your browser does not support the video tag.
970 </video>'''
971  # Cache the the rendering of the video as HTML
972  if not hasattr(self, '_base64_video'):
973  # First write the video to a tempfile. Set delete to False
974  # so we can re-open to read binary data.
975  with tempfile.NamedTemporaryFile(suffix='.m4v',
976  delete=False) as f:
977  # We create a writer manually so that we can get the
978  # appropriate size for the tag
979  Writer = writers[rcParams['animation.writer']]
980  writer = Writer(codec='h264',
981  bitrate=rcParams['animation.bitrate'],
982  fps=1000. / self._interval)
983  self.save(f.name, writer=writer)
984 
985  # Now open and base64 encode
986  with open(f.name, 'rb') as video:
987  vid64 = encodebytes(video.read())
988  self._base64_video = vid64.decode('ascii')
989  self._video_size = 'width="{0}" height="{1}"'.format(
990  *writer.frame_size)
991 
992  # Now we can remove
993  os.remove(f.name)
994 
995  # Default HTML5 options are to autoplay and to display video controls
996  options = ['controls', 'autoplay']
997 
998  # If we're set to repeat, make it loop
999  if self.repeat:
1000  options.append('loop')
1001  return VIDEO_TAG.format(video=self._base64_video,
1002  size=self._video_size,
1003  options=' '.join(options))
1004 
1005  def _repr_html_(self):
1006  r'IPython display hook for rendering.'
1007  fmt = rcParams['animation.html']
1008  if fmt == 'html5':
1009  return self.to_html5_video()
1010 
1011 
1013  '''
1014  :class:`Animation` subclass that supports time-based animation, drawing
1015  a new frame every *interval* milliseconds.
1016 
1017  *repeat* controls whether the animation should repeat when the sequence
1018  of frames is completed.
1019 
1020  *repeat_delay* optionally adds a delay in milliseconds before repeating
1021  the animation.
1022  '''
1023  def __init__(self, fig, interval=200, repeat_delay=None, repeat=True,
1024  event_source=None, *args, **kwargs):
1025  # Store the timing information
1026  self._interval = interval
1027  self._repeat_delay = repeat_delay
1028  self.repeat = repeat
1029 
1030  # If we're not given an event source, create a new timer. This permits
1031  # sharing timers between animation objects for syncing animations.
1032  if event_source is None:
1033  event_source = fig.canvas.new_timer()
1034  event_source.interval = self._interval
1035 
1036  Animation.__init__(self, fig, event_source=event_source,
1037  *args, **kwargs)
1038 
1039  def _step(self, *args):
1040  '''
1041  Handler for getting events.
1042  '''
1043  # Extends the _step() method for the Animation class. If
1044  # Animation._step signals that it reached the end and we want to
1045  # repeat, we refresh the frame sequence and return True. If
1046  # _repeat_delay is set, change the event_source's interval to our loop
1047  # delay and set the callback to one which will then set the interval
1048  # back.
1049  still_going = Animation._step(self, *args)
1050  if not still_going and self.repeat:
1051  self._init_draw()
1052  self.frame_seq = self.new_frame_seq()
1053  if self._repeat_delay:
1054  self.event_source.remove_callback(self._step)
1055  self.event_source.add_callback(self._loop_delay)
1056  self.event_source.interval = self._repeat_delay
1057  return True
1058  else:
1059  return Animation._step(self, *args)
1060  else:
1061  return still_going
1062 
1063  def _stop(self, *args):
1064  # If we stop in the middle of a loop delay (which is relatively likely
1065  # given the potential pause here, remove the loop_delay callback as
1066  # well.
1067  self.event_source.remove_callback(self._loop_delay)
1068  Animation._stop(self)
1069 
1070  def _loop_delay(self, *args):
1071  # Reset the interval and change callbacks after the delay.
1072  self.event_source.remove_callback(self._loop_delay)
1073  self.event_source.interval = self._interval
1074  self.event_source.add_callback(self._step)
1075  Animation._step(self)
1076 
1077 
1079  '''
1080  Before calling this function, all plotting should have taken place
1081  and the relevant artists saved.
1082 
1083  frame_info is a list, with each list entry a collection of artists that
1084  represent what needs to be enabled on each frame. These will be disabled
1085  for other frames.
1086  '''
1087  def __init__(self, fig, artists, *args, **kwargs):
1088  # Internal list of artists drawn in the most recent frame.
1089  self._drawn_artists = []
1090 
1091  # Use the list of artists as the framedata, which will be iterated
1092  # over by the machinery.
1093  self._framedata = artists
1094  TimedAnimation.__init__(self, fig, *args, **kwargs)
1095 
1096  def _init_draw(self):
1097  # Make all the artists involved in *any* frame invisible
1098  figs = set()
1099  for f in self.new_frame_seq():
1100  for artist in f:
1101  artist.set_visible(False)
1102  artist.set_animated(self._blit)
1103  # Assemble a list of unique axes that need flushing
1104  if artist.axes.figure not in figs:
1105  figs.add(artist.axes.figure)
1106 
1107  # Flush the needed axes
1108  for fig in figs:
1109  fig.canvas.draw_idle()
1110 
1111  def _pre_draw(self, framedata, blit):
1112  '''
1113  Clears artists from the last frame.
1114  '''
1115  if blit:
1116  # Let blit handle clearing
1117  self._blit_clear(self._drawn_artists, self._blit_cache)
1118  else:
1119  # Otherwise, make all the artists from the previous frame invisible
1120  for artist in self._drawn_artists:
1121  artist.set_visible(False)
1122 
1123  def _draw_frame(self, artists):
1124  # Save the artists that were passed in as framedata for the other
1125  # steps (esp. blitting) to use.
1126  self._drawn_artists = artists
1127 
1128  # Make all the artists from the current frame visible
1129  for artist in artists:
1130  artist.set_visible(True)
1131 
1132 
1134  '''
1135  Makes an animation by repeatedly calling a function *func*, passing in
1136  (optional) arguments in *fargs*.
1137 
1138  *frames* can be a generator, an iterable, or a number of frames.
1139 
1140  *init_func* is a function used to draw a clear frame. If not given, the
1141  results of drawing from the first item in the frames sequence will be
1142  used. This function will be called once before the first frame.
1143 
1144  If blit=True, *func* and *init_func* must return an iterable of
1145  artists to be re-drawn.
1146 
1147  *kwargs* include *repeat*, *repeat_delay*, and *interval*:
1148  *interval* draws a new frame every *interval* milliseconds.
1149  *repeat* controls whether the animation should repeat when the sequence
1150  of frames is completed.
1151  *repeat_delay* optionally adds a delay in milliseconds before repeating
1152  the animation.
1153  '''
1154  def __init__(self, fig, func, frames=None, init_func=None, fargs=None,
1155  save_count=None, **kwargs):
1156  if fargs:
1157  self._args = fargs
1158  else:
1159  self._args = ()
1160  self._func = func
1161 
1162  # Amount of framedata to keep around for saving movies. This is only
1163  # used if we don't know how many frames there will be: in the case
1164  # of no generator or in the case of a callable.
1165  self.save_count = save_count
1166 
1167  # Set up a function that creates a new iterable when needed. If nothing
1168  # is passed in for frames, just use itertools.count, which will just
1169  # keep counting from 0. A callable passed in for frames is assumed to
1170  # be a generator. An iterable will be used as is, and anything else
1171  # will be treated as a number of frames.
1172  if frames is None:
1173  self._iter_gen = itertools.count
1174  elif six.callable(frames):
1175  self._iter_gen = frames
1176  elif iterable(frames):
1177  self._iter_gen = lambda: iter(frames)
1178  if hasattr(frames, '__len__'):
1179  self.save_count = len(frames)
1180  else:
1181  self._iter_gen = lambda: xrange(frames).__iter__()
1182  self.save_count = frames
1183 
1184  # If we're passed in and using the default, set it to 100.
1185  if self.save_count is None:
1186  self.save_count = 100
1187 
1188  self._init_func = init_func
1189 
1190  # Needs to be initialized so the draw functions work without checking
1191  self._save_seq = []
1192 
1193  TimedAnimation.__init__(self, fig, **kwargs)
1194 
1195  # Need to reset the saved seq, since right now it will contain data
1196  # for a single frame from init, which is not what we want.
1197  self._save_seq = []
1198 
1199  def new_frame_seq(self):
1200  # Use the generating function to generate a new frame sequence
1201  return self._iter_gen()
1202 
1204  # Generate an iterator for the sequence of saved data. If there are
1205  # no saved frames, generate a new frame sequence and take the first
1206  # save_count entries in it.
1207  if self._save_seq:
1208  # While iterating we are going to update _save_seq
1209  # so make a copy to safely iterate over
1210  self._old_saved_seq = list(self._save_seq)
1211  return iter(self._old_saved_seq)
1212  else:
1213  return itertools.islice(self.new_frame_seq(), self.save_count)
1214 
1215  def _init_draw(self):
1216  # Initialize the drawing either using the given init_func or by
1217  # calling the draw function with the first item of the frame sequence.
1218  # For blitting, the init_func should return a sequence of modified
1219  # artists.
1220  if self._init_func is None:
1221  self._draw_frame(next(self.new_frame_seq()))
1222 
1223  else:
1224  self._drawn_artists = self._init_func()
1225  if self._blit:
1226  if self._drawn_artists is None:
1227  raise RuntimeError('The init_func must return a '
1228  'sequence of Artist objects.')
1229  for a in self._drawn_artists:
1230  a.set_animated(self._blit)
1231  self._save_seq = []
1232 
1233  def _draw_frame(self, framedata):
1234  # Save the data for potential saving of movies.
1235  self._save_seq.append(framedata)
1236 
1237  # Make sure to respect save_count (keep only the last save_count
1238  # around)
1239  self._save_seq = self._save_seq[-self.save_count:]
1240 
1241  # Call the func with framedata and args. If blitting is desired,
1242  # func needs to return a sequence of any artists that were modified.
1243  self._drawn_artists = self._func(framedata, *self._args)
1244  if self._blit:
1245  if self._drawn_artists is None:
1246  raise RuntimeError('The animation function must return a '
1247  'sequence of Artist objects.')
1248  for a in self._drawn_artists:
1249  a.set_animated(self._blit)
def setup(self, fig, outfile, dpi, args)
Definition: animation.py:172
def savefig(args, kwargs)
Definition: pyplot.py:694
def _pre_draw(self, framedata, blit)
Definition: animation.py:882
def save(self, filename, writer=None, fps=None, dpi=None, codec=None, bitrate=None, extra_args=None, metadata=None, extra_anim=None, savefig_kwargs=None)
Definition: animation.py:678
def __init__(self, args, kwargs)
Definition: animation.py:299
def __init__(self, fig, interval=200, repeat_delay=None, repeat=True, event_source=None, args, kwargs)
Definition: animation.py:1024
def _post_draw(self, framedata, blit)
Definition: animation.py:893
def close(args)
Definition: pyplot.py:625
def iterable(obj)
Definition: cbook.py:687
def __init__(self, fps=5, codec=None, bitrate=None, extra_args=None, metadata=None)
Definition: animation.py:119
def _draw_frame(self, framedata)
Definition: animation.py:888
def __init__(self, fig, artists, args, kwargs)
Definition: animation.py:1087
def grab_frame(self, savefig_kwargs)
Definition: animation.py:224
def setup(self, fig, outfile, dpi, frame_prefix='_tmp', clear_temp=True)
Definition: animation.py:303
def grab_frame(self, savefig_kwargs)
Definition: animation.py:367
def __init__(self, fig, func, frames=None, init_func=None, fargs=None, save_count=None, kwargs)
Definition: animation.py:1155
def _draw_next_frame(self, framedata, blit)
Definition: animation.py:870
def _blit_clear(self, artists, bg_cache)
Definition: animation.py:920
def __init__(self, fig, event_source=None, blit=False)
Definition: animation.py:626