Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit cab1abd

Browse files
efiringtacaswell
authored andcommitted
Merge pull request #8253 from ngoldbaum/simplify-figsize-adjust
Handle floating point round-off error when converting to pixels for h264 animations Conflicts: lib/matplotlib/tests/test_animation.py - conflicts in tests
1 parent 02e89e6 commit cab1abd

File tree

2 files changed

+71
-12
lines changed

2 files changed

+71
-12
lines changed

lib/matplotlib/animation.py

Lines changed: 61 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import six
2424
from six.moves import xrange, zip
2525

26+
import numpy as np
2627
import os
2728
import platform
2829
import sys
@@ -61,9 +62,38 @@
6162

6263

6364
def adjusted_figsize(w, h, dpi, n):
65+
'''Compute figure size so that pixels are a multiple of n
66+
67+
Parameters
68+
----------
69+
w, h : float
70+
Size in inches
71+
72+
dpi : float
73+
The dpi
74+
75+
n : int
76+
The target multiple
77+
78+
Returns
79+
-------
80+
wnew, hnew : float
81+
The new figure size in inches.
82+
'''
83+
84+
# this maybe simplified if / when we adopt consistent rounding for
85+
# pixel size across the whole library
86+
def correct_roundoff(x, dpi, n):
87+
if int(x*dpi) % n != 0:
88+
if int(np.nextafter(x, np.inf)*dpi) % n == 0:
89+
x = np.nextafter(x, np.inf)
90+
elif int(np.nextafter(x, -np.inf)*dpi) % n == 0:
91+
x = np.nextafter(x, -np.inf)
92+
return x
93+
6494
wnew = int(w * dpi / n) * n / dpi
6595
hnew = int(h * dpi / n) * n / dpi
66-
return wnew, hnew
96+
return (correct_roundoff(wnew, dpi, n), correct_roundoff(hnew, dpi, n))
6797

6898

6999
# A registry for available MovieWriter classes
@@ -214,8 +244,11 @@ def _adjust_frame_size(self):
214244
verbose.report('figure size (inches) has been adjusted '
215245
'from %s x %s to %s x %s' % (wo, ho, w, h),
216246
level='helpful')
247+
else:
248+
w, h = self.fig.get_size_inches()
217249
verbose.report('frame size in pixels is %s x %s' % self.frame_size,
218250
level='debug')
251+
return w, h
219252

220253
def setup(self, fig, outfile, dpi):
221254
'''
@@ -235,7 +268,7 @@ def setup(self, fig, outfile, dpi):
235268
self.outfile = outfile
236269
self.fig = fig
237270
self.dpi = dpi
238-
self._adjust_frame_size()
271+
self._w, self._h = self._adjust_frame_size()
239272

240273
# Run here so that grab_frame() can write the data to a pipe. This
241274
# eliminates the need for temp files.
@@ -285,6 +318,10 @@ def grab_frame(self, **savefig_kwargs):
285318
verbose.report('MovieWriter.grab_frame: Grabbing frame.',
286319
level='debug')
287320
try:
321+
# re-adjust the figure size in case it has been changed by the
322+
# user. We must ensure that every frame is the same size or
323+
# the movie will not save correctly.
324+
self.fig.set_size_inches(self._w, self._h)
288325
# Tell the figure to save its data to the sink, using the
289326
# frame format and dpi.
290327
self.fig.savefig(self._frame_sink(), format=self.frame_format,
@@ -334,16 +371,21 @@ def isAvailable(cls):
334371
if not bin_path:
335372
return False
336373
try:
337-
p = subprocess.Popen(bin_path,
338-
shell=False,
339-
stdout=subprocess.PIPE,
340-
stderr=subprocess.PIPE,
341-
creationflags=subprocess_creation_flags)
342-
p.communicate()
343-
return True
374+
p = subprocess.Popen(
375+
bin_path,
376+
shell=False,
377+
stdout=subprocess.PIPE,
378+
stderr=subprocess.PIPE,
379+
creationflags=subprocess_creation_flags)
380+
return cls._handle_subprocess(p)
344381
except OSError:
345382
return False
346383

384+
@classmethod
385+
def _handle_subprocess(cls, process):
386+
process.communicate()
387+
return True
388+
347389

348390
class FileMovieWriter(MovieWriter):
349391
'''`MovieWriter` for writing to individual files and stitching at the end.
@@ -514,10 +556,18 @@ def output_args(self):
514556

515557
return args + ['-y', self.outfile]
516558

559+
@classmethod
560+
def _handle_subprocess(cls, process):
561+
_, err = process.communicate()
562+
# Ubuntu 12.04 ships a broken ffmpeg binary which we shouldn't use
563+
if 'Libav' in err.decode():
564+
return False
565+
return True
566+
517567

518568
# Combine FFMpeg options with pipe-based writing
519569
@writers.register('ffmpeg')
520-
class FFMpegWriter(MovieWriter, FFMpegBase):
570+
class FFMpegWriter(FFMpegBase, MovieWriter):
521571
'''Pipe-based ffmpeg writer.
522572
523573
Frames are streamed directly to ffmpeg via a pipe and written in a single
@@ -538,7 +588,7 @@ def _args(self):
538588

539589
# Combine FFMpeg options with temp file-based writing
540590
@writers.register('ffmpeg_file')
541-
class FFMpegFileWriter(FileMovieWriter, FFMpegBase):
591+
class FFMpegFileWriter(FFMpegBase, FileMovieWriter):
542592
'''File-based ffmpeg writer.
543593
544594
Frames are written to temporary files on disk and then stitched

lib/matplotlib/tests/test_animation.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,14 @@ def check_save_animation(writer, extension='mp4'):
4949
ax.set_xlim(0, 10)
5050
ax.set_ylim(-1, 1)
5151

52+
dpi = None
53+
codec = None
54+
if writer == 'ffmpeg':
55+
# Issue #8253
56+
fig.set_size_inches((10.85, 9.21))
57+
dpi = 100.
58+
codec = 'h264'
59+
5260
def init():
5361
line.set_data([], [])
5462
return line,
@@ -64,7 +72,8 @@ def animate(i):
6472
F.close()
6573
anim = animation.FuncAnimation(fig, animate, init_func=init, frames=5)
6674
try:
67-
anim.save(F.name, fps=30, writer=writer, bitrate=500)
75+
anim.save(F.name, fps=30, writer=writer, bitrate=500,
76+
dpi=dpi, codec=codec)
6877
except UnicodeDecodeError:
6978
raise KnownFailureTest("There can be errors in the numpy " +
7079
"import stack, " +

0 commit comments

Comments
 (0)