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

Skip to content

Commit 846b95f

Browse files
committed
Merge pull request #6430 from efiring/merge_from_1.5
Merge from 1.5
2 parents f5f416c + 909b77b commit 846b95f

6 files changed

Lines changed: 129 additions & 60 deletions

File tree

lib/matplotlib/animation.py

Lines changed: 56 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,11 @@
3535
from base64 import encodestring as encodebytes
3636
import contextlib
3737
import tempfile
38+
import warnings
3839
from matplotlib.cbook import iterable, is_string_like
3940
from matplotlib.compat import subprocess
4041
from matplotlib import verbose
41-
from matplotlib import rcParams, rcParamsDefault
42+
from matplotlib import rcParams, rcParamsDefault, rc_context
4243

4344
# Process creation flag for subprocess to prevent it raising a terminal
4445
# window. See for example:
@@ -109,6 +110,11 @@ class MovieWriter(object):
109110
frame_format: string
110111
The format used in writing frame data, defaults to 'rgba'
111112
'''
113+
114+
# Specifies whether the size of all frames need to be identical
115+
# i.e. whether we can use savefig.bbox = 'tight'
116+
frame_size_can_vary = False
117+
112118
def __init__(self, fps=5, codec=None, bitrate=None, extra_args=None,
113119
metadata=None):
114120
'''
@@ -127,8 +133,8 @@ def __init__(self, fps=5, codec=None, bitrate=None, extra_args=None,
127133
automatically by the underlying utility.
128134
extra_args: list of strings or None
129135
A list of extra string arguments to be passed to the underlying
130-
movie utiltiy. The default is None, which passes the additional
131-
argurments in the 'animation.extra_args' rcParam.
136+
movie utility. The default is None, which passes the additional
137+
arguments in the 'animation.extra_args' rcParam.
132138
metadata: dict of string:string or None
133139
A dictionary of keys and values for metadata to include in the
134140
output file. Some keys that may be of use include:
@@ -283,6 +289,11 @@ def isAvailable(cls):
283289

284290
class FileMovieWriter(MovieWriter):
285291
'`MovieWriter` subclass that handles writing to a file.'
292+
293+
# In general, if frames are writen to files on disk, it's not important
294+
# that they all be identically sized
295+
frame_size_can_vary = True
296+
286297
def __init__(self, *args, **kwargs):
287298
MovieWriter.__init__(self, *args, **kwargs)
288299
self.frame_format = rcParams['animation.frame_format']
@@ -409,15 +420,15 @@ class FFMpegBase(object):
409420

410421
@property
411422
def output_args(self):
412-
# The %dk adds 'k' as a suffix so that ffmpeg treats our bitrate as in
413-
# kbps
414423
args = ['-vcodec', self.codec]
415424
# For h264, the default format is yuv444p, which is not compatible
416425
# with quicktime (and others). Specifying yuv420p fixes playback on
417426
# iOS,as well as HTML5 video in firefox and safari (on both Win and
418427
# OSX). Also fixes internet explorer. This is as of 2015/10/29.
419428
if self.codec == 'h264' and '-pix_fmt' not in self.extra_args:
420429
args.extend(['-pix_fmt', 'yuv420p'])
430+
# The %dk adds 'k' as a suffix so that ffmpeg treats our bitrate as in
431+
# kbps
421432
if self.bitrate > 0:
422433
args.extend(['-b', '%dk' % self.bitrate])
423434
if self.extra_args:
@@ -691,8 +702,8 @@ def save(self, filename, writer=None, fps=None, dpi=None, codec=None,
691702
`animation.bitrate`.
692703
693704
*extra_args* is a list of extra string arguments to be passed to the
694-
underlying movie utiltiy. The default is None, which passes the
695-
additional argurments in the 'animation.extra_args' rcParam.
705+
underlying movie utility. The default is None, which passes the
706+
additional arguments in the 'animation.extra_args' rcParam.
696707
697708
*metadata* is a dictionary of keys and values for metadata to include
698709
in the output file. Some keys that may be of use include:
@@ -712,29 +723,6 @@ def save(self, filename, writer=None, fps=None, dpi=None, codec=None,
712723
if savefig_kwargs is None:
713724
savefig_kwargs = {}
714725

715-
# FIXME: Using 'bbox_inches' doesn't currently work with
716-
# writers that pipe the data to the command because this
717-
# requires a fixed frame size (see Ryan May's reply in this
718-
# thread: [1]). Thus we drop the 'bbox_inches' argument if it
719-
# exists in savefig_kwargs.
720-
#
721-
# [1] (http://matplotlib.1069221.n5.nabble.com/
722-
# Animation-class-let-save-accept-kwargs-which-
723-
# are-passed-on-to-savefig-td39627.html)
724-
#
725-
if 'bbox_inches' in savefig_kwargs:
726-
if not (writer in ['ffmpeg_file', 'mencoder_file'] or
727-
isinstance(writer,
728-
(FFMpegFileWriter, MencoderFileWriter))):
729-
print("Warning: discarding the 'bbox_inches' argument in "
730-
"'savefig_kwargs' as it is only currently supported "
731-
"with the writers 'ffmpeg_file' and 'mencoder_file' "
732-
"(writer used: "
733-
"'{0}').".format(
734-
writer if isinstance(writer, six.string_types)
735-
else writer.__class__.__name__))
736-
savefig_kwargs.pop('bbox_inches')
737-
738726
# Need to disconnect the first draw callback, since we'll be doing
739727
# draws. Otherwise, we'll end up starting the animation.
740728
if self._first_draw_id is not None:
@@ -778,7 +766,6 @@ def save(self, filename, writer=None, fps=None, dpi=None, codec=None,
778766
extra_args=extra_args,
779767
metadata=metadata)
780768
else:
781-
import warnings
782769
warnings.warn("MovieWriter %s unavailable" % writer)
783770

784771
try:
@@ -792,22 +779,48 @@ def save(self, filename, writer=None, fps=None, dpi=None, codec=None,
792779

793780
verbose.report('Animation.save using %s' % type(writer),
794781
level='helpful')
782+
783+
# FIXME: Using 'bbox_inches' doesn't currently work with
784+
# writers that pipe the data to the command because this
785+
# requires a fixed frame size (see Ryan May's reply in this
786+
# thread: [1]). Thus we drop the 'bbox_inches' argument if it
787+
# exists in savefig_kwargs.
788+
#
789+
# [1] (http://matplotlib.1069221.n5.nabble.com/
790+
# Animation-class-let-save-accept-kwargs-which-
791+
# are-passed-on-to-savefig-td39627.html)
792+
#
793+
if 'bbox_inches' in savefig_kwargs and not writer.frame_size_can_vary:
794+
warnings.warn("Warning: discarding the 'bbox_inches' argument in "
795+
"'savefig_kwargs' as it not supported by "
796+
"{0}).".format(writer.__class__.__name__))
797+
savefig_kwargs.pop('bbox_inches')
798+
795799
# Create a new sequence of frames for saved data. This is different
796800
# from new_frame_seq() to give the ability to save 'live' generated
797801
# frame information to be saved later.
798802
# TODO: Right now, after closing the figure, saving a movie won't work
799803
# since GUI widgets are gone. Either need to remove extra code to
800-
# allow for this non-existant use case or find a way to make it work.
801-
with writer.saving(self._fig, filename, dpi):
802-
for anim in all_anim:
803-
# Clear the initial frame
804-
anim._init_draw()
805-
for data in zip(*[a.new_saved_frame_seq()
806-
for a in all_anim]):
807-
for anim, d in zip(all_anim, data):
808-
# TODO: Need to see if turning off blit is really necessary
809-
anim._draw_next_frame(d, blit=False)
810-
writer.grab_frame(**savefig_kwargs)
804+
# allow for this non-existent use case or find a way to make it work.
805+
with rc_context():
806+
# See above about bbox_inches savefig kwarg
807+
if (not writer.frame_size_can_vary and
808+
rcParams['savefig.bbox'] == 'tight'):
809+
verbose.report("Disabling savefig.bbox = 'tight', as it is "
810+
"not supported by "
811+
"{0}.".format(writer.__class__.__name__),
812+
level='helpful')
813+
rcParams['savefig.bbox'] = None
814+
with writer.saving(self._fig, filename, dpi):
815+
for anim in all_anim:
816+
# Clear the initial frame
817+
anim._init_draw()
818+
for data in zip(*[a.new_saved_frame_seq()
819+
for a in all_anim]):
820+
for anim, d in zip(all_anim, data):
821+
# TODO: See if turning off blit is really necessary
822+
anim._draw_next_frame(d, blit=False)
823+
writer.grab_frame(**savefig_kwargs)
811824

812825
# Reconnect signal for first draw if necessary
813826
if reconnect_first_draw:
@@ -935,7 +948,7 @@ def to_html5_video(self):
935948
directly into the HTML5 video tag. This respects the rc parameters
936949
for the writer as well as the bitrate. This also makes use of the
937950
``interval`` to control the speed, and uses the ``repeat``
938-
paramter to decide whether to loop.
951+
parameter to decide whether to loop.
939952
'''
940953
VIDEO_TAG = r'''<video {size} {options}>
941954
<source type="video/mp4" src="data:video/mp4;base64,{video}">

lib/matplotlib/cbook.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1503,8 +1503,8 @@ def issubclass_safe(x, klass):
15031503
return False
15041504

15051505

1506-
def safe_masked_invalid(x):
1507-
x = np.asanyarray(x)
1506+
def safe_masked_invalid(x, copy=False):
1507+
x = np.array(x, subok=True, copy=copy)
15081508
try:
15091509
xm = np.ma.masked_invalid(x, copy=False)
15101510
xm.shrink_mask()

lib/matplotlib/contour.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1132,7 +1132,7 @@ def changed(self):
11321132
# add label colors
11331133
cm.ScalarMappable.changed(self)
11341134

1135-
def _autolev(self, z, N):
1135+
def _autolev(self, N):
11361136
"""
11371137
Select contour levels to span the data.
11381138
@@ -1168,12 +1168,12 @@ def _contour_level_args(self, z, args):
11681168
self._auto = False
11691169
if self.levels is None:
11701170
if len(args) == 0:
1171-
lev = self._autolev(z, 7)
1171+
lev = self._autolev(7)
11721172
else:
11731173
level_arg = args[0]
11741174
try:
11751175
if type(level_arg) == int:
1176-
lev = self._autolev(z, level_arg)
1176+
lev = self._autolev(level_arg)
11771177
else:
11781178
lev = np.asarray(level_arg).astype(np.float64)
11791179
except:
@@ -1533,12 +1533,12 @@ def _contour_args(self, args, kwargs):
15331533
raise TypeError("Too many arguments to %s; see help(%s)" %
15341534
(fn, fn))
15351535
z = ma.masked_invalid(z, copy=False)
1536-
self.zmax = ma.maximum(z)
1537-
self.zmin = ma.minimum(z)
1536+
self.zmax = float(z.max())
1537+
self.zmin = float(z.min())
15381538
if self.logscale and self.zmin <= 0:
15391539
z = ma.masked_where(z <= 0, z)
15401540
warnings.warn('Log scale: values of z <= 0 have been masked')
1541-
self.zmin = z.min()
1541+
self.zmin = float(z.min())
15421542
self._contour_level_args(z, args)
15431543
return (x, y, z)
15441544

lib/matplotlib/image.py

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -513,7 +513,7 @@ def set_data(self, A):
513513
if hasattr(A, 'getpixel'):
514514
self._A = pil_to_array(A)
515515
else:
516-
self._A = cbook.safe_masked_invalid(A)
516+
self._A = cbook.safe_masked_invalid(A, copy=True)
517517

518518
if (self._A.dtype != np.uint8 and
519519
not np.can_cast(self._A.dtype, np.float)):
@@ -822,9 +822,9 @@ def set_data(self, x, y, A):
822822
colormapped, or a (M,N,3) RGB array, or a (M,N,4) RGBA
823823
array.
824824
"""
825-
x = np.asarray(x, np.float32)
826-
y = np.asarray(y, np.float32)
827-
A = cbook.safe_masked_invalid(A)
825+
x = np.array(x, np.float32)
826+
y = np.array(y, np.float32)
827+
A = cbook.safe_masked_invalid(A, copy=True)
828828
if len(x.shape) != 1 or len(y.shape) != 1\
829829
or A.shape[0:2] != (y.shape[0], x.shape[0]):
830830
raise TypeError("Axes don't match array shape")
@@ -899,7 +899,8 @@ def __init__(self, ax,
899899
"""
900900
super(PcolorImage, self).__init__(ax, norm=norm, cmap=cmap)
901901
self.update(kwargs)
902-
self.set_data(x, y, A)
902+
if A is not None:
903+
self.set_data(x, y, A)
903904

904905
def make_image(self, renderer, magnification=1.0, unsampled=False):
905906
if self._A is None:
@@ -934,15 +935,15 @@ def _check_unsampled_image(self, renderer):
934935
return False
935936

936937
def set_data(self, x, y, A):
937-
A = cbook.safe_masked_invalid(A)
938+
A = cbook.safe_masked_invalid(A, copy=True)
938939
if x is None:
939940
x = np.arange(0, A.shape[1]+1, dtype=np.float64)
940941
else:
941-
x = np.asarray(x, np.float64).ravel()
942+
x = np.array(x, np.float64).ravel()
942943
if y is None:
943944
y = np.arange(0, A.shape[0]+1, dtype=np.float64)
944945
else:
945-
y = np.asarray(y, np.float64).ravel()
946+
y = np.array(y, np.float64).ravel()
946947

947948
if A.shape[:2] != (y.size-1, x.size-1):
948949
raise ValueError(
@@ -1016,6 +1017,12 @@ def make_image(self, renderer, magnification=1.0, unsampled=False):
10161017
self._A, bbox, bbox, clip, magnification=magnification,
10171018
unsampled=unsampled, round_to_pixel_border=False)
10181019

1020+
def set_data(self, A):
1021+
"""Set the image array."""
1022+
cm.ScalarMappable.set_array(self,
1023+
cbook.safe_masked_invalid(A, copy=True))
1024+
self.stale = True
1025+
10191026

10201027
class BboxImage(_ImageBase):
10211028
"""The Image class whose size is determined by the given bbox."""

lib/matplotlib/tests/test_image.py

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212

1313
from matplotlib.testing.decorators import (image_comparison,
1414
knownfailureif, cleanup)
15-
from matplotlib.image import BboxImage, imread, NonUniformImage
15+
from matplotlib.image import (BboxImage, imread, NonUniformImage,
16+
AxesImage, FigureImage, PcolorImage)
1617
from matplotlib.transforms import Bbox, Affine2D, TransformedBbox
1718
from matplotlib import rcParams
1819
from matplotlib import patches
@@ -486,6 +487,50 @@ def test_nonuniformimage_setnorm():
486487
im.set_norm(plt.Normalize())
487488

488489

490+
@cleanup
491+
def test_nonuniformimage_setdata():
492+
ax = plt.gca()
493+
im = NonUniformImage(ax)
494+
x = np.arange(3, dtype=np.float64)
495+
y = np.arange(4, dtype=np.float64)
496+
z = np.arange(12, dtype=np.float64).reshape((4, 3))
497+
im.set_data(x, y, z)
498+
x[0] = y[0] = z[0, 0] = 9.9
499+
assert im._A[0, 0] == im._Ax[0] == im._Ay[0] == 0, 'value changed'
500+
501+
502+
@cleanup
503+
def test_axesimage_setdata():
504+
ax = plt.gca()
505+
im = AxesImage(ax)
506+
z = np.arange(12, dtype=np.float64).reshape((4, 3))
507+
im.set_data(z)
508+
z[0, 0] = 9.9
509+
assert im._A[0, 0] == 0, 'value changed'
510+
511+
512+
@cleanup
513+
def test_figureimage_setdata():
514+
fig = plt.gcf()
515+
im = FigureImage(fig)
516+
z = np.arange(12, dtype=np.float64).reshape((4, 3))
517+
im.set_data(z)
518+
z[0, 0] = 9.9
519+
assert im._A[0, 0] == 0, 'value changed'
520+
521+
522+
@cleanup
523+
def test_pcolorimage_setdata():
524+
ax = plt.gca()
525+
im = PcolorImage(ax)
526+
x = np.arange(3, dtype=np.float64)
527+
y = np.arange(4, dtype=np.float64)
528+
z = np.arange(6, dtype=np.float64).reshape((3, 2))
529+
im.set_data(x, y, z)
530+
x[0] = y[0] = z[0, 0] = 9.9
531+
assert im._A[0, 0] == im._Ax[0] == im._Ay[0] == 0, 'value changed'
532+
533+
489534
@cleanup
490535
def test_minimized_rasterized():
491536
# This ensures that the rasterized content in the colorbars is

lib/matplotlib/ticker.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1665,6 +1665,7 @@ def __init__(self, base=10.0, subs=[1.0], numdecs=4, numticks=15):
16651665
"""
16661666
self.base(base)
16671667
self.subs(subs)
1668+
# this needs to be validated > 1 with traitlets
16681669
self.numticks = numticks
16691670
self.numdecs = numdecs
16701671

@@ -1737,6 +1738,9 @@ def tick_values(self, vmin, vmax):
17371738
subs = self._subs
17381739

17391740
stride = 1
1741+
if not self.numticks > 1:
1742+
raise RuntimeError('The number of ticks must be greater than 1 '
1743+
'for LogLocator.')
17401744
while numdec / stride + 1 > self.numticks:
17411745
stride += 1
17421746

0 commit comments

Comments
 (0)