Skip to content
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

Round instead of ceil output shape when rotating image #3173

Merged
merged 2 commits into from Sep 19, 2019

Conversation

ahojnnes
Copy link
Member

Addresses issue #3071

Previously, we ensured that the output contained every subpixel information
from the input when rotating by ceiling the shape. This lead to inconsistent
output when rotating by multiples of 90 degrees. For example:

>>> transform.rotate(np.zeros((470, 230)), angle=90, resize=True).shape
(231, 470)

The new behavior correctly produces an output shape of (230, 470).

Previously, we ensured that the output contained every subpixel information
from the input when rotating by ceiling the shape. This lead to inconsistent
output when rotating by multiples of 90 degrees. For example:

    >>> transform.rotate(np.zeros((470, 230)), angle=90, resize=True).shape
    (231, 470)

The new behavior correctly produces an output shape of (230, 470).
@@ -371,7 +371,7 @@ def rotate(image, angle, resize=False, center=None, order=1, mode='constant',
maxr = corners[:, 1].max()
out_rows = maxr - minr + 1
out_cols = maxc - minc + 1
output_shape = np.ceil((out_rows, out_cols))
output_shape = np.round((out_rows, out_cols))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure about this. In Python 3 this rounds to the nearest even value, which means that the function might still produce a differently shaped output if some dimensions of the input image are odd. Should this line use floor instead?

P.S. To the best of my knowledge, np.round is deprecated, and np.around is recommended instead.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The even/odd behavior is not an issue, since it only takes effect at the 0.5 boundary, otherwise the behavior is expected. np.round/np.around is the correct choice here.

@codecov-io
Copy link

codecov-io commented Jun 11, 2018

Codecov Report

Merging #3173 into master will increase coverage by <.01%.
The diff coverage is 100%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #3173      +/-   ##
==========================================
+ Coverage   86.08%   86.09%   +<.01%     
==========================================
  Files         338      338              
  Lines       27234    27237       +3     
==========================================
+ Hits        23444    23449       +5     
+ Misses       3790     3788       -2
Impacted Files Coverage Δ
skimage/transform/_warps.py 99.45% <100%> (ø) ⬆️
skimage/transform/tests/test_warps.py 100% <100%> (ø) ⬆️
skimage/draw/_random_shapes.py 97.89% <0%> (+2.1%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 1be50d1...feb8026. Read the comment docs.

@emmanuelle
Copy link
Member

Thanks @ahojnnes This indeed solves #3071, at the cost of losing a bit of subpixel information on edges. Are we comfortable wtth this? The idea of resize=True is to keep all the pixel information.
I'm inclined to say that it's a very small loss, but I wonder what others think... (@jni?)

@jni
Copy link
Member

jni commented Jun 12, 2018

I'm 👍 on this change.

@stefanv
Copy link
Member

stefanv commented Jun 12, 2018 via email

@JDWarner
Copy link
Contributor

Rounding to evens is IEEE standards correct. It eliminates bias which would otherwise be present during the rounding operation.

@stefanv
Copy link
Member

stefanv commented Jun 12, 2018 via email

@jni
Copy link
Member

jni commented Jun 12, 2018

@stefanv my expectation would be to use round here. If you are only doing a single transformation, the subpixel accuracy is unlikely to matter. If you are chaining many transformations through their outputs (instead of through the transformation chaining API), then you will have much bigger problems than a missing edge pixel.

The 90° rotation is a clear use case where ceil is broken. I think it should be a priority to fix it. Got any better ideas than this?

@ahojnnes
Copy link
Member Author

My rationale was that even if we use ceil, the output image will be cut off “abpruptly” for any interpolation order but nearest neighbor interpolation. So, ceil is not any better than round in these cases, and then round is just better because it solves the rot90 problem.

@stefanv
Copy link
Member

stefanv commented Jun 12, 2018

So imagine two images, each scaled by 0.5x. The shapes are (10, 10) and (7, 7). What should the output sizes ideally be? This PR says (5, 5) and (3, 3), if I understand correctly.

This means that at least one data-point now falls outside of the new image.

@jni
Copy link
Member

jni commented Jun 12, 2018

@stefanv nearest even argues the new size would be (4, 4)? But, I get your point, (9, 9) would go to (4, 4). I don't think this is a huge problem as the image should be pre-filtered for downsampling. And in the converse case of (5, 5), now you are getting data that is mostly synthesised outside of the input image. Is that worse?

@ahojnnes
Copy link
Member Author

We are talking about rotating an image and not rescaling? If at all, the output image will be bigger than the input image if resize=True.

@jni
Copy link
Member

jni commented Jun 12, 2018

@ahojnnes I think @stefanv wanted to use the example of rescaling as a case study of what should happen. But actually for rescaling it seems we are already rounding:

>>> import numpy as np
>>> from skimage import transform
>>> image = np.random.random((9, 9))
>>> transform.rescale(image, 0.5, multichannel=False).shape
(4, 4)

@stefanv does this existing behaviour change your mind about what should happen with rotate?

@ahojnnes
Copy link
Member Author

@jni In the rescale case, the function just rounds the output size, but the actual rescaling factor used during interpolation is then determined based on the rounded coordinates, so no information is lost in this case for sure.

@jni
Copy link
Member

jni commented Jun 12, 2018

the actual rescaling factor used during interpolation is then determined based on the rounded coordinates

Whoa! I'm not sure I like that, but ok, thanks for the info! =)

@axu2
Copy link

axu2 commented Jun 12, 2018

Unsure of what exactly you want, but maybe if a user wants to rotate an image by a multiple of 90 degrees, they shouldn't be using transform.rotate and instead use np.rot90?

https://docs.scipy.org/doc/numpy/reference/generated/numpy.rot90.html

Maybe transform.rot can call np.rotate when its a multiple of 90 deg?

@stefanv
Copy link
Member

stefanv commented Jun 13, 2018

Eh, yes, not sure I like the magic of rescale either.

If we want to commit to magical fixing of coordinates, why not shrink the output coordinates by 5 * eps (or some chosen delta) and take the ceil? Still a consistent recipe, fixes the rot90 case, but does not cause sizes like 4.5 to go down to 4.

@ahojnnes
Copy link
Member Author

ahojnnes commented Oct 28, 2018

@stefanv I don't think your last proposal is a good fix for the problem. This "magical threshold" that you mention will depend on the image size and might not work in many cases. How about we do a check for angle mod 90 == 0 and enforce the dimensions to be correct for this specific case?

@alexdesiqueira
Copy link
Member

Hey everyone,
any news on this one? :)

@jni
Copy link
Member

jni commented May 25, 2019

@stefanv is the blocker here, you should go bug him in person @alexandrejaguar =P

Reading the whole discussion again, I still think rounding is the correct and least-surprising choice here.

@stefanv
Copy link
Member

stefanv commented Sep 19, 2019

It's consistent, it fixes the issue; let's put it in.

@stefanv stefanv merged commit f9a56eb into scikit-image:master Sep 19, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

9 participants