Skip to content

[Bug]: alpha array-type not working with RGB image in imshow() #26092

Closed

Description

@patquem

Bug summary

Hi,
Whereas alpha = constant works with RGB image, this is not the case when working with an array-type for alpha.
(In my real case, I can only pass standard imshow parameters like alpha to the function of the library I use, and not a RGBA array).
Patrick

Code for reproduction

import numpy as np
import matplotlib.pyplot as plt
from skimage.color import gray2rgb

arr = np.random.random((10, 10))
arr_rgb = gray2rgb(arr)

alpha = np.ones_like(arr)
alpha[:5] = 0.2

plt.figure()
plt.tight_layout()
plt.subplot(121)
plt.title("Expected outcome")
plt.imshow(arr, alpha=alpha, cmap='gray')
plt.subplot(122)
plt.title("Actual outcome")
plt.imshow(arr_rgb, alpha=alpha)
plt.show()

Actual outcome

image

Expected outcome

image

Additional information

No response

Operating system

Windows

Matplotlib Version

3.7.1

Matplotlib Backend

TkAgg

Python version

Python 3.10.11

Jupyter version

No response

Installation

pip

Activity

  1. rcomer commented on Jun 8, 2023

    @rcomer
    Member

    Here is an example without skimage (fixed by #28437):

    import numpy as np
    import matplotlib.colors as mcolors
    import matplotlib.pyplot as plt
    
    arr = np.random.random((10, 10))
    cmap = plt.get_cmap('gray')
    norm = mcolors.Normalize()
    arr_rgb = cmap(norm(arr))[:, :, :3]
    
    alpha = np.ones_like(arr)
    alpha[:5] = 0.2
    
    plt.subplot(121)
    plt.title("Expected outcome")
    plt.imshow(arr, alpha=alpha, cmap='gray')
    
    plt.subplot(122)
    plt.title("Actual outcome")
    plt.imshow(arr_rgb, alpha=alpha)
    
    plt.show()

    I get this with both v3.7.1 and main:
    test

    The docstring for imshow does suggest that alpha may be an array, but it also says the array should be the same shape as the image so I assume that only means for the (M, N) case. So I'm not sure whether this is a bug or a documentation issue. ETA: though I'm not sure why the current behaviour would be desired - if you didn't want alpha to have an effect then you would just not pass it.

  2. patquem commented on Jun 8, 2023

    @patquem
    ContributorAuthor

    hello @rcomer,
    Thanks for replying.
    The library I use (Orix) manipulates complex object and allows to plot data thanks to this function : plot_map()
    The plotting is based on a RGB array (issue from a complex object) and I wish to pass alpha to highlight some area of my map. I can't act directly on the RGB array.
    Patrick

  3. added this to the v3.7.2 milestone on Jun 8, 2023
  4. tacaswell commented on Jun 8, 2023

    @tacaswell
    Member

    This is a bug. imshow basically has two orthogonal code paths, one for if the user passes us a ndim==2 array (which goes through color mapping) and one for for if eth user passes us a ndim==3 array (which more-or-less just goes out).

    I suspect that when we implemented the "alapha-as-array" we forgot about the RGB(A) case.

  5. tacaswell commented on Jun 8, 2023

    @tacaswell
    Member

    Yeah, the issue is in

    else:
    if A.ndim == 2: # _interpolation_stage == 'rgba'
    self.norm.autoscale_None(A)
    A = self.to_rgba(A)
    if A.shape[2] == 3:
    A = _rgb_to_rgba(A)
    alpha = self._get_scalar_alpha()
    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
    # or an RGBA array of re-sampled input
    output = self.to_rgba(output, bytes=True, norm=False)
    # output is now a correctly sized RGBA array of uint8
    # Apply alpha *after* if the input was greyscale without a mask
    if A.ndim == 2:
    alpha = self._get_scalar_alpha()
    alpha_channel = output[:, :, 3]
    alpha_channel[:] = ( # Assignment will cast to uint8.
    alpha_channel.astype(np.float32) * out_alpha * alpha)
    where we do apply alpha, but are asking for the scalar alpha not the array alpha.

    #14889 is where this feature came in and where we picked up the explicit call to _get_scalar_alpha.

    That said, I think the fix is to add logic to

    if A.shape[2] == 3:
    A = _rgb_to_rgba(A)
    where we promote RGB -> RGBA to take into account a possibly array-like alpha.

    I suspect that we have the same bug in

    self._imcache = self.to_rgba(A, bytes=True, norm=(A.ndim == 2))
    which is handling the fully un-sampled case and have confirmed we see the same bug in
    if A.ndim == 2: # _interpolation_stage == 'rgba'
    self.norm.autoscale_None(A)
    A = self.to_rgba(A)

    I am going to label this as good first issue as it is a clear bug (array alpha should work with RGB input). I would say this is a medium difficulty because the image processing code is a bit complicated. Most of that complexity is there for a good reason so be prepared to understand a majority of the _make_image function., The only dicey thing to work out is what to do in the case of the user passing both and RGBA array and an array alpha: Do we error, discard one (and warn), or blend them?

    The patch:

    diff --git a/lib/matplotlib/image.py b/lib/matplotlib/image.py
    index 135934a244..0ca26bdabf 100644
    --- a/lib/matplotlib/image.py
    +++ b/lib/matplotlib/image.py
    @@ -553,9 +553,11 @@ class _ImageBase(martist.Artist, cm.ScalarMappable):
                 else:
                     if A.ndim == 2:  # _interpolation_stage == 'rgba'
                         self.norm.autoscale_None(A)
    -                    A = self.to_rgba(A)
    -                if A.shape[2] == 3:
    +                    A = self.to_rgba(A, alpha=self.get_alpha())
    +                elif A.shape[2] == 3:
                         A = _rgb_to_rgba(A)
    +                    if alpha := self.get_alpha() is not None:
    +                        A[:, :, 3] = self.get_alpha()
                     alpha = self._get_scalar_alpha()
                     output_alpha = _resample(  # resample alpha channel
                         self, A[..., 3], out_shape, t, alpha=alpha)

    fixes at least the obvious cases, but does not address the "what about RGBA" issue and I think will double apply the alpha in scalar cases.

    I don't have time today to chase through those details or write tests.

  6. added
    🌱 Good first issueOpen a pull request against these issues if there are no active ones!
    Difficulty: Mediumhttps://matplotlib.org/devdocs/devel/contribute.html#good-first-issues
    on Jun 8, 2023
  7. github-actions commented on Jun 8, 2023

    @github-actions

    Good first issue - notes for new contributors

    This issue is suited to new contributors because it does not require understanding of the Matplotlib internals. To get started, please see our contributing guide.

    We do not assign issues. Check the Development section in the sidebar for linked pull requests (PRs). If there are none, feel free to start working on it. If there is an open PR, please collaborate on the work by reviewing it rather than duplicating it in a competing PR.

    If something is unclear, please reach out on any of our communication channels.

  8. rcomer commented on Jun 8, 2023

    @rcomer
    Member

    The only dicey thing to work out is what to do in the case of the user passing both and RGBA array and an array alpha: Do we error, discard one (and warn), or blend them?

    Looks like we have a precedent that alpha overrides the existing alpha channel on an rgba tuple.

    if alpha is not None:
    c = c[:3] + (alpha,)

    Edit: OTOH we also have precedent for ignoring alpha.
    https://matplotlib.org/stable/api/cm_api.html#matplotlib.cm.ScalarMappable.to_rgba

  9. greglucas commented on Jun 8, 2023

    @greglucas
    Contributor

    Oh the fun, there is also a precedent that it doesn't for ScalarMappables too :)

    If the last dimension is 3, the *alpha* kwarg (defaulting to 1)
    will be used to fill in the transparency. If the last dimension
    is 4, the *alpha* kwarg is ignored; it does not
    replace the preexisting alpha. A ValueError will be raised

  10. tacaswell commented on Jun 8, 2023

    @tacaswell
    Member

    And via

    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
    if you pass an RGBA array and a scalar alpha they get combined so we have precedent for 3 of the 4 options!

    import numpy as np
    import matplotlib.pyplot as plt
    a = np.zeros([2, 2, 4])
    a[:, :, 0] = 1
    a[:, :, 3] = np.linspace(0, 1, 4).reshape(2, 2)
    fig, (ax1, ax2) = plt.subplots(1, 2)
    ax1.imshow(a)
    ax2.imshow(a, alpha=.5)
    plt.show()
  11. 28 remaining items

  12. added a commit that references this issue on Dec 17, 2025
    4d40299
  13. komalkurapati147 commented on Dec 26, 2025

    @komalkurapati147

    Hi, I’d like to work on this issue as a beginner contributor.
    I plan to update imshow() so that array‑type alpha values are correctly applied to RGB images.
    Please let me know if it’s okay to proceed.

  14. added a commit that references this issue on Dec 29, 2025
    8ddd6f2
  15. added a commit that references this issue on Feb 18, 2026
    8b79331
  16. ShubhamPatel-16 commented on Feb 26, 2026

    @ShubhamPatel-16

    Hi, I’d like to work on this issue as a beginner contributor.
    I plan to update imshow() so that array‑type alpha values are correctly applied to RGB images.
    Please let me know if it’s okay to proceed

  17. melissawm commented on Feb 26, 2026

    @melissawm
    Member

    @ShubhamPatel-16 you can go ahead! Please make sure you carefully read our Contributing guide and the original issue, and let us know if you have questions. Cheers!

  18. story645 commented on Feb 26, 2026

    @story645
    Member

    @ShubhamPatel-16 I also suggest you read the closed pull requests to see why they were closed so that you don't try the same solution. Which, a really helpful triage contribution is posting a summary of the failed attempts to this issue.

  19. rcomer commented on Feb 26, 2026

    @rcomer
    Member

    This issue has two open linked PRs already. I believe #30795 is correct apart from the changenote. I have not looked at the more recent PR.

  20. timhoffm commented on Feb 27, 2026

    @timhoffm
    Member

    Let’s just fix the change note and push #30795 through

  21. added this to the v3.11.0 milestone on Feb 27, 2026
  22. tacaswell commented on Feb 27, 2026

    @tacaswell
    Member

    Will this be fixed by #31021 ?

  23. timhoffm commented on Feb 27, 2026

    @timhoffm
    Member

    I don't think so, at least it does not touch the same code (the change to image.py l.515ff in https://github.com/matplotlib/matplotlib/pull/30795/changes is the relevant fix). I'd be surprised if you could achieve the same effect with code changes in other places.

  24. ayshih commented on Feb 27, 2026

    @ayshih
    Contributor

    No, this bug is unrelated to #31021 (because it has to do with how the alpha channel is assembled prior to it being even handed off to the resampler/renderer)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions