From 6062833f76f1c6baee4e8f3b079f4cf7374c4e4b Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Sun, 29 Nov 2020 15:13:28 +0100 Subject: [PATCH] Cleanup Animation frame_formats. - Warn if the requested frame format is unsupported and fallback occurs. - ImageMagickFileWriter supports 'rgba' but requires explicit frame_size and depth to do so (they are the same as for ImageMagickWriter, and passing them doesn't negatively impact other formats); it does not support 'raw' (per https://imagemagick.org/script/formats.php) but we can just override it as 'rgba'. Test script: ```python import numpy as np import matplotlib.pyplot as plt import matplotlib.animation as animation fig, ax = plt.subplots() x = np.arange(0, 2*np.pi, 0.01) line, = ax.plot(x, np.sin(x)) def animate(i): line.set_ydata(np.sin(x + i / 50)) # update the data. return line, ani = animation.FuncAnimation( fig, animate, interval=50, blit=True, save_count=20) plt.rcParams["animation.frame_format"] = "rgba" # or "raw" writer = animation.FFMpegFileWriter() # or animation.ImageMagickFileWriter() ani.save("/tmp/movie.mp4", writer=writer) ``` (Also move import of PIL up, as it's a standard dependency now.) --- doc/missing-references.json | 54 ++++++++++++++++++++++--------------- lib/matplotlib/animation.py | 22 ++++++++++----- 2 files changed, 49 insertions(+), 27 deletions(-) diff --git a/doc/missing-references.json b/doc/missing-references.json index f2f4b7576da5..1bb005c9a18a 100644 --- a/doc/missing-references.json +++ b/doc/missing-references.json @@ -504,7 +504,7 @@ "lib/matplotlib/figure.py:docstring of matplotlib.figure.Figure.legend:220", "lib/matplotlib/figure.py:docstring of matplotlib.figure.FigureBase.legend:220", "lib/matplotlib/figure.py:docstring of matplotlib.figure.SubFigure.legend:220", - "lib/matplotlib/legend.py:docstring of matplotlib.legend.Legend:178", + "lib/matplotlib/legend.py:docstring of matplotlib.legend.Legend:179", "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.figlegend:220", "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.legend:219" ] @@ -522,11 +522,11 @@ }, "py:meth": { "AbstractPathEffect._update_gc": [ - "lib/matplotlib/patheffects.py:docstring of matplotlib.patheffects.SimpleLineShadow:43", - "lib/matplotlib/patheffects.py:docstring of matplotlib.patheffects.SimplePatchShadow:42", - "lib/matplotlib/patheffects.py:docstring of matplotlib.patheffects.TickedStroke:59", - "lib/matplotlib/patheffects.py:docstring of matplotlib.patheffects.withSimplePatchShadow:51", - "lib/matplotlib/patheffects.py:docstring of matplotlib.patheffects.withTickedStroke:54" + "lib/matplotlib/patheffects.py:docstring of matplotlib.patheffects.SimpleLineShadow:44", + "lib/matplotlib/patheffects.py:docstring of matplotlib.patheffects.SimplePatchShadow:43", + "lib/matplotlib/patheffects.py:docstring of matplotlib.patheffects.TickedStroke:60", + "lib/matplotlib/patheffects.py:docstring of matplotlib.patheffects.withSimplePatchShadow:52", + "lib/matplotlib/patheffects.py:docstring of matplotlib.patheffects.withTickedStroke:55" ], "FigureCanvasQTAgg.blit": [ "doc/api/prev_api_changes/api_changes_2.2.0.rst:199" @@ -802,7 +802,7 @@ "lib/matplotlib/figure.py:docstring of matplotlib.figure.Figure.legend:129", "lib/matplotlib/figure.py:docstring of matplotlib.figure.FigureBase.legend:129", "lib/matplotlib/figure.py:docstring of matplotlib.figure.SubFigure.legend:129", - "lib/matplotlib/legend.py:docstring of matplotlib.legend.Legend:87", + "lib/matplotlib/legend.py:docstring of matplotlib.legend.Legend:88", "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.figlegend:129", "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.legend:128" ], @@ -851,7 +851,7 @@ "lib/matplotlib/figure.py:docstring of matplotlib.figure.Figure.legend:129", "lib/matplotlib/figure.py:docstring of matplotlib.figure.FigureBase.legend:129", "lib/matplotlib/figure.py:docstring of matplotlib.figure.SubFigure.legend:129", - "lib/matplotlib/legend.py:docstring of matplotlib.legend.Legend:87", + "lib/matplotlib/legend.py:docstring of matplotlib.legend.Legend:88", "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.figlegend:129", "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.legend:128" ], @@ -977,7 +977,7 @@ "doc/api/_as_gen/matplotlib.animation.AVConvFileWriter.rst:39::1" ], "matplotlib.animation.AVConvWriter.args_key": [ - "doc/api/_as_gen/matplotlib.animation.AVConvWriter.rst:36::1" + "doc/api/_as_gen/matplotlib.animation.AVConvWriter.rst:37::1" ], "matplotlib.animation.AVConvWriter.bin_path": [ "doc/api/_as_gen/matplotlib.animation.AVConvWriter.rst:28::1" @@ -986,13 +986,13 @@ "doc/api/_as_gen/matplotlib.animation.AVConvWriter.rst:28::1" ], "matplotlib.animation.AVConvWriter.exec_key": [ - "doc/api/_as_gen/matplotlib.animation.AVConvWriter.rst:36::1" + "doc/api/_as_gen/matplotlib.animation.AVConvWriter.rst:37::1" ], "matplotlib.animation.AVConvWriter.finish": [ "doc/api/_as_gen/matplotlib.animation.AVConvWriter.rst:28::1" ], "matplotlib.animation.AVConvWriter.frame_size": [ - "doc/api/_as_gen/matplotlib.animation.AVConvWriter.rst:36::1" + "doc/api/_as_gen/matplotlib.animation.AVConvWriter.rst:37::1" ], "matplotlib.animation.AVConvWriter.grab_frame": [ "doc/api/_as_gen/matplotlib.animation.AVConvWriter.rst:28::1" @@ -1001,7 +1001,7 @@ "doc/api/_as_gen/matplotlib.animation.AVConvWriter.rst:28::1" ], "matplotlib.animation.AVConvWriter.output_args": [ - "doc/api/_as_gen/matplotlib.animation.AVConvWriter.rst:36::1" + "doc/api/_as_gen/matplotlib.animation.AVConvWriter.rst:37::1" ], "matplotlib.animation.AVConvWriter.saving": [ "doc/api/_as_gen/matplotlib.animation.AVConvWriter.rst:28::1" @@ -1009,6 +1009,9 @@ "matplotlib.animation.AVConvWriter.setup": [ "doc/api/_as_gen/matplotlib.animation.AVConvWriter.rst:28::1" ], + "matplotlib.animation.AVConvWriter.supported_formats": [ + "doc/api/_as_gen/matplotlib.animation.AVConvWriter.rst:37::1" + ], "matplotlib.animation.ArtistAnimation.new_frame_seq": [ "doc/api/_as_gen/matplotlib.animation.ArtistAnimation.rst:23::1" ], @@ -1070,7 +1073,7 @@ "doc/api/_as_gen/matplotlib.animation.FFMpegFileWriter.rst:28::1" ], "matplotlib.animation.FFMpegWriter.args_key": [ - "doc/api/_as_gen/matplotlib.animation.FFMpegWriter.rst:36::1" + "doc/api/_as_gen/matplotlib.animation.FFMpegWriter.rst:37::1" ], "matplotlib.animation.FFMpegWriter.bin_path": [ "doc/api/_as_gen/matplotlib.animation.FFMpegWriter.rst:28::1" @@ -1079,13 +1082,13 @@ "doc/api/_as_gen/matplotlib.animation.FFMpegWriter.rst:28::1" ], "matplotlib.animation.FFMpegWriter.exec_key": [ - "doc/api/_as_gen/matplotlib.animation.FFMpegWriter.rst:36::1" + "doc/api/_as_gen/matplotlib.animation.FFMpegWriter.rst:37::1" ], "matplotlib.animation.FFMpegWriter.finish": [ "doc/api/_as_gen/matplotlib.animation.FFMpegWriter.rst:28::1" ], "matplotlib.animation.FFMpegWriter.frame_size": [ - "doc/api/_as_gen/matplotlib.animation.FFMpegWriter.rst:36::1" + "doc/api/_as_gen/matplotlib.animation.FFMpegWriter.rst:37::1" ], "matplotlib.animation.FFMpegWriter.grab_frame": [ "doc/api/_as_gen/matplotlib.animation.FFMpegWriter.rst:28::1" @@ -1094,7 +1097,7 @@ "doc/api/_as_gen/matplotlib.animation.FFMpegWriter.rst:28::1" ], "matplotlib.animation.FFMpegWriter.output_args": [ - "doc/api/_as_gen/matplotlib.animation.FFMpegWriter.rst:36::1" + "doc/api/_as_gen/matplotlib.animation.FFMpegWriter.rst:37::1" ], "matplotlib.animation.FFMpegWriter.saving": [ "doc/api/_as_gen/matplotlib.animation.FFMpegWriter.rst:28::1" @@ -1102,6 +1105,9 @@ "matplotlib.animation.FFMpegWriter.setup": [ "doc/api/_as_gen/matplotlib.animation.FFMpegWriter.rst:28::1" ], + "matplotlib.animation.FFMpegWriter.supported_formats": [ + "doc/api/_as_gen/matplotlib.animation.FFMpegWriter.rst:37::1" + ], "matplotlib.animation.FileMovieWriter.args_key": [ "lib/matplotlib/animation.py:docstring of matplotlib.animation.FileMovieWriter.clear_temp:1::1" ], @@ -1123,6 +1129,9 @@ "matplotlib.animation.FileMovieWriter.saving": [ "doc/api/_as_gen/matplotlib.animation.FileMovieWriter.rst:28::1" ], + "matplotlib.animation.FileMovieWriter.supported_formats": [ + "lib/matplotlib/animation.py:docstring of matplotlib.animation.FileMovieWriter.clear_temp:1::1" + ], "matplotlib.animation.FuncAnimation.pause": [ "lib/matplotlib/animation.py:docstring of matplotlib.animation.FuncAnimation.new_frame_seq:1::1" ], @@ -1202,7 +1211,7 @@ "doc/api/_as_gen/matplotlib.animation.ImageMagickFileWriter.rst:28::1" ], "matplotlib.animation.ImageMagickWriter.args_key": [ - "doc/api/_as_gen/matplotlib.animation.ImageMagickWriter.rst:37::1" + "doc/api/_as_gen/matplotlib.animation.ImageMagickWriter.rst:38::1" ], "matplotlib.animation.ImageMagickWriter.bin_path": [ "doc/api/_as_gen/matplotlib.animation.ImageMagickWriter.rst:28::1" @@ -1211,16 +1220,16 @@ "doc/api/_as_gen/matplotlib.animation.ImageMagickWriter.rst:28::1" ], "matplotlib.animation.ImageMagickWriter.delay": [ - "doc/api/_as_gen/matplotlib.animation.ImageMagickWriter.rst:37::1" + "doc/api/_as_gen/matplotlib.animation.ImageMagickWriter.rst:38::1" ], "matplotlib.animation.ImageMagickWriter.exec_key": [ - "doc/api/_as_gen/matplotlib.animation.ImageMagickWriter.rst:37::1" + "doc/api/_as_gen/matplotlib.animation.ImageMagickWriter.rst:38::1" ], "matplotlib.animation.ImageMagickWriter.finish": [ "doc/api/_as_gen/matplotlib.animation.ImageMagickWriter.rst:28::1" ], "matplotlib.animation.ImageMagickWriter.frame_size": [ - "doc/api/_as_gen/matplotlib.animation.ImageMagickWriter.rst:37::1" + "doc/api/_as_gen/matplotlib.animation.ImageMagickWriter.rst:38::1" ], "matplotlib.animation.ImageMagickWriter.grab_frame": [ "doc/api/_as_gen/matplotlib.animation.ImageMagickWriter.rst:28::1" @@ -1229,7 +1238,7 @@ "doc/api/_as_gen/matplotlib.animation.ImageMagickWriter.rst:28::1" ], "matplotlib.animation.ImageMagickWriter.output_args": [ - "doc/api/_as_gen/matplotlib.animation.ImageMagickWriter.rst:37::1" + "doc/api/_as_gen/matplotlib.animation.ImageMagickWriter.rst:38::1" ], "matplotlib.animation.ImageMagickWriter.saving": [ "doc/api/_as_gen/matplotlib.animation.ImageMagickWriter.rst:28::1" @@ -1237,6 +1246,9 @@ "matplotlib.animation.ImageMagickWriter.setup": [ "doc/api/_as_gen/matplotlib.animation.ImageMagickWriter.rst:28::1" ], + "matplotlib.animation.ImageMagickWriter.supported_formats": [ + "doc/api/_as_gen/matplotlib.animation.ImageMagickWriter.rst:38::1" + ], "matplotlib.animation.MovieWriter.frame_size": [ "lib/matplotlib/animation.py:docstring of matplotlib.animation.MovieWriter.args_key:1::1" ], diff --git a/lib/matplotlib/animation.py b/lib/matplotlib/animation.py index 4d76953dd512..48a3ae41ed09 100644 --- a/lib/matplotlib/animation.py +++ b/lib/matplotlib/animation.py @@ -31,6 +31,7 @@ import warnings import numpy as np +from PIL import Image import matplotlib as mpl from matplotlib._animation_data import ( @@ -263,6 +264,10 @@ class MovieWriter(AbstractMovieWriter): exec_key = cbook._deprecate_privatize_attribute("3.3") args_key = cbook._deprecate_privatize_attribute("3.3") + # Pipe-based writers only support RGBA, but file-based ones support more + # formats. + supported_formats = ["rgba"] + def __init__(self, fps=5, codec=None, bitrate=None, extra_args=None, metadata=None): """ @@ -296,8 +301,7 @@ def __init__(self, fps=5, codec=None, bitrate=None, extra_args=None, super().__init__(fps=fps, metadata=metadata, codec=codec, bitrate=bitrate) - - self.frame_format = 'rgba' + self.frame_format = self.supported_formats[0] self.extra_args = extra_args def _adjust_frame_size(self): @@ -471,6 +475,10 @@ def frame_format(self, frame_format): if frame_format in self.supported_formats: self._frame_format = frame_format else: + cbook._warn_external( + f"Ignoring file format {frame_format!r} which is not " + f"supported by {type(self).__name__}; using " + f"{self.supported_formats[0]} instead.") self._frame_format = self.supported_formats[0] def _base_temp_name(self): @@ -521,7 +529,6 @@ def setup(self, fig, outfile, dpi=None): self._frames = [] def grab_frame(self, **savefig_kwargs): - from PIL import Image buf = BytesIO() self.fig.savefig( buf, **{**savefig_kwargs, "format": "rgba", "dpi": self.dpi}) @@ -736,15 +743,18 @@ class ImageMagickFileWriter(ImageMagickBase, FileMovieWriter): Frames are written to temporary files on disk and then stitched together at the end. - """ supported_formats = ['png', 'jpeg', 'ppm', 'tiff', 'sgi', 'bmp', 'pbm', 'raw', 'rgba'] def _args(self): - return ([self.bin_path(), '-delay', str(self.delay), '-loop', '0', - '%s*.%s' % (self.temp_prefix, self.frame_format)] + # Force format: ImageMagick does not recognize 'raw'. + fmt = 'rgba:' if self.frame_format == 'raw' else '' + return ([self.bin_path(), + '-size', '%ix%i' % self.frame_size, '-depth', '8', + '-delay', str(self.delay), '-loop', '0', + '%s%s*.%s' % (fmt, self.temp_prefix, self.frame_format)] + self.output_args)