-
-
Notifications
You must be signed in to change notification settings - Fork 2.2k
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
warp/rotate: fixed a bug with clipping when cval is not in the input range #6335
Conversation
Hello @ayshih! Thanks for updating this PR. We checked the lines you've touched for PEP 8 issues, and found:
Comment last updated at 2022-04-16 02:21:40 UTC |
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.
Thanks, @ayshih!
Can you add a test case that verifies this fix to the clipping bug to skimage/transform/tests/test_warps.py
?
Please also see the suggestion on avoiding NaN-related overhead for the common case where no NaN values are present.
Unit tests added |
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.
Thank you @ayshih for tackling this. I don't understand this code fully yet and might look deeper when I have more time but see below...
skimage/transform/_warps.py
Outdated
and not np.isnan(cval) | ||
and not min_val <= cval <= max_val | ||
and min_func(output_image) <= cval <= max_func(output_image)) |
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'm a little bit confused by the (new) logic in this function. So I tested it locally and noticed that commenting these 3 lines has no effect on testing test_warps.py
at all! So either the test are not covering this function fully or the logic does not work the way you expect it to.
Why is it relevant to preserve cval
if it's within min_func(output_image) <= cval <= max_func(output_image)
?
If not min_val <= cval <= max_val
, then the calls to min()
/ max()
within np.clip(...)
ensure that it is preserved. So I think that is why this line has no effect.
For not np.isnan(cval)
to matter, the test suite should use cval=np.nan
.
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.
Sorry, yes, there is incomplete test coverage, so I can add more tests.
Why is it relevant to preserve
cval
if it's withinmin_func(output_image) <= cval <= max_func(output_image)
?
My thinking here is that even if cval
is not in the input range, we don't want to expand the effective input range to include cval
unless we know that cval
was actually used in the output image. That is, if cval
is not in the output range, cval
wasn't actually used, so we proceed with clipping to the input range of min_val
to max_val
.
Note that it's not possible to test this logic using rotate()
because that invariably uses cval
for the output image for non-trivial rotations, while trivial rotations would not result in any pixel values that would need to be clipped. The test may be able to get away with using resize(..., mode='constant')
, but may require using warp()
.
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.
Tests added
It turns out that the explicit not np.isnan(cval)
is redundant because the min_func(output_image) <= cval <= max_func(output_image)
can only be true if cval is not NaN.
Surprisingly, it turns out that even if you remove both checks, the rest of the new code as written happens to still pass the test for when cval
is NaN due to a later quirk. cval
will be NaN for the clip()
call, but that doesn't break the clipping because min(min_val, np.nan)
evaluates to min_val
. I say "surprisingly" because not only does np.min([min_val, np.nan])
evaluate to np.nan
, min(np.nan, min_val)
(i.e., swapping the order of the arguments) evaluates to np.nan
as well. I wasn't aware of that consequence of comparison operators for np.nan
always evaluating to False
: min(min_val, np.nan) != min(np.nan, min_val)
.
skimage/transform/_warps.py
Outdated
if preserve_cval: | ||
cval_mask = output_image == cval | ||
|
||
np.clip(output_image, min_val, max_val, out=output_image) | ||
|
||
if preserve_cval: | ||
output_image[cval_mask] = cval |
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.
Just to make sure that I understand the way clip
and cval
interact together: if cval
is outside the range of the input image and clip=True
, what takes precedence? I think the old behavior tried to give cval
precedence, although I think that output_image == cval
never worked as intended. When I inspect the output image, the "outside area" seems to be close to but not precisely cval
... So I wonder if cval_mask
ever evaluated to 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.
Ah, reading the original issue might help with that question. 😅 So cval
should take precedence.
At last, all tests green |
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.
Thanks for adding the tests. I made just one new suggestion here
Co-authored-by: Gregory Lee <grlee77@gmail.com>
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.
Thanks @ayshih, it looks good now. sorry for taking so long to get back to this
@meeseeksdev backport to v0.19.x |
… when cval is not in the input range
…s not in the input range (#6411) Co-authored-by: Albert Y. Shih <ayshih@gmail.com>
This is an adaptation of the following PR included in skimage 0.19.3: scikit-image/scikit-image#6335
Description
Fixes #6305
For warp/rotate, when specifying
clip=True
(which is the default), the behavior whencval
is outside of the range of the input image gives slightly wrong results for orders 1 and 3 (which use native code) and very wrong results for orders 2, 4, and 5 (which fall back to SciPy code). This is due to the use of a strict equality check.Example code:
Before this PR:
After this PR:
This PR also probably closes #5599 since the second block in that issue would no longer appear inconsistent:
Checklist
./doc/examples
(new features only)./benchmarks
, if your changes aren't covered by anexisting benchmark
For reviewers
later.
__init__.py
.doc/release/release_dev.rst
.example, to backport to v0.19.x after merging, add the following in a PR
comment:
@meeseeksdev backport to v0.19.x
run-benchmark
label. To rerun, the labelcan be removed and then added again. The benchmark output can be checked in
the "Actions" tab.