-
-
Notifications
You must be signed in to change notification settings - Fork 7.9k
BUG: fix normalizing image data contained in np.ndarray subclass #27682
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
base: main
Are you sure you want to change the base?
Conversation
Can you create a fake unit that mimics the astropy behavior? Something like a very thin wrapper over nd.array that triggers the error but is fixed here? Something like #26953 |
febad98
to
f6340d4
Compare
Thank you for your suggestion ! I wasn't able to construct such a mock with a bottom-up approach (build one from the ground up), supposedly because of the sheer complexity of |
Is much appreciated since that helps us better understand what's going wonky here. |
eed2fe8
to
d337f69
Compare
Ok I think it's now ready for review. |
Thanks! Re: mocking astropy.units.Quantity -- @mhvk can double check if needed. |
Apologies for asking this several hours late, but are either of these relevant here?
matplotlib/lib/matplotlib/tests/test_image.py Line 1199 in 59f9b3c
|
And the second one to #18286 that mentioned |
@rcomer Indeed these classes will not reproduce the issue we're currently seeing with |
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.
Certainly looks like a reasonable mock-up of Quantity
!
lib/matplotlib/image.py
Outdated
@@ -461,12 +461,12 @@ def _make_image(self, A, in_bbox, out_bbox, clip_bbox, magnification=1.0, | ||
vmid = np.float64(self.norm.vmin) + dv / 2 | ||
fact = 1e7 if scaled_dtype == np.float64 else 1e4 | ||
newmin = vmid - dv * fact | ||
if newmin < a_min: | ||
if newmin < np.float64(a_min): |
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.
I'd maybe do this just below where A
itself is converted to a plain array, ie.,
A_scaled = np.array(A, dtype=scaled_dtype)
a_min = np.float64(a_min)
a_max = np.float64(a_max)
(Arguably, scaled_dtype.type
might be more logical than np.float64
, but then one should adjust other things too)
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.
done
Can you add a comment that that's the important bit? |
Actually I think every part of the mock class is crucial to reproduce the problem, should I really comment all of them ? |
d337f69
to
b91cae8
Compare
😬 No, if it's a combination of the things then yeah it doesn't need a specific comment. |
I've been trying to understand what's going on here. It seems:
I think the better long term fix here is to add proper support for the image data to have units in the So it's unclear to me whether it's worth merging this PR as a bit of a stopgap fix, or whether we should be aiming to add proper support for unitful data in |
As a quick proof of concept, the following diff fixes the original issue, also allows mousing over the data to work (that's still broken with this PR), and opens the door to further improvements like labelling the colorbar with the units. diff --git a/lib/matplotlib/image.py b/lib/matplotlib/image.py
index e326906269..88325a59ed 100644
--- a/lib/matplotlib/image.py
+++ b/lib/matplotlib/image.py
@@ -26,6 +26,7 @@ import matplotlib.colors as mcolors
from matplotlib.transforms import (
Affine2D, BboxBase, Bbox, BboxTransform, BboxTransformTo,
IdentityTransform, TransformedBbox)
+import matplotlib.units as munits
_log = logging.getLogger(__name__)
@@ -724,6 +725,13 @@ class _ImageBase(martist.Artist, cm.ScalarMappable):
----------
A : array-like or `PIL.Image.Image`
"""
+ converter = munits.registry.get_converter(A)
+ if converter is not None:
+ self._units = converter.default_units(A, self)
+ A = converter.convert(A, self._units, self)
+ else:
+ self._units = None
+
if isinstance(A, PIL.Image.Image):
A = pil_to_array(A) # Needed e.g. to apply png palette.
self._A = self._normalize_image_array(A) It does have the issue that the last arguments to the converter methods are meant to be of type |
Happy to drop this PR in favour of a better fix if needed ! |
We don't have converter logic for data passed to color mapping. Folks are asked to pass a numpy array to scalar mappables. Maybe its OK to normalize those mappable to so something without units earlier, but I don't know that we have enough infrastructure in place to actually use unit converters here. |
I still think that throwing a convertor on NoNorm (or possibly writing a I think the complicated part has always been propagating the correct labels back to the colorbar. |
PR summary
This fixes a downstream issue with plotting data from a
astropy.units.Quantity
object (which derives fromnp.ndarray
), namely astropy/astropy#11306Although the external issue was also linked to #19476, this patch does not fix that problem.
As a result, I am unsure how to write a test for the astropy bug here, though I could easily add a test in astropy itself (which is regularily tested against matplotlib dev).
PR checklist