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

Skip to content

Commit 44fcb87

Browse files
authored
Merge pull request #8253 from ngoldbaum/simplify-figsize-adjust
Handle floating point round-off error when converting to pixels for h264 animations
2 parents 50bb88a + 5d4f5de commit 44fcb87

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
@@ -62,9 +63,38 @@
6263

6364

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

6999

70100
# A registry for available MovieWriter classes
@@ -278,8 +308,11 @@ def _adjust_frame_size(self):
278308
verbose.report('figure size (inches) has been adjusted '
279309
'from %s x %s to %s x %s' % (wo, ho, w, h),
280310
level='helpful')
311+
else:
312+
w, h = self.fig.get_size_inches()
281313
verbose.report('frame size in pixels is %s x %s' % self.frame_size,
282314
level='debug')
315+
return w, h
283316

284317
def setup(self, fig, outfile, dpi=None):
285318
'''
@@ -301,7 +334,7 @@ def setup(self, fig, outfile, dpi=None):
301334
if dpi is None:
302335
dpi = self.fig.dpi
303336
self.dpi = dpi
304-
self._adjust_frame_size()
337+
self._w, self._h = self._adjust_frame_size()
305338

306339
# Run here so that grab_frame() can write the data to a pipe. This
307340
# eliminates the need for temp files.
@@ -337,6 +370,10 @@ def grab_frame(self, **savefig_kwargs):
337370
verbose.report('MovieWriter.grab_frame: Grabbing frame.',
338371
level='debug')
339372
try:
373+
# re-adjust the figure size in case it has been changed by the
374+
# user. We must ensure that every frame is the same size or
375+
# the movie will not save correctly.
376+
self.fig.set_size_inches(self._w, self._h)
340377
# Tell the figure to save its data to the sink, using the
341378
# frame format and dpi.
342379
self.fig.savefig(self._frame_sink(), format=self.frame_format,
@@ -386,16 +423,21 @@ def isAvailable(cls):
386423
if not bin_path:
387424
return False
388425
try:
389-
p = subprocess.Popen(bin_path,
390-
shell=False,
391-
stdout=subprocess.PIPE,
392-
stderr=subprocess.PIPE,
393-
creationflags=subprocess_creation_flags)
394-
p.communicate()
395-
return True
426+
p = subprocess.Popen(
427+
bin_path,
428+
shell=False,
429+
stdout=subprocess.PIPE,
430+
stderr=subprocess.PIPE,
431+
creationflags=subprocess_creation_flags)
432+
return cls._handle_subprocess(p)
396433
except OSError:
397434
return False
398435

436+
@classmethod
437+
def _handle_subprocess(cls, process):
438+
process.communicate()
439+
return True
440+
399441

400442
class FileMovieWriter(MovieWriter):
401443
'''`MovieWriter` for writing to individual files and stitching at the end.
@@ -570,10 +612,18 @@ def output_args(self):
570612

571613
return args + ['-y', self.outfile]
572614

615+
@classmethod
616+
def _handle_subprocess(cls, process):
617+
_, err = process.communicate()
618+
# Ubuntu 12.04 ships a broken ffmpeg binary which we shouldn't use
619+
if 'Libav' in err.decode():
620+
return False
621+
return True
622+
573623

574624
# Combine FFMpeg options with pipe-based writing
575625
@writers.register('ffmpeg')
576-
class FFMpegWriter(MovieWriter, FFMpegBase):
626+
class FFMpegWriter(FFMpegBase, MovieWriter):
577627
'''Pipe-based ffmpeg writer.
578628
579629
Frames are streamed directly to ffmpeg via a pipe and written in a single
@@ -594,7 +644,7 @@ def _args(self):
594644

595645
# Combine FFMpeg options with temp file-based writing
596646
@writers.register('ffmpeg_file')
597-
class FFMpegFileWriter(FileMovieWriter, FFMpegBase):
647+
class FFMpegFileWriter(FFMpegBase, FileMovieWriter):
598648
'''File-based ffmpeg writer.
599649
600650
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
@@ -145,6 +145,14 @@ def test_save_animation_smoketest(tmpdir, writer, extension):
145145
ax.set_xlim(0, 10)
146146
ax.set_ylim(-1, 1)
147147

148+
dpi = None
149+
codec = None
150+
if writer == 'ffmpeg':
151+
# Issue #8253
152+
fig.set_size_inches((10.85, 9.21))
153+
dpi = 100.
154+
codec = 'h264'
155+
148156
def init():
149157
line.set_data([], [])
150158
return line,
@@ -160,7 +168,8 @@ def animate(i):
160168
with tmpdir.as_cwd():
161169
anim = animation.FuncAnimation(fig, animate, init_func=init, frames=5)
162170
try:
163-
anim.save('movie.' + extension, fps=30, writer=writer, bitrate=500)
171+
anim.save('movie.' + extension, fps=30, writer=writer, bitrate=500,
172+
dpi=dpi, codec=codec)
164173
except UnicodeDecodeError:
165174
pytest.xfail("There can be errors in the numpy import stack, "
166175
"see issues #1891 and #2679")

0 commit comments

Comments
 (0)