-
-
Notifications
You must be signed in to change notification settings - Fork 7.9k
Fix crash when imshow encounters longdouble data #10768
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
Conversation
I believe that is correct. |
If we just down-cast to float64, that's not really supporting float128, its just not crashing. I think that float128 should error, maybe w/ a better error, and tell users they need to cast themselves, given that opinions vary wildly as to how down-casting should happen. ping @efiring who had an opinion about all this when I added float128 before. |
Maybe downcast with a warning. Effectively, as a plotting library, we are always downcasting drastically, since plots have very limited combinations of dynamic range and resolution. I presume the use of float128 is very rare, and I don't see any way that "supporting" it in matplotlib is going to help anyone do anything useful. Use of float128 should be very rare because it is wildly platform-dependent, and doesn't even provide the advertised 128 bits. It is not included in the numpy table of supported types: |
I'd be fine with downcast-with-warning... For this PR, you'd want to add the warning, and an assert that its emited in the test... |
Probably also worth (effectively) reverting #8447 then, given that by the time we reach colormapping, the data is certainly not float128 anymore? (dunno) |
I've updated the pull request to add a warning before casting, remove the redundant check for casting in colors.py, and added an assert in the test to ensure that the warning is emitted. |
lib/matplotlib/image.py
Outdated
@@ -361,6 +362,12 @@ def _make_image(self, A, in_bbox, out_bbox, clip_bbox, magnification=1.0, | |||
a_min, a_max = np.int32(0), np.int32(1) | |||
if inp_dtype.kind == 'f': | |||
scaled_dtype = A.dtype | |||
# Cast to float64 | |||
if A.dtype == np.longdouble: |
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.
OK, still not sure what to do about np.float128
and np.float96
. Should these be trapped here as well?
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.
Based on the platform version, np.float128 or np.float96 may or may not exist so adding traps for them might not work. Instead, I think casting anything that isn't float64, float32 or float16 to float64 would be a better idea.
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.
Really, they aren't even defined? Yuck....
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.
Yes. np.longdouble is platform dependent also. That's why I most likely have to remove the assert for warnings from the test case since the CI runs on Windows which has np.longdouble == 'float64' == 'float'. This causes either excessive warnings which will break all sorts of CI tests (if I choose to show warnings for each cast) or no warnings at all (if I only show warnings on casts from a larger size to smaller).
Given the issues with longdouble float128 etc maybe the right thing to do is let this error? |
The problem with longdouble is mostly with testing. The changes in this PR makes a downcast to float64 whenever it encounters longdouble, float96 or float128, which is working fine. The issue is that, on Windows, longdouble == float64 so a trap/error for longdouble would cause an error on float/float64 too. Adding a check for float64 solves this but means that warnings will only be emitted on platforms where longdouble == float128/float96, which makes it hard to write a test that asserts for a warning. |
lib/matplotlib/image.py
Outdated
@@ -361,6 +362,13 @@ def _make_image(self, A, in_bbox, out_bbox, clip_bbox, magnification=1.0, | |||
a_min, a_max = np.int32(0), np.int32(1) | |||
if inp_dtype.kind == 'f': | |||
scaled_dtype = A.dtype | |||
# Cast to float64 | |||
if not (A.dtype == np.float32 or A.dtype == np.float16): |
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.
if A.dtype not in (np.float32, np.float16):
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.
lib/matplotlib/image.py
Outdated
@@ -361,6 +362,13 @@ def _make_image(self, A, in_bbox, out_bbox, clip_bbox, magnification=1.0, | |||
a_min, a_max = np.int32(0), np.int32(1) | |||
if inp_dtype.kind == 'f': | |||
scaled_dtype = A.dtype | |||
# Cast to float64 | |||
if not (A.dtype == np.float32 or A.dtype == np.float16): | |||
if (A.dtype != np.float64): |
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.
Remove unnecessary parentheses.
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.
@@ -361,6 +362,13 @@ def _make_image(self, A, in_bbox, out_bbox, clip_bbox, magnification=1.0, | |||
a_min, a_max = np.int32(0), np.int32(1) | |||
if inp_dtype.kind == 'f': | |||
scaled_dtype = A.dtype | |||
# Cast to float64 | |||
if A.dtype not in (np.float32, np.float16): | |||
if A.dtype != np.float64: |
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.
might as well just make this one if-statement....
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.
There are things happening after this one but not in it...
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.
if A.dtype not in (np.float32, np.float16, np.float64):
warnings.warn()
scaled_dtype = np.float64
should do it, right?
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 actually did this in commit a453ba4. This led to problems in some Windows environments and failures in CI. It seems like scaled_dtype needs to be set to np.float64 instead of np.longdouble even if np.longdouble == np.float64.
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.
not in
returns something different than !=
? I'm not following the logic here at all.
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.
It's very platform specific. Running on Win-64 gives:
>>> numpy.longdouble in (numpy.float16, numpy.float64)
False
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.
But numpy.longdouble == numpy.float64
returns true?
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.
Very weird.
>>> numpy.longdouble == numpy.float64
False
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.
Again, the problem doesn't have anything to do with the check here; it's the use of scaled_dtype
later that causes a problem. If you flatten the condition, then scaled_dtype=np.longdouble
is not replaced by np.float64
(on Windows only!) It's then crashing in C code later (which is arguably a NumPy inconsistency) which doesn't understand np.longdouble
.
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.
OK; I get it now. everything not np.float16/32
has to be explicitly set to np.float64
, even if python thinks np.longdouble
is equivalent to np.float64
, because the C code doesn't think its equivalent.
Sorry to be so dense, but I do think its pretty bad that the C code ends up incompatible in this way.
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.
This should be squashed.
lib/matplotlib/image.py
Outdated
@@ -10,6 +10,7 @@ | |||
from io import BytesIO | |||
|
|||
from math import ceil | |||
import warnings |
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.
This belongs after logging
.
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
cc74d0b
to
809353c
Compare
Squashed all the previous commits into one commit. |
I'll merge tomorrow if no one does so earlier. Just want to leave it up if anyone else wants to chime in. But I think I understand what is going on now and this fix seems fine. |
PR Summary
This PR solves #10342. Support for longdouble was added in #8447, however attempting to draw anything causes a crash. This PR adds a cast to float64 when imshow encounters longdouble data.
PR Checklist