matplotlib  1.3.1
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.3.1.tar.gz  ("inofficial" and yet experimental doxygen-generated source code documentation)  

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