animation.py 70 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804
  1. # TODO:
  2. # * Documentation -- this will need a new section of the User's Guide.
  3. # Both for Animations and just timers.
  4. # - Also need to update
  5. # https://scipy-cookbook.readthedocs.io/items/Matplotlib_Animations.html
  6. # * Blit
  7. # * Currently broken with Qt4 for widgets that don't start on screen
  8. # * Still a few edge cases that aren't working correctly
  9. # * Can this integrate better with existing matplotlib animation artist flag?
  10. # - If animated removes from default draw(), perhaps we could use this to
  11. # simplify initial draw.
  12. # * Example
  13. # * Frameless animation - pure procedural with no loop
  14. # * Need example that uses something like inotify or subprocess
  15. # * Complex syncing examples
  16. # * Movies
  17. # * Can blit be enabled for movies?
  18. # * Need to consider event sources to allow clicking through multiple figures
  19. import abc
  20. import base64
  21. import contextlib
  22. from io import BytesIO, TextIOWrapper
  23. import itertools
  24. import logging
  25. from pathlib import Path
  26. import shutil
  27. import subprocess
  28. import sys
  29. from tempfile import TemporaryDirectory
  30. import uuid
  31. import warnings
  32. import numpy as np
  33. from PIL import Image
  34. import matplotlib as mpl
  35. from matplotlib._animation_data import (
  36. DISPLAY_TEMPLATE, INCLUDED_FRAMES, JS_INCLUDE, STYLE_INCLUDE)
  37. from matplotlib import _api, cbook
  38. import matplotlib.colors as mcolors
  39. _log = logging.getLogger(__name__)
  40. # Process creation flag for subprocess to prevent it raising a terminal
  41. # window. See for example https://stackoverflow.com/q/24130623/
  42. subprocess_creation_flags = (
  43. subprocess.CREATE_NO_WINDOW if sys.platform == 'win32' else 0)
  44. # Other potential writing methods:
  45. # * http://pymedia.org/
  46. # * libming (produces swf) python wrappers: https://github.com/libming/libming
  47. # * Wrap x264 API:
  48. # (https://stackoverflow.com/q/2940671/)
  49. def adjusted_figsize(w, h, dpi, n):
  50. """
  51. Compute figure size so that pixels are a multiple of n.
  52. Parameters
  53. ----------
  54. w, h : float
  55. Size in inches.
  56. dpi : float
  57. The dpi.
  58. n : int
  59. The target multiple.
  60. Returns
  61. -------
  62. wnew, hnew : float
  63. The new figure size in inches.
  64. """
  65. # this maybe simplified if / when we adopt consistent rounding for
  66. # pixel size across the whole library
  67. def correct_roundoff(x, dpi, n):
  68. if int(x*dpi) % n != 0:
  69. if int(np.nextafter(x, np.inf)*dpi) % n == 0:
  70. x = np.nextafter(x, np.inf)
  71. elif int(np.nextafter(x, -np.inf)*dpi) % n == 0:
  72. x = np.nextafter(x, -np.inf)
  73. return x
  74. wnew = int(w * dpi / n) * n / dpi
  75. hnew = int(h * dpi / n) * n / dpi
  76. return correct_roundoff(wnew, dpi, n), correct_roundoff(hnew, dpi, n)
  77. class MovieWriterRegistry:
  78. """Registry of available writer classes by human readable name."""
  79. def __init__(self):
  80. self._registered = dict()
  81. def register(self, name):
  82. """
  83. Decorator for registering a class under a name.
  84. Example use::
  85. @registry.register(name)
  86. class Foo:
  87. pass
  88. """
  89. def wrapper(writer_cls):
  90. self._registered[name] = writer_cls
  91. return writer_cls
  92. return wrapper
  93. def is_available(self, name):
  94. """
  95. Check if given writer is available by name.
  96. Parameters
  97. ----------
  98. name : str
  99. Returns
  100. -------
  101. bool
  102. """
  103. try:
  104. cls = self._registered[name]
  105. except KeyError:
  106. return False
  107. return cls.isAvailable()
  108. def __iter__(self):
  109. """Iterate over names of available writer class."""
  110. for name in self._registered:
  111. if self.is_available(name):
  112. yield name
  113. def list(self):
  114. """Get a list of available MovieWriters."""
  115. return [*self]
  116. def __getitem__(self, name):
  117. """Get an available writer class from its name."""
  118. if self.is_available(name):
  119. return self._registered[name]
  120. raise RuntimeError(f"Requested MovieWriter ({name}) not available")
  121. writers = MovieWriterRegistry()
  122. class AbstractMovieWriter(abc.ABC):
  123. """
  124. Abstract base class for writing movies, providing a way to grab frames by
  125. calling `~AbstractMovieWriter.grab_frame`.
  126. `setup` is called to start the process and `finish` is called afterwards.
  127. `saving` is provided as a context manager to facilitate this process as ::
  128. with moviewriter.saving(fig, outfile='myfile.mp4', dpi=100):
  129. # Iterate over frames
  130. moviewriter.grab_frame(**savefig_kwargs)
  131. The use of the context manager ensures that `setup` and `finish` are
  132. performed as necessary.
  133. An instance of a concrete subclass of this class can be given as the
  134. ``writer`` argument of `Animation.save()`.
  135. """
  136. def __init__(self, fps=5, metadata=None, codec=None, bitrate=None):
  137. self.fps = fps
  138. self.metadata = metadata if metadata is not None else {}
  139. self.codec = mpl._val_or_rc(codec, 'animation.codec')
  140. self.bitrate = mpl._val_or_rc(bitrate, 'animation.bitrate')
  141. @abc.abstractmethod
  142. def setup(self, fig, outfile, dpi=None):
  143. """
  144. Setup for writing the movie file.
  145. Parameters
  146. ----------
  147. fig : `~matplotlib.figure.Figure`
  148. The figure object that contains the information for frames.
  149. outfile : str
  150. The filename of the resulting movie file.
  151. dpi : float, default: ``fig.dpi``
  152. The DPI (or resolution) for the file. This controls the size
  153. in pixels of the resulting movie file.
  154. """
  155. # Check that path is valid
  156. Path(outfile).parent.resolve(strict=True)
  157. self.outfile = outfile
  158. self.fig = fig
  159. if dpi is None:
  160. dpi = self.fig.dpi
  161. self.dpi = dpi
  162. @property
  163. def frame_size(self):
  164. """A tuple ``(width, height)`` in pixels of a movie frame."""
  165. w, h = self.fig.get_size_inches()
  166. return int(w * self.dpi), int(h * self.dpi)
  167. @abc.abstractmethod
  168. def grab_frame(self, **savefig_kwargs):
  169. """
  170. Grab the image information from the figure and save as a movie frame.
  171. All keyword arguments in *savefig_kwargs* are passed on to the
  172. `~.Figure.savefig` call that saves the figure. However, several
  173. keyword arguments that are supported by `~.Figure.savefig` may not be
  174. passed as they are controlled by the MovieWriter:
  175. - *dpi*, *bbox_inches*: These may not be passed because each frame of the
  176. animation much be exactly the same size in pixels.
  177. - *format*: This is controlled by the MovieWriter.
  178. """
  179. @abc.abstractmethod
  180. def finish(self):
  181. """Finish any processing for writing the movie."""
  182. @contextlib.contextmanager
  183. def saving(self, fig, outfile, dpi, *args, **kwargs):
  184. """
  185. Context manager to facilitate writing the movie file.
  186. ``*args, **kw`` are any parameters that should be passed to `setup`.
  187. """
  188. if mpl.rcParams['savefig.bbox'] == 'tight':
  189. _log.info("Disabling savefig.bbox = 'tight', as it may cause "
  190. "frame size to vary, which is inappropriate for "
  191. "animation.")
  192. # This particular sequence is what contextlib.contextmanager wants
  193. self.setup(fig, outfile, dpi, *args, **kwargs)
  194. with mpl.rc_context({'savefig.bbox': None}):
  195. try:
  196. yield self
  197. finally:
  198. self.finish()
  199. class MovieWriter(AbstractMovieWriter):
  200. """
  201. Base class for writing movies.
  202. This is a base class for MovieWriter subclasses that write a movie frame
  203. data to a pipe. You cannot instantiate this class directly.
  204. See examples for how to use its subclasses.
  205. Attributes
  206. ----------
  207. frame_format : str
  208. The format used in writing frame data, defaults to 'rgba'.
  209. fig : `~matplotlib.figure.Figure`
  210. The figure to capture data from.
  211. This must be provided by the subclasses.
  212. """
  213. # Builtin writer subclasses additionally define the _exec_key and _args_key
  214. # attributes, which indicate the rcParams entries where the path to the
  215. # executable and additional command-line arguments to the executable are
  216. # stored. Third-party writers cannot meaningfully set these as they cannot
  217. # extend rcParams with new keys.
  218. # Pipe-based writers only support RGBA, but file-based ones support more
  219. # formats.
  220. supported_formats = ["rgba"]
  221. def __init__(self, fps=5, codec=None, bitrate=None, extra_args=None,
  222. metadata=None):
  223. """
  224. Parameters
  225. ----------
  226. fps : int, default: 5
  227. Movie frame rate (per second).
  228. codec : str or None, default: :rc:`animation.codec`
  229. The codec to use.
  230. bitrate : int, default: :rc:`animation.bitrate`
  231. The bitrate of the movie, in kilobits per second. Higher values
  232. means higher quality movies, but increase the file size. A value
  233. of -1 lets the underlying movie encoder select the bitrate.
  234. extra_args : list of str or None, optional
  235. Extra command-line arguments passed to the underlying movie encoder. These
  236. arguments are passed last to the encoder, just before the filename. The
  237. default, None, means to use :rc:`animation.[name-of-encoder]_args` for the
  238. builtin writers.
  239. metadata : dict[str, str], default: {}
  240. A dictionary of keys and values for metadata to include in the
  241. output file. Some keys that may be of use include:
  242. title, artist, genre, subject, copyright, srcform, comment.
  243. """
  244. if type(self) is MovieWriter:
  245. # TODO MovieWriter is still an abstract class and needs to be
  246. # extended with a mixin. This should be clearer in naming
  247. # and description. For now, just give a reasonable error
  248. # message to users.
  249. raise TypeError(
  250. 'MovieWriter cannot be instantiated directly. Please use one '
  251. 'of its subclasses.')
  252. super().__init__(fps=fps, metadata=metadata, codec=codec,
  253. bitrate=bitrate)
  254. self.frame_format = self.supported_formats[0]
  255. self.extra_args = extra_args
  256. def _adjust_frame_size(self):
  257. if self.codec == 'h264':
  258. wo, ho = self.fig.get_size_inches()
  259. w, h = adjusted_figsize(wo, ho, self.dpi, 2)
  260. if (wo, ho) != (w, h):
  261. self.fig.set_size_inches(w, h, forward=True)
  262. _log.info('figure size in inches has been adjusted '
  263. 'from %s x %s to %s x %s', wo, ho, w, h)
  264. else:
  265. w, h = self.fig.get_size_inches()
  266. _log.debug('frame size in pixels is %s x %s', *self.frame_size)
  267. return w, h
  268. def setup(self, fig, outfile, dpi=None):
  269. # docstring inherited
  270. super().setup(fig, outfile, dpi=dpi)
  271. self._w, self._h = self._adjust_frame_size()
  272. # Run here so that grab_frame() can write the data to a pipe. This
  273. # eliminates the need for temp files.
  274. self._run()
  275. def _run(self):
  276. # Uses subprocess to call the program for assembling frames into a
  277. # movie file. *args* returns the sequence of command line arguments
  278. # from a few configuration options.
  279. command = self._args()
  280. _log.info('MovieWriter._run: running command: %s',
  281. cbook._pformat_subprocess(command))
  282. PIPE = subprocess.PIPE
  283. self._proc = subprocess.Popen(
  284. command, stdin=PIPE, stdout=PIPE, stderr=PIPE,
  285. creationflags=subprocess_creation_flags)
  286. def finish(self):
  287. """Finish any processing for writing the movie."""
  288. out, err = self._proc.communicate()
  289. # Use the encoding/errors that universal_newlines would use.
  290. out = TextIOWrapper(BytesIO(out)).read()
  291. err = TextIOWrapper(BytesIO(err)).read()
  292. if out:
  293. _log.log(
  294. logging.WARNING if self._proc.returncode else logging.DEBUG,
  295. "MovieWriter stdout:\n%s", out)
  296. if err:
  297. _log.log(
  298. logging.WARNING if self._proc.returncode else logging.DEBUG,
  299. "MovieWriter stderr:\n%s", err)
  300. if self._proc.returncode:
  301. raise subprocess.CalledProcessError(
  302. self._proc.returncode, self._proc.args, out, err)
  303. def grab_frame(self, **savefig_kwargs):
  304. # docstring inherited
  305. _validate_grabframe_kwargs(savefig_kwargs)
  306. _log.debug('MovieWriter.grab_frame: Grabbing frame.')
  307. # Readjust the figure size in case it has been changed by the user.
  308. # All frames must have the same size to save the movie correctly.
  309. self.fig.set_size_inches(self._w, self._h)
  310. # Save the figure data to the sink, using the frame format and dpi.
  311. self.fig.savefig(self._proc.stdin, format=self.frame_format,
  312. dpi=self.dpi, **savefig_kwargs)
  313. def _args(self):
  314. """Assemble list of encoder-specific command-line arguments."""
  315. return NotImplementedError("args needs to be implemented by subclass.")
  316. @classmethod
  317. def bin_path(cls):
  318. """
  319. Return the binary path to the commandline tool used by a specific
  320. subclass. This is a class method so that the tool can be looked for
  321. before making a particular MovieWriter subclass available.
  322. """
  323. return str(mpl.rcParams[cls._exec_key])
  324. @classmethod
  325. def isAvailable(cls):
  326. """Return whether a MovieWriter subclass is actually available."""
  327. return shutil.which(cls.bin_path()) is not None
  328. class FileMovieWriter(MovieWriter):
  329. """
  330. `MovieWriter` for writing to individual files and stitching at the end.
  331. This must be sub-classed to be useful.
  332. """
  333. def __init__(self, *args, **kwargs):
  334. super().__init__(*args, **kwargs)
  335. self.frame_format = mpl.rcParams['animation.frame_format']
  336. def setup(self, fig, outfile, dpi=None, frame_prefix=None):
  337. """
  338. Setup for writing the movie file.
  339. Parameters
  340. ----------
  341. fig : `~matplotlib.figure.Figure`
  342. The figure to grab the rendered frames from.
  343. outfile : str
  344. The filename of the resulting movie file.
  345. dpi : float, default: ``fig.dpi``
  346. The dpi of the output file. This, with the figure size,
  347. controls the size in pixels of the resulting movie file.
  348. frame_prefix : str, optional
  349. The filename prefix to use for temporary files. If *None* (the
  350. default), files are written to a temporary directory which is
  351. deleted by `finish`; if not *None*, no temporary files are
  352. deleted.
  353. """
  354. # Check that path is valid
  355. Path(outfile).parent.resolve(strict=True)
  356. self.fig = fig
  357. self.outfile = outfile
  358. if dpi is None:
  359. dpi = self.fig.dpi
  360. self.dpi = dpi
  361. self._adjust_frame_size()
  362. if frame_prefix is None:
  363. self._tmpdir = TemporaryDirectory()
  364. self.temp_prefix = str(Path(self._tmpdir.name, 'tmp'))
  365. else:
  366. self._tmpdir = None
  367. self.temp_prefix = frame_prefix
  368. self._frame_counter = 0 # used for generating sequential file names
  369. self._temp_paths = list()
  370. self.fname_format_str = '%s%%07d.%s'
  371. def __del__(self):
  372. if hasattr(self, '_tmpdir') and self._tmpdir:
  373. self._tmpdir.cleanup()
  374. @property
  375. def frame_format(self):
  376. """
  377. Format (png, jpeg, etc.) to use for saving the frames, which can be
  378. decided by the individual subclasses.
  379. """
  380. return self._frame_format
  381. @frame_format.setter
  382. def frame_format(self, frame_format):
  383. if frame_format in self.supported_formats:
  384. self._frame_format = frame_format
  385. else:
  386. _api.warn_external(
  387. f"Ignoring file format {frame_format!r} which is not "
  388. f"supported by {type(self).__name__}; using "
  389. f"{self.supported_formats[0]} instead.")
  390. self._frame_format = self.supported_formats[0]
  391. def _base_temp_name(self):
  392. # Generates a template name (without number) given the frame format
  393. # for extension and the prefix.
  394. return self.fname_format_str % (self.temp_prefix, self.frame_format)
  395. def grab_frame(self, **savefig_kwargs):
  396. # docstring inherited
  397. # Creates a filename for saving using basename and counter.
  398. _validate_grabframe_kwargs(savefig_kwargs)
  399. path = Path(self._base_temp_name() % self._frame_counter)
  400. self._temp_paths.append(path) # Record the filename for later use.
  401. self._frame_counter += 1 # Ensures each created name is unique.
  402. _log.debug('FileMovieWriter.grab_frame: Grabbing frame %d to path=%s',
  403. self._frame_counter, path)
  404. with open(path, 'wb') as sink: # Save figure to the sink.
  405. self.fig.savefig(sink, format=self.frame_format, dpi=self.dpi,
  406. **savefig_kwargs)
  407. def finish(self):
  408. # Call run here now that all frame grabbing is done. All temp files
  409. # are available to be assembled.
  410. try:
  411. self._run()
  412. super().finish()
  413. finally:
  414. if self._tmpdir:
  415. _log.debug(
  416. 'MovieWriter: clearing temporary path=%s', self._tmpdir
  417. )
  418. self._tmpdir.cleanup()
  419. @writers.register('pillow')
  420. class PillowWriter(AbstractMovieWriter):
  421. @classmethod
  422. def isAvailable(cls):
  423. return True
  424. def setup(self, fig, outfile, dpi=None):
  425. super().setup(fig, outfile, dpi=dpi)
  426. self._frames = []
  427. def grab_frame(self, **savefig_kwargs):
  428. _validate_grabframe_kwargs(savefig_kwargs)
  429. buf = BytesIO()
  430. self.fig.savefig(
  431. buf, **{**savefig_kwargs, "format": "rgba", "dpi": self.dpi})
  432. self._frames.append(Image.frombuffer(
  433. "RGBA", self.frame_size, buf.getbuffer(), "raw", "RGBA", 0, 1))
  434. def finish(self):
  435. self._frames[0].save(
  436. self.outfile, save_all=True, append_images=self._frames[1:],
  437. duration=int(1000 / self.fps), loop=0)
  438. # Base class of ffmpeg information. Has the config keys and the common set
  439. # of arguments that controls the *output* side of things.
  440. class FFMpegBase:
  441. """
  442. Mixin class for FFMpeg output.
  443. This is a base class for the concrete `FFMpegWriter` and `FFMpegFileWriter`
  444. classes.
  445. """
  446. _exec_key = 'animation.ffmpeg_path'
  447. _args_key = 'animation.ffmpeg_args'
  448. @property
  449. def output_args(self):
  450. args = []
  451. if Path(self.outfile).suffix == '.gif':
  452. self.codec = 'gif'
  453. else:
  454. args.extend(['-vcodec', self.codec])
  455. extra_args = (self.extra_args if self.extra_args is not None
  456. else mpl.rcParams[self._args_key])
  457. # For h264, the default format is yuv444p, which is not compatible
  458. # with quicktime (and others). Specifying yuv420p fixes playback on
  459. # iOS, as well as HTML5 video in firefox and safari (on both Win and
  460. # OSX). Also fixes internet explorer. This is as of 2015/10/29.
  461. if self.codec == 'h264' and '-pix_fmt' not in extra_args:
  462. args.extend(['-pix_fmt', 'yuv420p'])
  463. # For GIF, we're telling FFMPEG to split the video stream, to generate
  464. # a palette, and then use it for encoding.
  465. elif self.codec == 'gif' and '-filter_complex' not in extra_args:
  466. args.extend(['-filter_complex',
  467. 'split [a][b];[a] palettegen [p];[b][p] paletteuse'])
  468. if self.bitrate > 0:
  469. args.extend(['-b', '%dk' % self.bitrate]) # %dk: bitrate in kbps.
  470. for k, v in self.metadata.items():
  471. args.extend(['-metadata', f'{k}={v}'])
  472. args.extend(extra_args)
  473. return args + ['-y', self.outfile]
  474. # Combine FFMpeg options with pipe-based writing
  475. @writers.register('ffmpeg')
  476. class FFMpegWriter(FFMpegBase, MovieWriter):
  477. """
  478. Pipe-based ffmpeg writer.
  479. Frames are streamed directly to ffmpeg via a pipe and written in a single pass.
  480. This effectively works as a slideshow input to ffmpeg with the fps passed as
  481. ``-framerate``, so see also `their notes on frame rates`_ for further details.
  482. .. _their notes on frame rates: https://trac.ffmpeg.org/wiki/Slideshow#Framerates
  483. """
  484. def _args(self):
  485. # Returns the command line parameters for subprocess to use
  486. # ffmpeg to create a movie using a pipe.
  487. args = [self.bin_path(), '-f', 'rawvideo', '-vcodec', 'rawvideo',
  488. '-s', '%dx%d' % self.frame_size, '-pix_fmt', self.frame_format,
  489. '-framerate', str(self.fps)]
  490. # Logging is quieted because subprocess.PIPE has limited buffer size.
  491. # If you have a lot of frames in your animation and set logging to
  492. # DEBUG, you will have a buffer overrun.
  493. if _log.getEffectiveLevel() > logging.DEBUG:
  494. args += ['-loglevel', 'error']
  495. args += ['-i', 'pipe:'] + self.output_args
  496. return args
  497. # Combine FFMpeg options with temp file-based writing
  498. @writers.register('ffmpeg_file')
  499. class FFMpegFileWriter(FFMpegBase, FileMovieWriter):
  500. """
  501. File-based ffmpeg writer.
  502. Frames are written to temporary files on disk and then stitched together at the end.
  503. This effectively works as a slideshow input to ffmpeg with the fps passed as
  504. ``-framerate``, so see also `their notes on frame rates`_ for further details.
  505. .. _their notes on frame rates: https://trac.ffmpeg.org/wiki/Slideshow#Framerates
  506. """
  507. supported_formats = ['png', 'jpeg', 'tiff', 'raw', 'rgba']
  508. def _args(self):
  509. # Returns the command line parameters for subprocess to use
  510. # ffmpeg to create a movie using a collection of temp images
  511. args = []
  512. # For raw frames, we need to explicitly tell ffmpeg the metadata.
  513. if self.frame_format in {'raw', 'rgba'}:
  514. args += [
  515. '-f', 'image2', '-vcodec', 'rawvideo',
  516. '-video_size', '%dx%d' % self.frame_size,
  517. '-pixel_format', 'rgba',
  518. ]
  519. args += ['-framerate', str(self.fps), '-i', self._base_temp_name()]
  520. if not self._tmpdir:
  521. args += ['-frames:v', str(self._frame_counter)]
  522. # Logging is quieted because subprocess.PIPE has limited buffer size.
  523. # If you have a lot of frames in your animation and set logging to
  524. # DEBUG, you will have a buffer overrun.
  525. if _log.getEffectiveLevel() > logging.DEBUG:
  526. args += ['-loglevel', 'error']
  527. return [self.bin_path(), *args, *self.output_args]
  528. # Base class for animated GIFs with ImageMagick
  529. class ImageMagickBase:
  530. """
  531. Mixin class for ImageMagick output.
  532. This is a base class for the concrete `ImageMagickWriter` and
  533. `ImageMagickFileWriter` classes, which define an ``input_names`` attribute
  534. (or property) specifying the input names passed to ImageMagick.
  535. """
  536. _exec_key = 'animation.convert_path'
  537. _args_key = 'animation.convert_args'
  538. def _args(self):
  539. # ImageMagick does not recognize "raw".
  540. fmt = "rgba" if self.frame_format == "raw" else self.frame_format
  541. extra_args = (self.extra_args if self.extra_args is not None
  542. else mpl.rcParams[self._args_key])
  543. return [
  544. self.bin_path(),
  545. "-size", "%ix%i" % self.frame_size,
  546. "-depth", "8",
  547. "-delay", str(100 / self.fps),
  548. "-loop", "0",
  549. f"{fmt}:{self.input_names}",
  550. *extra_args,
  551. self.outfile,
  552. ]
  553. @classmethod
  554. def bin_path(cls):
  555. binpath = super().bin_path()
  556. if binpath == 'convert':
  557. binpath = mpl._get_executable_info('magick').executable
  558. return binpath
  559. @classmethod
  560. def isAvailable(cls):
  561. try:
  562. return super().isAvailable()
  563. except mpl.ExecutableNotFoundError as _enf:
  564. # May be raised by get_executable_info.
  565. _log.debug('ImageMagick unavailable due to: %s', _enf)
  566. return False
  567. # Combine ImageMagick options with pipe-based writing
  568. @writers.register('imagemagick')
  569. class ImageMagickWriter(ImageMagickBase, MovieWriter):
  570. """
  571. Pipe-based animated gif writer.
  572. Frames are streamed directly to ImageMagick via a pipe and written
  573. in a single pass.
  574. """
  575. input_names = "-" # stdin
  576. # Combine ImageMagick options with temp file-based writing
  577. @writers.register('imagemagick_file')
  578. class ImageMagickFileWriter(ImageMagickBase, FileMovieWriter):
  579. """
  580. File-based animated gif writer.
  581. Frames are written to temporary files on disk and then stitched
  582. together at the end.
  583. """
  584. supported_formats = ['png', 'jpeg', 'tiff', 'raw', 'rgba']
  585. input_names = property(
  586. lambda self: f'{self.temp_prefix}*.{self.frame_format}')
  587. # Taken directly from jakevdp's JSAnimation package at
  588. # http://github.com/jakevdp/JSAnimation
  589. def _included_frames(frame_count, frame_format, frame_dir):
  590. return INCLUDED_FRAMES.format(Nframes=frame_count,
  591. frame_dir=frame_dir,
  592. frame_format=frame_format)
  593. def _embedded_frames(frame_list, frame_format):
  594. """frame_list should be a list of base64-encoded png files"""
  595. if frame_format == 'svg':
  596. # Fix MIME type for svg
  597. frame_format = 'svg+xml'
  598. template = ' frames[{0}] = "data:image/{1};base64,{2}"\n'
  599. return "\n" + "".join(
  600. template.format(i, frame_format, frame_data.replace('\n', '\\\n'))
  601. for i, frame_data in enumerate(frame_list))
  602. @writers.register('html')
  603. class HTMLWriter(FileMovieWriter):
  604. """Writer for JavaScript-based HTML movies."""
  605. supported_formats = ['png', 'jpeg', 'tiff', 'svg']
  606. @classmethod
  607. def isAvailable(cls):
  608. return True
  609. def __init__(self, fps=30, codec=None, bitrate=None, extra_args=None,
  610. metadata=None, embed_frames=False, default_mode='loop',
  611. embed_limit=None):
  612. if extra_args:
  613. _log.warning("HTMLWriter ignores 'extra_args'")
  614. extra_args = () # Don't lookup nonexistent rcParam[args_key].
  615. self.embed_frames = embed_frames
  616. self.default_mode = default_mode.lower()
  617. _api.check_in_list(['loop', 'once', 'reflect'],
  618. default_mode=self.default_mode)
  619. # Save embed limit, which is given in MB
  620. self._bytes_limit = mpl._val_or_rc(embed_limit, 'animation.embed_limit')
  621. # Convert from MB to bytes
  622. self._bytes_limit *= 1024 * 1024
  623. super().__init__(fps, codec, bitrate, extra_args, metadata)
  624. def setup(self, fig, outfile, dpi=None, frame_dir=None):
  625. outfile = Path(outfile)
  626. _api.check_in_list(['.html', '.htm'], outfile_extension=outfile.suffix)
  627. self._saved_frames = []
  628. self._total_bytes = 0
  629. self._hit_limit = False
  630. if not self.embed_frames:
  631. if frame_dir is None:
  632. frame_dir = outfile.with_name(outfile.stem + '_frames')
  633. frame_dir.mkdir(parents=True, exist_ok=True)
  634. frame_prefix = frame_dir / 'frame'
  635. else:
  636. frame_prefix = None
  637. super().setup(fig, outfile, dpi, frame_prefix)
  638. self._clear_temp = False
  639. def grab_frame(self, **savefig_kwargs):
  640. _validate_grabframe_kwargs(savefig_kwargs)
  641. if self.embed_frames:
  642. # Just stop processing if we hit the limit
  643. if self._hit_limit:
  644. return
  645. f = BytesIO()
  646. self.fig.savefig(f, format=self.frame_format,
  647. dpi=self.dpi, **savefig_kwargs)
  648. imgdata64 = base64.encodebytes(f.getvalue()).decode('ascii')
  649. self._total_bytes += len(imgdata64)
  650. if self._total_bytes >= self._bytes_limit:
  651. _log.warning(
  652. "Animation size has reached %s bytes, exceeding the limit "
  653. "of %s. If you're sure you want a larger animation "
  654. "embedded, set the animation.embed_limit rc parameter to "
  655. "a larger value (in MB). This and further frames will be "
  656. "dropped.", self._total_bytes, self._bytes_limit)
  657. self._hit_limit = True
  658. else:
  659. self._saved_frames.append(imgdata64)
  660. else:
  661. return super().grab_frame(**savefig_kwargs)
  662. def finish(self):
  663. # save the frames to an html file
  664. if self.embed_frames:
  665. fill_frames = _embedded_frames(self._saved_frames,
  666. self.frame_format)
  667. frame_count = len(self._saved_frames)
  668. else:
  669. # temp names is filled by FileMovieWriter
  670. frame_count = len(self._temp_paths)
  671. fill_frames = _included_frames(
  672. frame_count, self.frame_format,
  673. self._temp_paths[0].parent.relative_to(self.outfile.parent))
  674. mode_dict = dict(once_checked='',
  675. loop_checked='',
  676. reflect_checked='')
  677. mode_dict[self.default_mode + '_checked'] = 'checked'
  678. interval = 1000 // self.fps
  679. with open(self.outfile, 'w') as of:
  680. of.write(JS_INCLUDE + STYLE_INCLUDE)
  681. of.write(DISPLAY_TEMPLATE.format(id=uuid.uuid4().hex,
  682. Nframes=frame_count,
  683. fill_frames=fill_frames,
  684. interval=interval,
  685. **mode_dict))
  686. # Duplicate the temporary file clean up logic from
  687. # FileMovieWriter.finish. We cannot call the inherited version of
  688. # finish because it assumes that there is a subprocess that we either
  689. # need to call to merge many frames together or that there is a
  690. # subprocess call that we need to clean up.
  691. if self._tmpdir:
  692. _log.debug('MovieWriter: clearing temporary path=%s', self._tmpdir)
  693. self._tmpdir.cleanup()
  694. class Animation:
  695. """
  696. A base class for Animations.
  697. This class is not usable as is, and should be subclassed to provide needed
  698. behavior.
  699. .. note::
  700. You must store the created Animation in a variable that lives as long
  701. as the animation should run. Otherwise, the Animation object will be
  702. garbage-collected and the animation stops.
  703. Parameters
  704. ----------
  705. fig : `~matplotlib.figure.Figure`
  706. The figure object used to get needed events, such as draw or resize.
  707. event_source : object, optional
  708. A class that can run a callback when desired events
  709. are generated, as well as be stopped and started.
  710. Examples include timers (see `TimedAnimation`) and file
  711. system notifications.
  712. blit : bool, default: False
  713. Whether blitting is used to optimize drawing. If the backend does not
  714. support blitting, then this parameter has no effect.
  715. See Also
  716. --------
  717. FuncAnimation, ArtistAnimation
  718. """
  719. def __init__(self, fig, event_source=None, blit=False):
  720. self._draw_was_started = False
  721. self._fig = fig
  722. # Disables blitting for backends that don't support it. This
  723. # allows users to request it if available, but still have a
  724. # fallback that works if it is not.
  725. self._blit = blit and fig.canvas.supports_blit
  726. # These are the basics of the animation. The frame sequence represents
  727. # information for each frame of the animation and depends on how the
  728. # drawing is handled by the subclasses. The event source fires events
  729. # that cause the frame sequence to be iterated.
  730. self.frame_seq = self.new_frame_seq()
  731. self.event_source = event_source
  732. # Instead of starting the event source now, we connect to the figure's
  733. # draw_event, so that we only start once the figure has been drawn.
  734. self._first_draw_id = fig.canvas.mpl_connect('draw_event', self._start)
  735. # Connect to the figure's close_event so that we don't continue to
  736. # fire events and try to draw to a deleted figure.
  737. self._close_id = self._fig.canvas.mpl_connect('close_event',
  738. self._stop)
  739. if self._blit:
  740. self._setup_blit()
  741. def __del__(self):
  742. if not getattr(self, '_draw_was_started', True):
  743. warnings.warn(
  744. 'Animation was deleted without rendering anything. This is '
  745. 'most likely not intended. To prevent deletion, assign the '
  746. 'Animation to a variable, e.g. `anim`, that exists until you '
  747. 'output the Animation using `plt.show()` or '
  748. '`anim.save()`.'
  749. )
  750. def _start(self, *args):
  751. """
  752. Starts interactive animation. Adds the draw frame command to the GUI
  753. handler, calls show to start the event loop.
  754. """
  755. # Do not start the event source if saving() it.
  756. if self._fig.canvas.is_saving():
  757. return
  758. # First disconnect our draw event handler
  759. self._fig.canvas.mpl_disconnect(self._first_draw_id)
  760. # Now do any initial draw
  761. self._init_draw()
  762. # Add our callback for stepping the animation and
  763. # actually start the event_source.
  764. self.event_source.add_callback(self._step)
  765. self.event_source.start()
  766. def _stop(self, *args):
  767. # On stop we disconnect all of our events.
  768. if self._blit:
  769. self._fig.canvas.mpl_disconnect(self._resize_id)
  770. self._fig.canvas.mpl_disconnect(self._close_id)
  771. self.event_source.remove_callback(self._step)
  772. self.event_source = None
  773. def save(self, filename, writer=None, fps=None, dpi=None, codec=None,
  774. bitrate=None, extra_args=None, metadata=None, extra_anim=None,
  775. savefig_kwargs=None, *, progress_callback=None):
  776. """
  777. Save the animation as a movie file by drawing every frame.
  778. Parameters
  779. ----------
  780. filename : str
  781. The output filename, e.g., :file:`mymovie.mp4`.
  782. writer : `MovieWriter` or str, default: :rc:`animation.writer`
  783. A `MovieWriter` instance to use or a key that identifies a
  784. class to use, such as 'ffmpeg'.
  785. fps : int, optional
  786. Movie frame rate (per second). If not set, the frame rate from the
  787. animation's frame interval.
  788. dpi : float, default: :rc:`savefig.dpi`
  789. Controls the dots per inch for the movie frames. Together with
  790. the figure's size in inches, this controls the size of the movie.
  791. codec : str, default: :rc:`animation.codec`.
  792. The video codec to use. Not all codecs are supported by a given
  793. `MovieWriter`.
  794. bitrate : int, default: :rc:`animation.bitrate`
  795. The bitrate of the movie, in kilobits per second. Higher values
  796. means higher quality movies, but increase the file size. A value
  797. of -1 lets the underlying movie encoder select the bitrate.
  798. extra_args : list of str or None, optional
  799. Extra command-line arguments passed to the underlying movie encoder. These
  800. arguments are passed last to the encoder, just before the output filename.
  801. The default, None, means to use :rc:`animation.[name-of-encoder]_args` for
  802. the builtin writers.
  803. metadata : dict[str, str], default: {}
  804. Dictionary of keys and values for metadata to include in
  805. the output file. Some keys that may be of use include:
  806. title, artist, genre, subject, copyright, srcform, comment.
  807. extra_anim : list, default: []
  808. Additional `Animation` objects that should be included
  809. in the saved movie file. These need to be from the same
  810. `.Figure` instance. Also, animation frames will
  811. just be simply combined, so there should be a 1:1 correspondence
  812. between the frames from the different animations.
  813. savefig_kwargs : dict, default: {}
  814. Keyword arguments passed to each `~.Figure.savefig` call used to
  815. save the individual frames.
  816. progress_callback : function, optional
  817. A callback function that will be called for every frame to notify
  818. the saving progress. It must have the signature ::
  819. def func(current_frame: int, total_frames: int) -> Any
  820. where *current_frame* is the current frame number and *total_frames* is the
  821. total number of frames to be saved. *total_frames* is set to None, if the
  822. total number of frames cannot be determined. Return values may exist but are
  823. ignored.
  824. Example code to write the progress to stdout::
  825. progress_callback = lambda i, n: print(f'Saving frame {i}/{n}')
  826. Notes
  827. -----
  828. *fps*, *codec*, *bitrate*, *extra_args* and *metadata* are used to
  829. construct a `.MovieWriter` instance and can only be passed if
  830. *writer* is a string. If they are passed as non-*None* and *writer*
  831. is a `.MovieWriter`, a `RuntimeError` will be raised.
  832. """
  833. all_anim = [self]
  834. if extra_anim is not None:
  835. all_anim.extend(anim for anim in extra_anim
  836. if anim._fig is self._fig)
  837. # Disable "Animation was deleted without rendering" warning.
  838. for anim in all_anim:
  839. anim._draw_was_started = True
  840. if writer is None:
  841. writer = mpl.rcParams['animation.writer']
  842. elif (not isinstance(writer, str) and
  843. any(arg is not None
  844. for arg in (fps, codec, bitrate, extra_args, metadata))):
  845. raise RuntimeError('Passing in values for arguments '
  846. 'fps, codec, bitrate, extra_args, or metadata '
  847. 'is not supported when writer is an existing '
  848. 'MovieWriter instance. These should instead be '
  849. 'passed as arguments when creating the '
  850. 'MovieWriter instance.')
  851. if savefig_kwargs is None:
  852. savefig_kwargs = {}
  853. else:
  854. # we are going to mutate this below
  855. savefig_kwargs = dict(savefig_kwargs)
  856. if fps is None and hasattr(self, '_interval'):
  857. # Convert interval in ms to frames per second
  858. fps = 1000. / self._interval
  859. # Re-use the savefig DPI for ours if none is given
  860. dpi = mpl._val_or_rc(dpi, 'savefig.dpi')
  861. if dpi == 'figure':
  862. dpi = self._fig.dpi
  863. writer_kwargs = {}
  864. if codec is not None:
  865. writer_kwargs['codec'] = codec
  866. if bitrate is not None:
  867. writer_kwargs['bitrate'] = bitrate
  868. if extra_args is not None:
  869. writer_kwargs['extra_args'] = extra_args
  870. if metadata is not None:
  871. writer_kwargs['metadata'] = metadata
  872. # If we have the name of a writer, instantiate an instance of the
  873. # registered class.
  874. if isinstance(writer, str):
  875. try:
  876. writer_cls = writers[writer]
  877. except RuntimeError: # Raised if not available.
  878. writer_cls = PillowWriter # Always available.
  879. _log.warning("MovieWriter %s unavailable; using Pillow "
  880. "instead.", writer)
  881. writer = writer_cls(fps, **writer_kwargs)
  882. _log.info('Animation.save using %s', type(writer))
  883. if 'bbox_inches' in savefig_kwargs:
  884. _log.warning("Warning: discarding the 'bbox_inches' argument in "
  885. "'savefig_kwargs' as it may cause frame size "
  886. "to vary, which is inappropriate for animation.")
  887. savefig_kwargs.pop('bbox_inches')
  888. # Create a new sequence of frames for saved data. This is different
  889. # from new_frame_seq() to give the ability to save 'live' generated
  890. # frame information to be saved later.
  891. # TODO: Right now, after closing the figure, saving a movie won't work
  892. # since GUI widgets are gone. Either need to remove extra code to
  893. # allow for this non-existent use case or find a way to make it work.
  894. facecolor = savefig_kwargs.get('facecolor',
  895. mpl.rcParams['savefig.facecolor'])
  896. if facecolor == 'auto':
  897. facecolor = self._fig.get_facecolor()
  898. def _pre_composite_to_white(color):
  899. r, g, b, a = mcolors.to_rgba(color)
  900. return a * np.array([r, g, b]) + 1 - a
  901. savefig_kwargs['facecolor'] = _pre_composite_to_white(facecolor)
  902. savefig_kwargs['transparent'] = False # just to be safe!
  903. # canvas._is_saving = True makes the draw_event animation-starting
  904. # callback a no-op; canvas.manager = None prevents resizing the GUI
  905. # widget (both are likewise done in savefig()).
  906. with writer.saving(self._fig, filename, dpi), \
  907. cbook._setattr_cm(self._fig.canvas, _is_saving=True, manager=None):
  908. for anim in all_anim:
  909. anim._init_draw() # Clear the initial frame
  910. frame_number = 0
  911. # TODO: Currently only FuncAnimation has a save_count
  912. # attribute. Can we generalize this to all Animations?
  913. save_count_list = [getattr(a, '_save_count', None)
  914. for a in all_anim]
  915. if None in save_count_list:
  916. total_frames = None
  917. else:
  918. total_frames = sum(save_count_list)
  919. for data in zip(*[a.new_saved_frame_seq() for a in all_anim]):
  920. for anim, d in zip(all_anim, data):
  921. # TODO: See if turning off blit is really necessary
  922. anim._draw_next_frame(d, blit=False)
  923. if progress_callback is not None:
  924. progress_callback(frame_number, total_frames)
  925. frame_number += 1
  926. writer.grab_frame(**savefig_kwargs)
  927. def _step(self, *args):
  928. """
  929. Handler for getting events. By default, gets the next frame in the
  930. sequence and hands the data off to be drawn.
  931. """
  932. # Returns True to indicate that the event source should continue to
  933. # call _step, until the frame sequence reaches the end of iteration,
  934. # at which point False will be returned.
  935. try:
  936. framedata = next(self.frame_seq)
  937. self._draw_next_frame(framedata, self._blit)
  938. return True
  939. except StopIteration:
  940. return False
  941. def new_frame_seq(self):
  942. """Return a new sequence of frame information."""
  943. # Default implementation is just an iterator over self._framedata
  944. return iter(self._framedata)
  945. def new_saved_frame_seq(self):
  946. """Return a new sequence of saved/cached frame information."""
  947. # Default is the same as the regular frame sequence
  948. return self.new_frame_seq()
  949. def _draw_next_frame(self, framedata, blit):
  950. # Breaks down the drawing of the next frame into steps of pre- and
  951. # post- draw, as well as the drawing of the frame itself.
  952. self._pre_draw(framedata, blit)
  953. self._draw_frame(framedata)
  954. self._post_draw(framedata, blit)
  955. def _init_draw(self):
  956. # Initial draw to clear the frame. Also used by the blitting code
  957. # when a clean base is required.
  958. self._draw_was_started = True
  959. def _pre_draw(self, framedata, blit):
  960. # Perform any cleaning or whatnot before the drawing of the frame.
  961. # This default implementation allows blit to clear the frame.
  962. if blit:
  963. self._blit_clear(self._drawn_artists)
  964. def _draw_frame(self, framedata):
  965. # Performs actual drawing of the frame.
  966. raise NotImplementedError('Needs to be implemented by subclasses to'
  967. ' actually make an animation.')
  968. def _post_draw(self, framedata, blit):
  969. # After the frame is rendered, this handles the actual flushing of
  970. # the draw, which can be a direct draw_idle() or make use of the
  971. # blitting.
  972. if blit and self._drawn_artists:
  973. self._blit_draw(self._drawn_artists)
  974. else:
  975. self._fig.canvas.draw_idle()
  976. # The rest of the code in this class is to facilitate easy blitting
  977. def _blit_draw(self, artists):
  978. # Handles blitted drawing, which renders only the artists given instead
  979. # of the entire figure.
  980. updated_ax = {a.axes for a in artists}
  981. # Enumerate artists to cache Axes backgrounds. We do not draw
  982. # artists yet to not cache foreground from plots with shared axes
  983. for ax in updated_ax:
  984. # If we haven't cached the background for the current view of this
  985. # Axes object, do so now. This might not always be reliable, but
  986. # it's an attempt to automate the process.
  987. cur_view = ax._get_view()
  988. view, bg = self._blit_cache.get(ax, (object(), None))
  989. if cur_view != view:
  990. self._blit_cache[ax] = (
  991. cur_view, ax.figure.canvas.copy_from_bbox(ax.bbox))
  992. # Make a separate pass to draw foreground.
  993. for a in artists:
  994. a.axes.draw_artist(a)
  995. # After rendering all the needed artists, blit each Axes individually.
  996. for ax in updated_ax:
  997. ax.figure.canvas.blit(ax.bbox)
  998. def _blit_clear(self, artists):
  999. # Get a list of the Axes that need clearing from the artists that
  1000. # have been drawn. Grab the appropriate saved background from the
  1001. # cache and restore.
  1002. axes = {a.axes for a in artists}
  1003. for ax in axes:
  1004. try:
  1005. view, bg = self._blit_cache[ax]
  1006. except KeyError:
  1007. continue
  1008. if ax._get_view() == view:
  1009. ax.figure.canvas.restore_region(bg)
  1010. else:
  1011. self._blit_cache.pop(ax)
  1012. def _setup_blit(self):
  1013. # Setting up the blit requires: a cache of the background for the Axes
  1014. self._blit_cache = dict()
  1015. self._drawn_artists = []
  1016. # _post_draw needs to be called first to initialize the renderer
  1017. self._post_draw(None, self._blit)
  1018. # Then we need to clear the Frame for the initial draw
  1019. # This is typically handled in _on_resize because QT and Tk
  1020. # emit a resize event on launch, but the macosx backend does not,
  1021. # thus we force it here for everyone for consistency
  1022. self._init_draw()
  1023. # Connect to future resize events
  1024. self._resize_id = self._fig.canvas.mpl_connect('resize_event',
  1025. self._on_resize)
  1026. def _on_resize(self, event):
  1027. # On resize, we need to disable the resize event handling so we don't
  1028. # get too many events. Also stop the animation events, so that
  1029. # we're paused. Reset the cache and re-init. Set up an event handler
  1030. # to catch once the draw has actually taken place.
  1031. self._fig.canvas.mpl_disconnect(self._resize_id)
  1032. self.event_source.stop()
  1033. self._blit_cache.clear()
  1034. self._init_draw()
  1035. self._resize_id = self._fig.canvas.mpl_connect('draw_event',
  1036. self._end_redraw)
  1037. def _end_redraw(self, event):
  1038. # Now that the redraw has happened, do the post draw flushing and
  1039. # blit handling. Then re-enable all of the original events.
  1040. self._post_draw(None, False)
  1041. self.event_source.start()
  1042. self._fig.canvas.mpl_disconnect(self._resize_id)
  1043. self._resize_id = self._fig.canvas.mpl_connect('resize_event',
  1044. self._on_resize)
  1045. def to_html5_video(self, embed_limit=None):
  1046. """
  1047. Convert the animation to an HTML5 ``<video>`` tag.
  1048. This saves the animation as an h264 video, encoded in base64
  1049. directly into the HTML5 video tag. This respects :rc:`animation.writer`
  1050. and :rc:`animation.bitrate`. This also makes use of the
  1051. *interval* to control the speed, and uses the *repeat*
  1052. parameter to decide whether to loop.
  1053. Parameters
  1054. ----------
  1055. embed_limit : float, optional
  1056. Limit, in MB, of the returned animation. No animation is created
  1057. if the limit is exceeded.
  1058. Defaults to :rc:`animation.embed_limit` = 20.0.
  1059. Returns
  1060. -------
  1061. str
  1062. An HTML5 video tag with the animation embedded as base64 encoded
  1063. h264 video.
  1064. If the *embed_limit* is exceeded, this returns the string
  1065. "Video too large to embed."
  1066. """
  1067. VIDEO_TAG = r'''<video {size} {options}>
  1068. <source type="video/mp4" src="data:video/mp4;base64,{video}">
  1069. Your browser does not support the video tag.
  1070. </video>'''
  1071. # Cache the rendering of the video as HTML
  1072. if not hasattr(self, '_base64_video'):
  1073. # Save embed limit, which is given in MB
  1074. embed_limit = mpl._val_or_rc(embed_limit, 'animation.embed_limit')
  1075. # Convert from MB to bytes
  1076. embed_limit *= 1024 * 1024
  1077. # Can't open a NamedTemporaryFile twice on Windows, so use a
  1078. # TemporaryDirectory instead.
  1079. with TemporaryDirectory() as tmpdir:
  1080. path = Path(tmpdir, "temp.m4v")
  1081. # We create a writer manually so that we can get the
  1082. # appropriate size for the tag
  1083. Writer = writers[mpl.rcParams['animation.writer']]
  1084. writer = Writer(codec='h264',
  1085. bitrate=mpl.rcParams['animation.bitrate'],
  1086. fps=1000. / self._interval)
  1087. self.save(str(path), writer=writer)
  1088. # Now open and base64 encode.
  1089. vid64 = base64.encodebytes(path.read_bytes())
  1090. vid_len = len(vid64)
  1091. if vid_len >= embed_limit:
  1092. _log.warning(
  1093. "Animation movie is %s bytes, exceeding the limit of %s. "
  1094. "If you're sure you want a large animation embedded, set "
  1095. "the animation.embed_limit rc parameter to a larger value "
  1096. "(in MB).", vid_len, embed_limit)
  1097. else:
  1098. self._base64_video = vid64.decode('ascii')
  1099. self._video_size = 'width="{}" height="{}"'.format(
  1100. *writer.frame_size)
  1101. # If we exceeded the size, this attribute won't exist
  1102. if hasattr(self, '_base64_video'):
  1103. # Default HTML5 options are to autoplay and display video controls
  1104. options = ['controls', 'autoplay']
  1105. # If we're set to repeat, make it loop
  1106. if getattr(self, '_repeat', False):
  1107. options.append('loop')
  1108. return VIDEO_TAG.format(video=self._base64_video,
  1109. size=self._video_size,
  1110. options=' '.join(options))
  1111. else:
  1112. return 'Video too large to embed.'
  1113. def to_jshtml(self, fps=None, embed_frames=True, default_mode=None):
  1114. """
  1115. Generate HTML representation of the animation.
  1116. Parameters
  1117. ----------
  1118. fps : int, optional
  1119. Movie frame rate (per second). If not set, the frame rate from
  1120. the animation's frame interval.
  1121. embed_frames : bool, optional
  1122. default_mode : str, optional
  1123. What to do when the animation ends. Must be one of ``{'loop',
  1124. 'once', 'reflect'}``. Defaults to ``'loop'`` if the *repeat*
  1125. parameter is True, otherwise ``'once'``.
  1126. """
  1127. if fps is None and hasattr(self, '_interval'):
  1128. # Convert interval in ms to frames per second
  1129. fps = 1000 / self._interval
  1130. # If we're not given a default mode, choose one base on the value of
  1131. # the _repeat attribute
  1132. if default_mode is None:
  1133. default_mode = 'loop' if getattr(self, '_repeat',
  1134. False) else 'once'
  1135. if not hasattr(self, "_html_representation"):
  1136. # Can't open a NamedTemporaryFile twice on Windows, so use a
  1137. # TemporaryDirectory instead.
  1138. with TemporaryDirectory() as tmpdir:
  1139. path = Path(tmpdir, "temp.html")
  1140. writer = HTMLWriter(fps=fps,
  1141. embed_frames=embed_frames,
  1142. default_mode=default_mode)
  1143. self.save(str(path), writer=writer)
  1144. self._html_representation = path.read_text()
  1145. return self._html_representation
  1146. def _repr_html_(self):
  1147. """IPython display hook for rendering."""
  1148. fmt = mpl.rcParams['animation.html']
  1149. if fmt == 'html5':
  1150. return self.to_html5_video()
  1151. elif fmt == 'jshtml':
  1152. return self.to_jshtml()
  1153. def pause(self):
  1154. """Pause the animation."""
  1155. self.event_source.stop()
  1156. if self._blit:
  1157. for artist in self._drawn_artists:
  1158. artist.set_animated(False)
  1159. def resume(self):
  1160. """Resume the animation."""
  1161. self.event_source.start()
  1162. if self._blit:
  1163. for artist in self._drawn_artists:
  1164. artist.set_animated(True)
  1165. class TimedAnimation(Animation):
  1166. """
  1167. `Animation` subclass for time-based animation.
  1168. A new frame is drawn every *interval* milliseconds.
  1169. .. note::
  1170. You must store the created Animation in a variable that lives as long
  1171. as the animation should run. Otherwise, the Animation object will be
  1172. garbage-collected and the animation stops.
  1173. Parameters
  1174. ----------
  1175. fig : `~matplotlib.figure.Figure`
  1176. The figure object used to get needed events, such as draw or resize.
  1177. interval : int, default: 200
  1178. Delay between frames in milliseconds.
  1179. repeat_delay : int, default: 0
  1180. The delay in milliseconds between consecutive animation runs, if
  1181. *repeat* is True.
  1182. repeat : bool, default: True
  1183. Whether the animation repeats when the sequence of frames is completed.
  1184. blit : bool, default: False
  1185. Whether blitting is used to optimize drawing.
  1186. """
  1187. def __init__(self, fig, interval=200, repeat_delay=0, repeat=True,
  1188. event_source=None, *args, **kwargs):
  1189. self._interval = interval
  1190. # Undocumented support for repeat_delay = None as backcompat.
  1191. self._repeat_delay = repeat_delay if repeat_delay is not None else 0
  1192. self._repeat = repeat
  1193. # If we're not given an event source, create a new timer. This permits
  1194. # sharing timers between animation objects for syncing animations.
  1195. if event_source is None:
  1196. event_source = fig.canvas.new_timer(interval=self._interval)
  1197. super().__init__(fig, event_source=event_source, *args, **kwargs)
  1198. def _step(self, *args):
  1199. """Handler for getting events."""
  1200. # Extends the _step() method for the Animation class. If
  1201. # Animation._step signals that it reached the end and we want to
  1202. # repeat, we refresh the frame sequence and return True. If
  1203. # _repeat_delay is set, change the event_source's interval to our loop
  1204. # delay and set the callback to one which will then set the interval
  1205. # back.
  1206. still_going = super()._step(*args)
  1207. if not still_going:
  1208. if self._repeat:
  1209. # Restart the draw loop
  1210. self._init_draw()
  1211. self.frame_seq = self.new_frame_seq()
  1212. self.event_source.interval = self._repeat_delay
  1213. return True
  1214. else:
  1215. # We are done with the animation. Call pause to remove
  1216. # animated flags from artists that were using blitting
  1217. self.pause()
  1218. if self._blit:
  1219. # Remove the resize callback if we were blitting
  1220. self._fig.canvas.mpl_disconnect(self._resize_id)
  1221. self._fig.canvas.mpl_disconnect(self._close_id)
  1222. self.event_source = None
  1223. return False
  1224. self.event_source.interval = self._interval
  1225. return True
  1226. repeat = _api.deprecate_privatize_attribute("3.7")
  1227. class ArtistAnimation(TimedAnimation):
  1228. """
  1229. `TimedAnimation` subclass that creates an animation by using a fixed
  1230. set of `.Artist` objects.
  1231. Before creating an instance, all plotting should have taken place
  1232. and the relevant artists saved.
  1233. .. note::
  1234. You must store the created Animation in a variable that lives as long
  1235. as the animation should run. Otherwise, the Animation object will be
  1236. garbage-collected and the animation stops.
  1237. Parameters
  1238. ----------
  1239. fig : `~matplotlib.figure.Figure`
  1240. The figure object used to get needed events, such as draw or resize.
  1241. artists : list
  1242. Each list entry is a collection of `.Artist` objects that are made
  1243. visible on the corresponding frame. Other artists are made invisible.
  1244. interval : int, default: 200
  1245. Delay between frames in milliseconds.
  1246. repeat_delay : int, default: 0
  1247. The delay in milliseconds between consecutive animation runs, if
  1248. *repeat* is True.
  1249. repeat : bool, default: True
  1250. Whether the animation repeats when the sequence of frames is completed.
  1251. blit : bool, default: False
  1252. Whether blitting is used to optimize drawing.
  1253. """
  1254. def __init__(self, fig, artists, *args, **kwargs):
  1255. # Internal list of artists drawn in the most recent frame.
  1256. self._drawn_artists = []
  1257. # Use the list of artists as the framedata, which will be iterated
  1258. # over by the machinery.
  1259. self._framedata = artists
  1260. super().__init__(fig, *args, **kwargs)
  1261. def _init_draw(self):
  1262. super()._init_draw()
  1263. # Make all the artists involved in *any* frame invisible
  1264. figs = set()
  1265. for f in self.new_frame_seq():
  1266. for artist in f:
  1267. artist.set_visible(False)
  1268. artist.set_animated(self._blit)
  1269. # Assemble a list of unique figures that need flushing
  1270. if artist.get_figure() not in figs:
  1271. figs.add(artist.get_figure())
  1272. # Flush the needed figures
  1273. for fig in figs:
  1274. fig.canvas.draw_idle()
  1275. def _pre_draw(self, framedata, blit):
  1276. """Clears artists from the last frame."""
  1277. if blit:
  1278. # Let blit handle clearing
  1279. self._blit_clear(self._drawn_artists)
  1280. else:
  1281. # Otherwise, make all the artists from the previous frame invisible
  1282. for artist in self._drawn_artists:
  1283. artist.set_visible(False)
  1284. def _draw_frame(self, artists):
  1285. # Save the artists that were passed in as framedata for the other
  1286. # steps (esp. blitting) to use.
  1287. self._drawn_artists = artists
  1288. # Make all the artists from the current frame visible
  1289. for artist in artists:
  1290. artist.set_visible(True)
  1291. class FuncAnimation(TimedAnimation):
  1292. """
  1293. `TimedAnimation` subclass that makes an animation by repeatedly calling
  1294. a function *func*.
  1295. .. note::
  1296. You must store the created Animation in a variable that lives as long
  1297. as the animation should run. Otherwise, the Animation object will be
  1298. garbage-collected and the animation stops.
  1299. Parameters
  1300. ----------
  1301. fig : `~matplotlib.figure.Figure`
  1302. The figure object used to get needed events, such as draw or resize.
  1303. func : callable
  1304. The function to call at each frame. The first argument will
  1305. be the next value in *frames*. Any additional positional
  1306. arguments can be supplied using `functools.partial` or via the *fargs*
  1307. parameter.
  1308. The required signature is::
  1309. def func(frame, *fargs) -> iterable_of_artists
  1310. It is often more convenient to provide the arguments using
  1311. `functools.partial`. In this way it is also possible to pass keyword
  1312. arguments. To pass a function with both positional and keyword
  1313. arguments, set all arguments as keyword arguments, just leaving the
  1314. *frame* argument unset::
  1315. def func(frame, art, *, y=None):
  1316. ...
  1317. ani = FuncAnimation(fig, partial(func, art=ln, y='foo'))
  1318. If ``blit == True``, *func* must return an iterable of all artists
  1319. that were modified or created. This information is used by the blitting
  1320. algorithm to determine which parts of the figure have to be updated.
  1321. The return value is unused if ``blit == False`` and may be omitted in
  1322. that case.
  1323. frames : iterable, int, generator function, or None, optional
  1324. Source of data to pass *func* and each frame of the animation
  1325. - If an iterable, then simply use the values provided. If the
  1326. iterable has a length, it will override the *save_count* kwarg.
  1327. - If an integer, then equivalent to passing ``range(frames)``
  1328. - If a generator function, then must have the signature::
  1329. def gen_function() -> obj
  1330. - If *None*, then equivalent to passing ``itertools.count``.
  1331. In all of these cases, the values in *frames* is simply passed through
  1332. to the user-supplied *func* and thus can be of any type.
  1333. init_func : callable, optional
  1334. A function used to draw a clear frame. If not given, the results of
  1335. drawing from the first item in the frames sequence will be used. This
  1336. function will be called once before the first frame.
  1337. The required signature is::
  1338. def init_func() -> iterable_of_artists
  1339. If ``blit == True``, *init_func* must return an iterable of artists
  1340. to be re-drawn. This information is used by the blitting algorithm to
  1341. determine which parts of the figure have to be updated. The return
  1342. value is unused if ``blit == False`` and may be omitted in that case.
  1343. fargs : tuple or None, optional
  1344. Additional arguments to pass to each call to *func*. Note: the use of
  1345. `functools.partial` is preferred over *fargs*. See *func* for details.
  1346. save_count : int, optional
  1347. Fallback for the number of values from *frames* to cache. This is
  1348. only used if the number of frames cannot be inferred from *frames*,
  1349. i.e. when it's an iterator without length or a generator.
  1350. interval : int, default: 200
  1351. Delay between frames in milliseconds.
  1352. repeat_delay : int, default: 0
  1353. The delay in milliseconds between consecutive animation runs, if
  1354. *repeat* is True.
  1355. repeat : bool, default: True
  1356. Whether the animation repeats when the sequence of frames is completed.
  1357. blit : bool, default: False
  1358. Whether blitting is used to optimize drawing. Note: when using
  1359. blitting, any animated artists will be drawn according to their zorder;
  1360. however, they will be drawn on top of any previous artists, regardless
  1361. of their zorder.
  1362. cache_frame_data : bool, default: True
  1363. Whether frame data is cached. Disabling cache might be helpful when
  1364. frames contain large objects.
  1365. """
  1366. def __init__(self, fig, func, frames=None, init_func=None, fargs=None,
  1367. save_count=None, *, cache_frame_data=True, **kwargs):
  1368. if fargs:
  1369. self._args = fargs
  1370. else:
  1371. self._args = ()
  1372. self._func = func
  1373. self._init_func = init_func
  1374. # Amount of framedata to keep around for saving movies. This is only
  1375. # used if we don't know how many frames there will be: in the case
  1376. # of no generator or in the case of a callable.
  1377. self._save_count = save_count
  1378. # Set up a function that creates a new iterable when needed. If nothing
  1379. # is passed in for frames, just use itertools.count, which will just
  1380. # keep counting from 0. A callable passed in for frames is assumed to
  1381. # be a generator. An iterable will be used as is, and anything else
  1382. # will be treated as a number of frames.
  1383. if frames is None:
  1384. self._iter_gen = itertools.count
  1385. elif callable(frames):
  1386. self._iter_gen = frames
  1387. elif np.iterable(frames):
  1388. if kwargs.get('repeat', True):
  1389. self._tee_from = frames
  1390. def iter_frames(frames=frames):
  1391. this, self._tee_from = itertools.tee(self._tee_from, 2)
  1392. yield from this
  1393. self._iter_gen = iter_frames
  1394. else:
  1395. self._iter_gen = lambda: iter(frames)
  1396. if hasattr(frames, '__len__'):
  1397. self._save_count = len(frames)
  1398. if save_count is not None:
  1399. _api.warn_external(
  1400. f"You passed in an explicit {save_count=} "
  1401. "which is being ignored in favor of "
  1402. f"{len(frames)=}."
  1403. )
  1404. else:
  1405. self._iter_gen = lambda: iter(range(frames))
  1406. self._save_count = frames
  1407. if save_count is not None:
  1408. _api.warn_external(
  1409. f"You passed in an explicit {save_count=} which is being "
  1410. f"ignored in favor of {frames=}."
  1411. )
  1412. if self._save_count is None and cache_frame_data:
  1413. _api.warn_external(
  1414. f"{frames=!r} which we can infer the length of, "
  1415. "did not pass an explicit *save_count* "
  1416. f"and passed {cache_frame_data=}. To avoid a possibly "
  1417. "unbounded cache, frame data caching has been disabled. "
  1418. "To suppress this warning either pass "
  1419. "`cache_frame_data=False` or `save_count=MAX_FRAMES`."
  1420. )
  1421. cache_frame_data = False
  1422. self._cache_frame_data = cache_frame_data
  1423. # Needs to be initialized so the draw functions work without checking
  1424. self._save_seq = []
  1425. super().__init__(fig, **kwargs)
  1426. # Need to reset the saved seq, since right now it will contain data
  1427. # for a single frame from init, which is not what we want.
  1428. self._save_seq = []
  1429. def new_frame_seq(self):
  1430. # Use the generating function to generate a new frame sequence
  1431. return self._iter_gen()
  1432. def new_saved_frame_seq(self):
  1433. # Generate an iterator for the sequence of saved data. If there are
  1434. # no saved frames, generate a new frame sequence and take the first
  1435. # save_count entries in it.
  1436. if self._save_seq:
  1437. # While iterating we are going to update _save_seq
  1438. # so make a copy to safely iterate over
  1439. self._old_saved_seq = list(self._save_seq)
  1440. return iter(self._old_saved_seq)
  1441. else:
  1442. if self._save_count is None:
  1443. frame_seq = self.new_frame_seq()
  1444. def gen():
  1445. try:
  1446. while True:
  1447. yield next(frame_seq)
  1448. except StopIteration:
  1449. pass
  1450. return gen()
  1451. else:
  1452. return itertools.islice(self.new_frame_seq(), self._save_count)
  1453. def _init_draw(self):
  1454. super()._init_draw()
  1455. # Initialize the drawing either using the given init_func or by
  1456. # calling the draw function with the first item of the frame sequence.
  1457. # For blitting, the init_func should return a sequence of modified
  1458. # artists.
  1459. if self._init_func is None:
  1460. try:
  1461. frame_data = next(self.new_frame_seq())
  1462. except StopIteration:
  1463. # we can't start the iteration, it may have already been
  1464. # exhausted by a previous save or just be 0 length.
  1465. # warn and bail.
  1466. warnings.warn(
  1467. "Can not start iterating the frames for the initial draw. "
  1468. "This can be caused by passing in a 0 length sequence "
  1469. "for *frames*.\n\n"
  1470. "If you passed *frames* as a generator "
  1471. "it may be exhausted due to a previous display or save."
  1472. )
  1473. return
  1474. self._draw_frame(frame_data)
  1475. else:
  1476. self._drawn_artists = self._init_func()
  1477. if self._blit:
  1478. if self._drawn_artists is None:
  1479. raise RuntimeError('The init_func must return a '
  1480. 'sequence of Artist objects.')
  1481. for a in self._drawn_artists:
  1482. a.set_animated(self._blit)
  1483. self._save_seq = []
  1484. def _draw_frame(self, framedata):
  1485. if self._cache_frame_data:
  1486. # Save the data for potential saving of movies.
  1487. self._save_seq.append(framedata)
  1488. self._save_seq = self._save_seq[-self._save_count:]
  1489. # Call the func with framedata and args. If blitting is desired,
  1490. # func needs to return a sequence of any artists that were modified.
  1491. self._drawn_artists = self._func(framedata, *self._args)
  1492. if self._blit:
  1493. err = RuntimeError('The animation function must return a sequence '
  1494. 'of Artist objects.')
  1495. try:
  1496. # check if a sequence
  1497. iter(self._drawn_artists)
  1498. except TypeError:
  1499. raise err from None
  1500. # check each item if it's artist
  1501. for i in self._drawn_artists:
  1502. if not isinstance(i, mpl.artist.Artist):
  1503. raise err
  1504. self._drawn_artists = sorted(self._drawn_artists,
  1505. key=lambda x: x.get_zorder())
  1506. for a in self._drawn_artists:
  1507. a.set_animated(self._blit)
  1508. save_count = _api.deprecate_privatize_attribute("3.7")
  1509. def _validate_grabframe_kwargs(savefig_kwargs):
  1510. if mpl.rcParams['savefig.bbox'] == 'tight':
  1511. raise ValueError(
  1512. f"{mpl.rcParams['savefig.bbox']=} must not be 'tight' as it "
  1513. "may cause frame size to vary, which is inappropriate for animation."
  1514. )
  1515. for k in ('dpi', 'bbox_inches', 'format'):
  1516. if k in savefig_kwargs:
  1517. raise TypeError(
  1518. f"grab_frame got an unexpected keyword argument {k!r}"
  1519. )