From 3405fa7885f138753a1a6b344ade7f2aecc1033b Mon Sep 17 00:00:00 2001 From: Yi Wei Date: Fri, 30 Jun 2023 00:45:33 +0200 Subject: [PATCH 1/8] FIX: pcolormesh writing to read-only input mask --- lib/matplotlib/cbook.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/cbook.py b/lib/matplotlib/cbook.py index 87656b5c3c00..52252b0a6aae 100644 --- a/lib/matplotlib/cbook.py +++ b/lib/matplotlib/cbook.py @@ -668,7 +668,7 @@ def remove(self, o): self.push(elem) -def safe_masked_invalid(x, copy=False): +def safe_masked_invalid(x, copy=True): x = np.array(x, subok=True, copy=copy) if not x.dtype.isnative: # If we have already made a copy, do the byteswap in place, else make a From 616be00d7fc2caf0ae47ba344e109dbc36c2e9c0 Mon Sep 17 00:00:00 2001 From: Yi Wei Date: Fri, 30 Jun 2023 15:39:46 +0200 Subject: [PATCH 2/8] FIX: pcolormesh writing to read-only input mask --- lib/matplotlib/axes/_axes.py | 4 ++-- lib/matplotlib/cbook.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 39e926e98932..cbfa5d644e2a 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -5764,7 +5764,7 @@ def _pcolorargs(self, funcname, *args, shading='auto', **kwargs): else: X, Y = np.meshgrid(np.arange(ncols + 1), np.arange(nrows + 1)) shading = 'flat' - C = cbook.safe_masked_invalid(C) + C = cbook.safe_masked_invalid(C, copy=True) return X, Y, C, shading if len(args) == 3: @@ -5853,7 +5853,7 @@ def _interp_grid(X): Y = _interp_grid(Y.T).T shading = 'flat' - C = cbook.safe_masked_invalid(C) + C = cbook.safe_masked_invalid(C, copy=True) return X, Y, C, shading @_preprocess_data() diff --git a/lib/matplotlib/cbook.py b/lib/matplotlib/cbook.py index 52252b0a6aae..87656b5c3c00 100644 --- a/lib/matplotlib/cbook.py +++ b/lib/matplotlib/cbook.py @@ -668,7 +668,7 @@ def remove(self, o): self.push(elem) -def safe_masked_invalid(x, copy=True): +def safe_masked_invalid(x, copy=False): x = np.array(x, subok=True, copy=copy) if not x.dtype.isnative: # If we have already made a copy, do the byteswap in place, else make a From 6542ab94cdfd9cee7631180bae22b9984b00181b Mon Sep 17 00:00:00 2001 From: Yi Wei Date: Fri, 30 Jun 2023 15:40:07 +0200 Subject: [PATCH 3/8] add tests --- lib/matplotlib/tests/test_axes.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 4f01752819f4..5b8730c035f1 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -1478,6 +1478,20 @@ def test_pcolorargs(): match='are not monotonically increasing or decreasing'): ax.pcolormesh(X, Y, Z, shading='auto') + # GH 26093 + x = np.arange(6).reshape(2, 3) + mask = np.broadcast_to([False, True, False], x.shape) # read-only array + masked_x = np.ma.array(x, mask=mask) + plt.pcolormesh(masked_x) + + x = np.linspace(0, 1, 10) + y = np.linspace(0, 1, 10) + X, Y = np.meshgrid(x, y) + Z = np.sin(2 * np.pi * X) * np.cos(2 * np.pi * Y) + Zmask = np.broadcast_to([True, False]*5, Z.shape) + masked_Z = np.ma.array(Z, mask = Zmask) + plt.pcolormesh(X, Y, masked_Z) + @check_figures_equal(extensions=["png"]) def test_pcolornearest(fig_test, fig_ref): From a9d9eaf1853349dd6acb7131eebfd53f681249d0 Mon Sep 17 00:00:00 2001 From: Yi Wei Date: Fri, 30 Jun 2023 15:42:27 +0200 Subject: [PATCH 4/8] fix formatting --- lib/matplotlib/tests/test_axes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 5b8730c035f1..e16f2dceffd4 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -1489,7 +1489,7 @@ def test_pcolorargs(): X, Y = np.meshgrid(x, y) Z = np.sin(2 * np.pi * X) * np.cos(2 * np.pi * Y) Zmask = np.broadcast_to([True, False]*5, Z.shape) - masked_Z = np.ma.array(Z, mask = Zmask) + masked_Z = np.ma.array(Z, mask=Zmask) plt.pcolormesh(X, Y, masked_Z) From 68dad00be28d8c6a036f03cb4c35bc386c1e214d Mon Sep 17 00:00:00 2001 From: Yi Wei Date: Fri, 30 Jun 2023 18:39:08 +0200 Subject: [PATCH 5/8] check if the mask is unwritable --- lib/matplotlib/axes/_axes.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index cbfa5d644e2a..821d7d5e25bc 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -5740,6 +5740,9 @@ def imshow(self, X, cmap=None, norm=None, *, aspect=None, self.add_image(im) return im + def _is_unwritable(self, c): + return np.ma.is_masked(c) and not c.mask.flags.writeable + def _pcolorargs(self, funcname, *args, shading='auto', **kwargs): # - create X and Y if not present; # - reshape X and Y as needed if they are 1-D; @@ -5764,7 +5767,8 @@ def _pcolorargs(self, funcname, *args, shading='auto', **kwargs): else: X, Y = np.meshgrid(np.arange(ncols + 1), np.arange(nrows + 1)) shading = 'flat' - C = cbook.safe_masked_invalid(C, copy=True) + copy = self._is_unwritable(C) + C = cbook.safe_masked_invalid(C, copy=copy) return X, Y, C, shading if len(args) == 3: @@ -5853,7 +5857,8 @@ def _interp_grid(X): Y = _interp_grid(Y.T).T shading = 'flat' - C = cbook.safe_masked_invalid(C, copy=True) + copy = self._is_unwritable(C) + C = cbook.safe_masked_invalid(C, copy=copy) return X, Y, C, shading @_preprocess_data() From b1d657c663e451d06bfbf8e454e40b55eb4b5d8c Mon Sep 17 00:00:00 2001 From: Yi Wei Date: Fri, 30 Jun 2023 18:41:31 +0200 Subject: [PATCH 6/8] add test_pcolorargs_with_read_only --- lib/matplotlib/tests/test_axes.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index e16f2dceffd4..b7a4a851ee7f 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -1478,17 +1478,18 @@ def test_pcolorargs(): match='are not monotonically increasing or decreasing'): ax.pcolormesh(X, Y, Z, shading='auto') - # GH 26093 + +def test_pcolorargs_with_read_only(): x = np.arange(6).reshape(2, 3) - mask = np.broadcast_to([False, True, False], x.shape) # read-only array - masked_x = np.ma.array(x, mask=mask) + xmask = np.broadcast_to([False, True, False], x.shape) # read-only array + masked_x = np.ma.array(x, mask=xmask) plt.pcolormesh(masked_x) x = np.linspace(0, 1, 10) y = np.linspace(0, 1, 10) X, Y = np.meshgrid(x, y) Z = np.sin(2 * np.pi * X) * np.cos(2 * np.pi * Y) - Zmask = np.broadcast_to([True, False]*5, Z.shape) + Zmask = np.broadcast_to([True, False] * 5, Z.shape) masked_Z = np.ma.array(Z, mask=Zmask) plt.pcolormesh(X, Y, masked_Z) From 7d3937cf5578e97d9c6e6982c0754a59aa973caa Mon Sep 17 00:00:00 2001 From: Yi Wei Date: Fri, 30 Jun 2023 20:10:36 +0200 Subject: [PATCH 7/8] add assertions --- lib/matplotlib/tests/test_axes.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index b7a4a851ee7f..254e13992360 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -1482,6 +1482,7 @@ def test_pcolorargs(): def test_pcolorargs_with_read_only(): x = np.arange(6).reshape(2, 3) xmask = np.broadcast_to([False, True, False], x.shape) # read-only array + assert xmask.flags.writeable is False masked_x = np.ma.array(x, mask=xmask) plt.pcolormesh(masked_x) @@ -1490,6 +1491,7 @@ def test_pcolorargs_with_read_only(): X, Y = np.meshgrid(x, y) Z = np.sin(2 * np.pi * X) * np.cos(2 * np.pi * Y) Zmask = np.broadcast_to([True, False] * 5, Z.shape) + assert Zmask.flags.writeable is False masked_Z = np.ma.array(Z, mask=Zmask) plt.pcolormesh(X, Y, masked_Z) From 95d39d3d03d1d5cb391c5eb6344ce6a85827c373 Mon Sep 17 00:00:00 2001 From: Yi Wei Date: Fri, 30 Jun 2023 20:11:03 +0200 Subject: [PATCH 8/8] copy mask locally --- lib/matplotlib/axes/_axes.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 821d7d5e25bc..cbfa5d644e2a 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -5740,9 +5740,6 @@ def imshow(self, X, cmap=None, norm=None, *, aspect=None, self.add_image(im) return im - def _is_unwritable(self, c): - return np.ma.is_masked(c) and not c.mask.flags.writeable - def _pcolorargs(self, funcname, *args, shading='auto', **kwargs): # - create X and Y if not present; # - reshape X and Y as needed if they are 1-D; @@ -5767,8 +5764,7 @@ def _pcolorargs(self, funcname, *args, shading='auto', **kwargs): else: X, Y = np.meshgrid(np.arange(ncols + 1), np.arange(nrows + 1)) shading = 'flat' - copy = self._is_unwritable(C) - C = cbook.safe_masked_invalid(C, copy=copy) + C = cbook.safe_masked_invalid(C, copy=True) return X, Y, C, shading if len(args) == 3: @@ -5857,8 +5853,7 @@ def _interp_grid(X): Y = _interp_grid(Y.T).T shading = 'flat' - copy = self._is_unwritable(C) - C = cbook.safe_masked_invalid(C, copy=copy) + C = cbook.safe_masked_invalid(C, copy=True) return X, Y, C, shading @_preprocess_data()