-
-
Notifications
You must be signed in to change notification settings - Fork 7.9k
Respect array alpha with interpolation_stage='rgba' in _Imagebase::_make_image #28437
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
c6caa3d
211db94
1e07a73
a1247ce
8a032c1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +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 :rc:`interpolation_state` | ||
resolved to "rbga". | ||
|
||
This is now fixed, and the alpha array overwrites any previous transparency information. |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -501,17 +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_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) | ||
alpha = self.get_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) | ||
output = _resample( # resample rgb channels | ||
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To be checked, but I think and explicit alpha should overwrite existing alpha rather than scaling it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Changing this will cause conflicts:
And the baseline image uses a combined alpha, see below. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok there is currently a different behavior between scalar and array alpha if the image already has an alpha. The documentation is not clear about the intended behavior:
My point is that this behavior is quite surprising and inconsistent:
In all other cases, when we have a rgba color and an explicit alpha, the explicit alpha overwrites the rgba alpha; effectively through https://matplotlib.org/stable/api/_as_gen/matplotlib.colors.to_rgba.html / https://matplotlib.org/stable/api/_as_gen/matplotlib.colors.to_rgba_array.html Additionally, I would have expected that scalar and array behave the same - either the scalar scales all elements and then the array also scales element-wise, or the both the scalar and the array overwrite. Since tests cover the current behavior, this PR should be fine in that it does not introduce a behavior change. OTOH I'm wondering whether this behavior is really intended. The test and alpha handling came in in #1955, but this PR only states to implement missing alpha handling. There has been no discussion whether overwriting or multiplying is the right choice. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
It should be possible to make the behaviors consistent. Forcing scalar alpha to overwrite causes conflicts, while multiplying array alpha pixel-wise does not seem to break any existing test case in This is a design choice so it's up to you. Either way, I can clarify the behavior in the docstring of
I see two methods of the same name
The second one is directly invoked in I'm not sure about
I think this PR and its related test case should be deemed correct - intended or not, because it does not cause conflicts anyway, either if we choose to keep the current behavior, or multiply array alpha. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @jklymak @anntzer I would like your opinion as image/interpolation experts. The underlying problem here is that for RGB and RGBA images the kwarg alpha has been ignored if it was an array. The solution for RGB is straight forward (just use the given alpha array). However, what is the expected behavior for RGBA images, i.e. Should the kwarg alpha replace the intrinsic alpha channel? Generally I would say yes, this is how alpha also works in other cases (which is coded in color.to_rgba_array). The problem is that a scalar alpha kwarg does not exhibit this behavior, instead Basically we have three options:
This is the current behavior: Code
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I can live with that. Then let's take this PR basically as is so that we can release it as a bugfix in 3.10.1, which is the important part. We can then decide how to change/migrate the current behavior for RGBA and scaler alpha. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sounds good to me. But I know @tacaswell and @anntzer have thoughts on this as well. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree taking this as is. I however disagree that multiplying in the scalar case for already RGBA data is inconsistent. In the case of lines the layer we add when rendering has variable alpha if anti-aliasing is turned on. When we set the alpha on a line we expect the result to be the combination of the AA alpha + the user alpha. By analogy, if the user provides us an RGBA image with non-unifrom alpha and provides an artist-level alpha the expectation would be that they are combined not replaced. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. From a consistency PoV I have a mild preference for scaling, which at least is internally consistent within imshow() (e.g. if we decide to also handle e.g. alpha=[[scalar]], a 1x1 array which is broadcastable to a 2D array, our life will be easier if we choose to scale) even though it is not consistent with other functions of matplotlib (but that's a secondary concern IMO). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The difference here, versus an antialiased line, is that the user has access to the alpha channel. I still think simpler is better. If someone sets alpha with a scalar and they meant it to be multiplicative, they can go back to their original alpha array and do the multiplication easily enough. |
||
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 | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AFAICS, these two calls were the only ones using the
_resample(..., alpha=...)
parameter. If it is not needed anymore, we should remove it.