From 32e727b00a7ddd86a20ab8f0117e0b0706cabfaf Mon Sep 17 00:00:00 2001 From: Fabien Maussion Date: Thu, 30 Jul 2015 12:24:37 +0200 Subject: [PATCH 1/3] bugs in colors.BoundaryNorm --- lib/matplotlib/colors.py | 16 ++++----- lib/matplotlib/tests/test_colors.py | 53 ++++++++++++++++++++++++++--- 2 files changed, 57 insertions(+), 12 deletions(-) diff --git a/lib/matplotlib/colors.py b/lib/matplotlib/colors.py index 29b4e9bb3bcc..3bb2730d4463 100644 --- a/lib/matplotlib/colors.py +++ b/lib/matplotlib/colors.py @@ -1270,22 +1270,22 @@ def __init__(self, boundaries, ncolors, clip=False): def __call__(self, x, clip=None): if clip is None: clip = self.clip - x = ma.asarray(x) - mask = ma.getmaskarray(x) - xx = x.filled(self.vmax + 1) + _x = ma.asarray(x) + mask = ma.getmaskarray(_x) + xx = np.atleast_1d(_x.filled(self.vmax + 1)) if clip: - np.clip(xx, self.vmin, self.vmax) - iret = np.zeros(x.shape, dtype=np.int16) + np.clip(xx, self.vmin, self.vmax, out=xx) + iret = np.atleast_1d(np.zeros(_x.shape, dtype=np.int16)) for i, b in enumerate(self.boundaries): iret[xx >= b] = i if self._interp: scalefac = float(self.Ncmap - 1) / (self.N - 2) iret = (iret * scalefac).astype(np.int16) iret[xx < self.vmin] = -1 - iret[xx >= self.vmax] = self.Ncmap + iret[xx >= self.vmax] = self.Ncmap # ISSUE here: clip is ignored ret = ma.array(iret, mask=mask) - if ret.shape == () and not mask: - ret = int(ret) # assume python scalar + if _x.shape == () and not mask: + ret = int(ret[0]) # assume python scalar return ret def inverse(self, value): diff --git a/lib/matplotlib/tests/test_colors.py b/lib/matplotlib/tests/test_colors.py index 0ac08a7db8c5..d86ebdb44e97 100644 --- a/lib/matplotlib/tests/test_colors.py +++ b/lib/matplotlib/tests/test_colors.py @@ -5,7 +5,7 @@ import itertools from distutils.version import LooseVersion as V -from nose.tools import assert_raises, assert_equal +from nose.tools import assert_raises, assert_equal, assert_true import numpy as np from numpy.testing.utils import assert_array_equal, assert_array_almost_equal @@ -39,15 +39,60 @@ def test_BoundaryNorm(): Github issue #1258: interpolation was failing with numpy 1.7 pre-release. """ - # TODO: expand this into a more general test of BoundaryNorm. + boundaries = [0, 1.1, 2.2] - vals = [-1, 0, 2, 2.2, 4] - expected = [-1, 0, 2, 3, 3] + vals = [-1, 0, 1, 2, 2.2, 4] + + # Without interpolation + expected = [-1, 0, 0, 1, 2, 2] + ncolors = len(boundaries) - 1 + bn = mcolors.BoundaryNorm(boundaries, ncolors) + assert_array_equal(bn(vals), expected) + # ncolors != len(boundaries) - 1 triggers interpolation + expected = [-1, 0, 0, 2, 3, 3] ncolors = len(boundaries) bn = mcolors.BoundaryNorm(boundaries, ncolors) assert_array_equal(bn(vals), expected) + # more boundaries for a third color + boundaries = [0, 1, 2, 3] + vals = [-1, 0.1, 1.1, 2.2, 4] + ncolors = 5 + expected = [-1, 0, 2, 4, 5] + bn = mcolors.BoundaryNorm(boundaries, ncolors) + assert_array_equal(bn(vals), expected) + + # a scalar as input should not trigger an error and should return a scalar + boundaries = [0, 1, 2] + vals = [-1, 0.1, 1.1, 2.2] + bn = mcolors.BoundaryNorm(boundaries, 2) + expected = [-1, 0, 1, 2] + for v, ex in zip(vals, expected): + ret = bn(v) + assert_true(isinstance(ret, six.integer_types)) + assert_array_equal(ret, ex) + assert_array_equal(bn([v]), ex) + + # same with interp + bn = mcolors.BoundaryNorm(boundaries, 3) + expected = [-1, 0, 2, 3] + for v, ex in zip(vals, expected): + ret = bn(v) + assert_true(isinstance(ret, six.integer_types)) + assert_array_equal(ret, ex) + assert_array_equal(bn([v]), ex) + + # Clipping + # For the "3" I do not know: isn't clipping also supposed to clip the max? + bn = mcolors.BoundaryNorm(boundaries, 3, clip=True) + expected = [0, 0, 2, 3] + for v, ex in zip(vals, expected): + ret = bn(v) + assert_true(isinstance(ret, six.integer_types)) + assert_array_equal(ret, ex) + assert_array_equal(bn([v]), ex) + def test_LogNorm(): """ From 7fab12947646b791e18016b67b27e63da47a5dc1 Mon Sep 17 00:00:00 2001 From: Fabien Maussion Date: Sat, 1 Aug 2015 15:56:04 +0200 Subject: [PATCH 2/3] added tests for masked array and clipping --- lib/matplotlib/colors.py | 20 ++++++++++++-------- lib/matplotlib/tests/test_colors.py | 26 ++++++++++++++++++++++++-- 2 files changed, 36 insertions(+), 10 deletions(-) diff --git a/lib/matplotlib/colors.py b/lib/matplotlib/colors.py index 3bb2730d4463..fbafd0508399 100644 --- a/lib/matplotlib/colors.py +++ b/lib/matplotlib/colors.py @@ -1252,8 +1252,9 @@ def __init__(self, boundaries, ncolors, clip=False): as i varies from 0 to len(boundaries)-2, j goes from 0 to ncolors-1. - Out-of-range values are mapped to -1 if low and ncolors - if high; these are converted to valid indices by + If clip == False, Out-of-range values are mapped + to -1 if low and ncolors if high; these are converted + to valid indices by :meth:`Colormap.__call__` . """ self.clip = clip @@ -1270,21 +1271,24 @@ def __init__(self, boundaries, ncolors, clip=False): def __call__(self, x, clip=None): if clip is None: clip = self.clip - _x = ma.asarray(x) - mask = ma.getmaskarray(_x) - xx = np.atleast_1d(_x.filled(self.vmax + 1)) + x = ma.masked_invalid(x, copy=False) + mask = ma.getmaskarray(x) + xx = np.atleast_1d(x.filled(self.vmax + 1)) if clip: np.clip(xx, self.vmin, self.vmax, out=xx) - iret = np.atleast_1d(np.zeros(_x.shape, dtype=np.int16)) + max_col = self.Ncmap - 1 + else: + max_col = self.Ncmap + iret = np.atleast_1d(np.zeros(x.shape, dtype=np.int16)) for i, b in enumerate(self.boundaries): iret[xx >= b] = i if self._interp: scalefac = float(self.Ncmap - 1) / (self.N - 2) iret = (iret * scalefac).astype(np.int16) iret[xx < self.vmin] = -1 - iret[xx >= self.vmax] = self.Ncmap # ISSUE here: clip is ignored + iret[xx >= self.vmax] = max_col ret = ma.array(iret, mask=mask) - if _x.shape == () and not mask: + if x.shape == (): ret = int(ret[0]) # assume python scalar return ret diff --git a/lib/matplotlib/tests/test_colors.py b/lib/matplotlib/tests/test_colors.py index d86ebdb44e97..1f8cf2acd654 100644 --- a/lib/matplotlib/tests/test_colors.py +++ b/lib/matplotlib/tests/test_colors.py @@ -84,15 +84,37 @@ def test_BoundaryNorm(): assert_array_equal(bn([v]), ex) # Clipping - # For the "3" I do not know: isn't clipping also supposed to clip the max? bn = mcolors.BoundaryNorm(boundaries, 3, clip=True) - expected = [0, 0, 2, 3] + expected = [0, 0, 2, 2] for v, ex in zip(vals, expected): ret = bn(v) assert_true(isinstance(ret, six.integer_types)) assert_array_equal(ret, ex) assert_array_equal(bn([v]), ex) + # Masked arrays + boundaries = [0, 1.1, 2.2] + vals = [-1., np.NaN, 0, 1.4, 9] + + # Without interpolation + ncolors = len(boundaries) - 1 + bn = mcolors.BoundaryNorm(boundaries, ncolors) + expected = np.ma.masked_array([-1, -99, 0, 1, 2], mask=[0, 1, 0, 0, 0]) + assert_array_equal(bn(vals), expected) + + # With interpolation + bn = mcolors.BoundaryNorm(boundaries, len(boundaries)) + expected = np.ma.masked_array([-1, -99, 0, 2, 3], mask=[0, 1, 0, 0, 0]) + assert_array_equal(bn(vals), expected) + + # Non-trivial masked arrays + vals = [np.Inf, np.NaN] + assert_true(np.all(bn(vals).mask)) + vals = [np.Inf] + assert_true(np.all(bn(vals).mask)) + + # Invalid scalar raises an exception + assert_raises(Exception, bn, np.NaN) def test_LogNorm(): """ From be12dc764f7b4805ea49614e3f41bf9bb7b06b6b Mon Sep 17 00:00:00 2001 From: Fabien Maussion Date: Mon, 3 Aug 2015 18:06:06 +0200 Subject: [PATCH 3/3] now consistent with other Normalize classes --- lib/matplotlib/colors.py | 17 ++++++++++------- lib/matplotlib/tests/test_colors.py | 8 +++----- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/lib/matplotlib/colors.py b/lib/matplotlib/colors.py index fbafd0508399..736d0f2d76b7 100644 --- a/lib/matplotlib/colors.py +++ b/lib/matplotlib/colors.py @@ -1252,10 +1252,12 @@ def __init__(self, boundaries, ncolors, clip=False): as i varies from 0 to len(boundaries)-2, j goes from 0 to ncolors-1. - If clip == False, Out-of-range values are mapped + Out-of-range values are mapped to -1 if low and ncolors if high; these are converted to valid indices by :meth:`Colormap.__call__` . + If clip == True, out-of-range values + are mapped to 0 if low and ncolors-1 if high. """ self.clip = clip self.vmin = boundaries[0] @@ -1268,18 +1270,19 @@ def __init__(self, boundaries, ncolors, clip=False): else: self._interp = True - def __call__(self, x, clip=None): + def __call__(self, value, clip=None): if clip is None: clip = self.clip - x = ma.masked_invalid(x, copy=False) - mask = ma.getmaskarray(x) - xx = np.atleast_1d(x.filled(self.vmax + 1)) + + xx, is_scalar = self.process_value(value) + mask = ma.getmaskarray(xx) + xx = np.atleast_1d(xx.filled(self.vmax + 1)) if clip: np.clip(xx, self.vmin, self.vmax, out=xx) max_col = self.Ncmap - 1 else: max_col = self.Ncmap - iret = np.atleast_1d(np.zeros(x.shape, dtype=np.int16)) + iret = np.zeros(xx.shape, dtype=np.int16) for i, b in enumerate(self.boundaries): iret[xx >= b] = i if self._interp: @@ -1288,7 +1291,7 @@ def __call__(self, x, clip=None): iret[xx < self.vmin] = -1 iret[xx >= self.vmax] = max_col ret = ma.array(iret, mask=mask) - if x.shape == (): + if is_scalar: ret = int(ret[0]) # assume python scalar return ret diff --git a/lib/matplotlib/tests/test_colors.py b/lib/matplotlib/tests/test_colors.py index 1f8cf2acd654..c9166a5a7db3 100644 --- a/lib/matplotlib/tests/test_colors.py +++ b/lib/matplotlib/tests/test_colors.py @@ -94,7 +94,7 @@ def test_BoundaryNorm(): # Masked arrays boundaries = [0, 1.1, 2.2] - vals = [-1., np.NaN, 0, 1.4, 9] + vals = np.ma.masked_invalid([-1., np.NaN, 0, 1.4, 9]) # Without interpolation ncolors = len(boundaries) - 1 @@ -108,13 +108,11 @@ def test_BoundaryNorm(): assert_array_equal(bn(vals), expected) # Non-trivial masked arrays - vals = [np.Inf, np.NaN] + vals = np.ma.masked_invalid([np.Inf, np.NaN]) assert_true(np.all(bn(vals).mask)) - vals = [np.Inf] + vals = np.ma.masked_invalid([np.Inf]) assert_true(np.all(bn(vals).mask)) - # Invalid scalar raises an exception - assert_raises(Exception, bn, np.NaN) def test_LogNorm(): """