From c6caa3d8bd134ea093131a747fa4c1b0f74e8066 Mon Sep 17 00:00:00 2001 From: Chaoyi Hu Date: Sat, 22 Jun 2024 15:49:25 -0700 Subject: [PATCH 1/5] respect array alpha with interpolation_stage='rgba' --- lib/matplotlib/image.py | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/lib/matplotlib/image.py b/lib/matplotlib/image.py index f71d49db8ad8..8098be9d67ed 100644 --- a/lib/matplotlib/image.py +++ b/lib/matplotlib/image.py @@ -501,17 +501,25 @@ def _make_image(self, A, in_bbox, out_bbox, clip_bbox, magnification=1.0, if A.ndim == 2: # interpolation_stage = 'rgba' self.norm.autoscale_None(A) A = self.to_rgba(A) - alpha = self._get_scalar_alpha() - if A.shape[2] == 3: - # No need to resample alpha or make a full array; NumPy will expand - # this out and cast to uint8 if necessary when it's assigned to the - # alpha channel below. - output_alpha = (255 * alpha) if A.dtype == np.uint8 else alpha + + alpha = self.get_alpha() + if alpha is not None and np.ndim(alpha) > 0: + output_alpha = _resample(self, alpha, out_shape, t, resample=True) + output = _resample( # resample rgb channels + # alpha: float, should only be specified when alpha is a scalar + self, _rgb_to_rgba(A[..., :3]), out_shape, t) else: - output_alpha = _resample( # resample alpha channel - self, A[..., 3], out_shape, t, alpha=alpha) - output = _resample( # resample rgb channels - self, _rgb_to_rgba(A[..., :3]), out_shape, t, alpha=alpha) + alpha = self._get_scalar_alpha() + if A.shape[2] == 3: + # No need to resample alpha or make a full array; NumPy will + # expand this out and cast to uint8 if necessary when it's + # assigned to the alpha channel below. + output_alpha = (255 * alpha) if A.dtype == np.uint8 else alpha + else: + output_alpha = _resample( # resample alpha channel + self, A[..., 3], out_shape, t, alpha=alpha) + output = _resample( # resample rgb channels + self, _rgb_to_rgba(A[..., :3]), out_shape, t, alpha=alpha) output[..., 3] = output_alpha # recombine rgb and alpha # output is now either a 2D array of normed (int or float) data From 211db94436b1b9abdee4b9fe221fc12752816749 Mon Sep 17 00:00:00 2001 From: Chaoyi Hu Date: Thu, 11 Jul 2024 13:03:19 -0700 Subject: [PATCH 2/5] expanded tests, improved readability --- lib/matplotlib/image.py | 32 +++++++++++---------- lib/matplotlib/tests/test_image.py | 46 ++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 15 deletions(-) diff --git a/lib/matplotlib/image.py b/lib/matplotlib/image.py index 8098be9d67ed..ddb4fd610b12 100644 --- a/lib/matplotlib/image.py +++ b/lib/matplotlib/image.py @@ -501,25 +501,27 @@ def _make_image(self, A, in_bbox, out_bbox, clip_bbox, magnification=1.0, if A.ndim == 2: # interpolation_stage = 'rgba' self.norm.autoscale_None(A) A = self.to_rgba(A) - alpha = self.get_alpha() - if alpha is not None and np.ndim(alpha) > 0: - output_alpha = _resample(self, alpha, out_shape, t, resample=True) - output = _resample( # resample rgb channels - # alpha: float, should only be specified when alpha is a scalar - self, _rgb_to_rgba(A[..., :3]), out_shape, t) - else: - alpha = self._get_scalar_alpha() - if A.shape[2] == 3: - # No need to resample alpha or make a full array; NumPy will - # expand this out and cast to uint8 if necessary when it's - # assigned to the alpha channel below. - output_alpha = (255 * alpha) if A.dtype == np.uint8 else alpha + if alpha is None: # alpha parameter not specified + if A.shape[2] == 3: # image has no alpha channel + output_alpha = 255 if A.dtype == np.uint8 else 1.0 else: output_alpha = _resample( # resample alpha channel - self, A[..., 3], out_shape, t, alpha=alpha) + self, A[..., 3], out_shape, t) output = _resample( # resample rgb channels - self, _rgb_to_rgba(A[..., :3]), out_shape, t, alpha=alpha) + self, _rgb_to_rgba(A[..., :3]), out_shape, t) + elif np.ndim(alpha) > 0: # Array alpha + # user-specified array alpha overrides the existing alpha channel + output_alpha = _resample(self, alpha, out_shape, t) + output = _resample( + self, _rgb_to_rgba(A[..., :3]), out_shape, t) + else: # Scalar alpha + if A.shape[2] == 3: # broadcast scalar alpha + output_alpha = (255 * alpha) if A.dtype == np.uint8 else alpha + else: # or apply scalar alpha to existing alpha channel + output_alpha = _resample(self, A[..., 3], out_shape, t) * alpha + output = _resample( + self, _rgb_to_rgba(A[..., :3]), out_shape, t) output[..., 3] = output_alpha # recombine rgb and alpha # output is now either a 2D array of normed (int or float) data diff --git a/lib/matplotlib/tests/test_image.py b/lib/matplotlib/tests/test_image.py index d8ac3786c395..7437651d2b86 100644 --- a/lib/matplotlib/tests/test_image.py +++ b/lib/matplotlib/tests/test_image.py @@ -1711,3 +1711,49 @@ def test_resample_dtypes(dtype, ndim): axes_image = ax.imshow(data) # Before fix the following raises ValueError for some dtypes. axes_image.make_image(None)[0] + + +@pytest.mark.parametrize('intp_stage', ('data', 'rgba')) +@check_figures_equal() +def test_interpolation_stage_rgba_respects_alpha_param(fig_test, fig_ref, intp_stage): + axs_tst = fig_test.subplots(2, 3) + axs_ref = fig_ref.subplots(2, 3) + ny, nx = 3, 3 + scalar_alpha = 0.5 + array_alpha = np.random.rand(ny, nx) + + # When the image does not have an alpha channel, alpha should be specified + # by the user or default to 1.0 + im_rgb = np.random.rand(ny, nx, 3) + im_concat_default_a = np.ones((ny, nx, 1)) # alpha defaults to 1.0 + im_rgba = np.concatenate( # combine rgb channels with array alpha + (im_rgb, array_alpha.reshape((ny, nx, 1))), axis=-1 + ) + axs_tst[0][0].imshow(im_rgb) + axs_ref[0][0].imshow(np.concatenate((im_rgb, im_concat_default_a), axis=-1)) + axs_tst[0][1].imshow(im_rgb, interpolation_stage=intp_stage, alpha=scalar_alpha) + axs_ref[0][1].imshow( + np.concatenate( # combine rgb channels with broadcasted scalar alpha + (im_rgb, scalar_alpha * im_concat_default_a), axis=-1 + ), interpolation_stage=intp_stage + ) + axs_tst[0][2].imshow(im_rgb, interpolation_stage=intp_stage, alpha=array_alpha) + axs_ref[0][2].imshow(im_rgba, interpolation_stage=intp_stage) + + # When the image already has an alpha channel, multiply it by the + # scalar alpha param, or replace it by the array alpha param + axs_tst[1][0].imshow(im_rgba) + axs_ref[1][0].imshow(im_rgb, alpha=array_alpha) + axs_tst[1][1].imshow(im_rgba, interpolation_stage=intp_stage, alpha=scalar_alpha) + axs_ref[1][1].imshow( + np.concatenate( # combine rgb channels with scaled array alpha + (im_rgb, scalar_alpha * array_alpha.reshape((ny, nx, 1))), axis=-1 + ), interpolation_stage=intp_stage + ) + new_array_alpha = np.random.rand(ny, nx) + axs_tst[1][2].imshow(im_rgba, interpolation_stage=intp_stage, alpha=new_array_alpha) + axs_ref[1][2].imshow( + np.concatenate( # combine rgb channels with new array alpha + (im_rgb, new_array_alpha.reshape((ny, nx, 1))), axis=-1 + ), interpolation_stage=intp_stage + ) From 1e07a733fe6776123a35349df959679407800ec9 Mon Sep 17 00:00:00 2001 From: Chaoyi Hu Date: Fri, 12 Jul 2024 12:23:49 -0700 Subject: [PATCH 3/5] change note --- doc/api/next_api_changes/behavior/28437-CH.rst | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 doc/api/next_api_changes/behavior/28437-CH.rst diff --git a/doc/api/next_api_changes/behavior/28437-CH.rst b/doc/api/next_api_changes/behavior/28437-CH.rst new file mode 100644 index 000000000000..5cb3e1e150f2 --- /dev/null +++ b/doc/api/next_api_changes/behavior/28437-CH.rst @@ -0,0 +1,15 @@ +*alpha* parameter handling in _ImageBase._make_image +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This PR expands and clarifies code logic in _ImageBase._make_image +related to the *alpha* parameter, and ensures that `~.Axes.imshow` +exhibits the following behavior: + +1. When the image does not have alpha channel: +- *alpha* not specified: default to 1.0. +- scalar *alpha*: broadcast. +- array *alpha*: concatenate. +2. When the image has an alpha channel: +- *alpha* not specified: nothings needs to be done. +- scalar *alpha*: multiply the alpha channel with user-specified scalar. +- array *alpha*: replace the alpha channel with user-specified array. From a1247ce6d128b2931755cbedc48431a01a8e0e08 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Thu, 20 Feb 2025 16:29:54 -0500 Subject: [PATCH 4/5] DOC: re-word API change note Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> --- .../next_api_changes/behavior/28437-CH.rst | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/doc/api/next_api_changes/behavior/28437-CH.rst b/doc/api/next_api_changes/behavior/28437-CH.rst index 5cb3e1e150f2..ec779d81fbf9 100644 --- a/doc/api/next_api_changes/behavior/28437-CH.rst +++ b/doc/api/next_api_changes/behavior/28437-CH.rst @@ -1,15 +1,8 @@ -*alpha* parameter handling in _ImageBase._make_image -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*alpha* parameter handling on images +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -This PR expands and clarifies code logic in _ImageBase._make_image -related to the *alpha* parameter, and ensures that `~.Axes.imshow` -exhibits the following behavior: +When passing and array to `imshow(..., alpha=...)`, the parameter was silently ignored +if the image data was a RGB or RBGA image or if `rcParams['interpolation_state']` +resolved to "rbga". -1. When the image does not have alpha channel: -- *alpha* not specified: default to 1.0. -- scalar *alpha*: broadcast. -- array *alpha*: concatenate. -2. When the image has an alpha channel: -- *alpha* not specified: nothings needs to be done. -- scalar *alpha*: multiply the alpha channel with user-specified scalar. -- array *alpha*: replace the alpha channel with user-specified array. +This is now fixed, and the alpha array overwrites any previous transparency information. From 8a032c11a5d037a8f28f80990acda011fc946e66 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Thu, 20 Feb 2025 22:22:52 -0500 Subject: [PATCH 5/5] DOC: fix rst markup --- doc/api/next_api_changes/behavior/28437-CH.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/api/next_api_changes/behavior/28437-CH.rst b/doc/api/next_api_changes/behavior/28437-CH.rst index ec779d81fbf9..6121dfec8163 100644 --- a/doc/api/next_api_changes/behavior/28437-CH.rst +++ b/doc/api/next_api_changes/behavior/28437-CH.rst @@ -1,8 +1,8 @@ *alpha* parameter handling on images ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -When passing and array to `imshow(..., alpha=...)`, the parameter was silently ignored -if the image data was a RGB or RBGA image or if `rcParams['interpolation_state']` +When passing and array to ``imshow(..., alpha=...)``, the parameter was silently ignored +if the image data was a RGB or RBGA image or if :rc:`interpolation_state` resolved to "rbga". This is now fixed, and the alpha array overwrites any previous transparency information.