-
Notifications
You must be signed in to change notification settings - Fork 28
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
Fixed a bug in __mul__ #24
Conversation
(ca, sa, 0.0, | ||
-sa, ca, 0.0, | ||
(ca, -sa, 0.0, | ||
sa, ca, 0.0, | ||
0.0, 0.0, 1.0)) |
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 seems that this modification simply switches from clockwise convention to counter-clockwise. See https://en.wikipedia.org/wiki/Transformation_matrix#Rotation
I don't see any problems with the examples. What were the expected results from each? |
The key point is that the two examples should have the same result. The first result, (0., 2.), is correct. |
@@ -385,7 +385,7 @@ def __mul__(self, other): | |||
vx, vy = other | |||
except Exception: | |||
return NotImplemented | |||
return (vx * sa + vy * sd + sc, vx * sb + vy * se + sf) | |||
return (vx * sa + vy * sb + sc, vx * sd + vy * se + sf) |
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 looks like a bug with the correct fix. The 3x3 matrix is [sa, sb, sc; sd, se, sf; 0, 0, 1] and the 3x1 matrix is [vx; vy; 1]. Can anyone else confirm?
I've given it a better look, and there might be a bug with the 3x3 dot 3x1 part of mul. Both results would then give (0.0, -2.0). Here is some proof with NumPy: import numpy as np
# R = rotation, T = translation, P = point
R = np.array(Affine.rotation(90.)).reshape((3, 3))
T = np.array(Affine.translation(-10., -5.)).reshape((3, 3))
P = np.array([12, 5, 1])
# Line 4
np.dot(R, np.dot(T, P)) # [ 0. -2. 1.]
# Line 5
np.dot(np.dot(R, T), P) # [ 0. -2. 1.] (Note, the 3rd item for points is always 1) The result (0, -2) is expected with this thought exercise:
However, I'm not sure about the other changes to rotation, since they change the chirality of the module, which is a bit invasive. However, I don't see any docstrings for |
Thanks @mwtoews for looking at this PR carefully. I apologize for not including more explanation (e.g., an example using NumPy like the one you constructed). The changes to the sign of rotation are, as you say, invasive, but they are required (I think -- I need to double check) once you correct for the error in |
It seems that up till now it was using a anti-clockwise convention, which I can see with playing with a raster in QGIS. There are different conventions used used in mapping, see [Bearing](https://en.wikipedia.org/wiki/Bearing_%28navigation%29#Bearing measurement). An alternative to streamline this convention is to use a -ve rotation: # using the version that fixes only the 3x3 dot 3x1 bug
Affine.rotation(-45.0) * (1.0, 1.0) # (-3.3306690738754696e-16, 1.414213562373095) which is effectively (0, sqrt(2)). I wonder if something like this should be used internally: @classmethod
def rotation(cls, angle, pivot=None, clockwise=False):
...
if not clockwise:
angle *= -1.0 |
@ToddSmall I think you've identified a bug. It seems to only affect transforming point coordinates from rotated affines (e.g. So I'd propose the following:
Thanks for digging into this! Affine's use being primarily with geospatial rasters, we don't often deal with rotation parameters so it's easy to see how that might've slipped though the cracks. |
@ToddSmall thanks for bringing this! I'm with @perrygeo above on next steps and happy to schedule a new release ASAP. |
in rasterio#24 (comment) o I've changed the rotation direction back to what was there before. o I've renamed test_composable to test_associate. o I've added an addition roundtrip test. o I've fixed the rotation example in README.rst.
@sgillies Would you prefer that I squash these two commits into one? |
@ToddSmall @mwtoews let's keep the original angle sign, which was positive == counter-clockwise, and has been reversed in this PR. @ToddSmall squashing would be great, thanks! |
A couple of the affine parameters in the affine * vector path in __mul__ were swapped. I also added some additional test coverage and fixed the rotation example in README.rst. I have *not* changed the rotation direction convention.
@ToddSmall this PR has changed the sense of the rotation angle and that still needs to be reverted. To demonstrate and guard against future regressions (sign changes being pernicious), I've added this new test to the master branch: https://github.com/sgillies/affine/blob/master/affine/tests/test_rotation.py. This test fails with a checkout of your branch:
With your code, the unit vector has been rotated clockwise, to below the x axis. |
@sgillies Sorry for the grief. I am confused about two things:
Addendum: In fact, my version does rotate counter-clockwise for a left-handed coordinate system. |
To clarify my logic here: The intention of
Point (10,0) becomes (0, -10): clockwise rotation. The
So one of the behaviors has to change if the math is to be consistent and associative. Fixing the I could see the case for the opposite approach: Assume the mult operator always produced correct results (by accident) but the rotation matrix has a bug and should be corrected to counter-clockwise. This is effectively the approach that @ToddSmall took initially and may be better (less impact to users who rely on the rotation of the current mult operator). In either case, the direction just needs to be made consistent to resolve this bug. The question is where do we break backwards compatibility? |
We're disagreeing on which one of the two rotation conventions described in http://mathworld.wolfram.com/RotationMatrix.html we should follow. SVG, for example, follows the second convention: a rotation(90) appears to rotate an object clockwise, but in fact what it does is rotate the user coordinate system containing the object 90 degrees counter-clockwise relative to the SVG canvas coordinate system, producing an apparent rotation of the object. In our module, where we're not going to introduce the concept of nested coordinate systems, let's stick to the first convention in that page: a positive angle rotates the object counter-clockwise within a fixed coordinate system. A right-handed coordinate system. If we agree on that, it's clear that the product of I'm not sure whether changing both the matrix and the operation while preserving the product is a breaking API change or not, but I'd be fine with releasing a 2.0 version if that helped downstream users. Rasterio, for one, won't be sensitive to this change. Thoughts on that @perrygeo @mwtoews? @ToddSmall please accept my apologies for how this issue has gone round! Are you still interested in landing this PR? |
I noticed that composition of transformations doesn't work, at least some of the time. Here's an example:
I think the problem is due to sign flips in the
__mul__
method. I've corrected the sign flips where necessary and added some additional test coverage. All tests pass.However, it's possible that I'm simply not understanding some nuance of raster coordinates versus "standard" Cartesian coordinates.