Add AffineTransform transform class #6218
Conversation
|
Small comments are in your own PR, but here some larger ones:
|
|
db13a87 to
3abf575
Compare
|
@mhvk I think I took care of your first two points. On the third, I'm not totally sold on adding the offset functionality to |
|
@adrn - up late, like I last night... On |
|
Love this PR. Nice work @adrn et al! |
4de3f11 to
df5081f
Compare
|
I've done the rebase already, just fixing a few lingering issues now. |
|
Just so comments don't get lost on a sub-PR, on """ How about using it for |
|
I'd be fine with that - that is, just make |
5a93662 to
4888e93
Compare
|
OK, now this diff should be much cleaner. Closing the sub-PR on my fork. Please leave any future comments here. |
mhvk
left a comment
There was a problem hiding this comment.
Mostly small & nitpicks, but also one possible buglet.
Most relevant would be a decision on frame.cartesian as the way to get to_cartesian_with_differentials().
| bary_sun_vel = None | ||
|
|
||
| if bary_sun_vel is not None: | ||
| bary_sun_pos = bary_sun_pos.with_differentials(bary_sun_vel) |
There was a problem hiding this comment.
Why not do this above, inside the if statement? Then no need to set bary_sun_vel = None either.
(perhaps get_body_barycentric_posvel should return the combination... for another time...)
| rotation = rotation_matrix(-np.arcsin(z_d), 'y') | ||
| representation = (intrep - translation).transform(rotation) | ||
| translation = CartesianRepresentation(gcf.galcen_distance * [1., 0., 0.]) | ||
| z_d = (gcf.z_sun / gcf.galcen_distance) |
There was a problem hiding this comment.
nitpick: why the parentheses?
| bary_sun_pos = get_body_barycentric('sun', hcrs_frame.obstime) | ||
| bary_sun_vel = None | ||
|
|
||
| bary_sun_pos = -bary_sun_pos |
There was a problem hiding this comment.
I'd again do this in the if statement, but now of course for the else clause you need a minus sign in front of get_body_barycentric.
| # Note: the above docstring gets overridden for differentials. | ||
| raise NotImplementedError() | ||
|
|
||
| def to_cartesian_with_differentials(self): |
There was a problem hiding this comment.
Hopefully, it does make sense in BaseFrame.cartesian -- the only thing I don't like is this PR!
|
|
||
| assert diff.get_name() == 'cartesian' | ||
| assert not diff.differentials | ||
| cart = sr.to_cartesian_with_differentials() |
There was a problem hiding this comment.
This test would need to move to test_baseframe.
| " supported (differentials: {0})." | ||
| .format(str(rep.differentials))) | ||
|
|
||
| if isinstance(fromcoord.data, UnitSphericalRepresentation): |
There was a problem hiding this comment.
Are we sure that this can only happen without offset? Or would it make sense to test that?
(I guess since any offset would be guaranteed to have units, while a unitspherical representation does not, this would already error, so probably no action needed)
| if isinstance(fromcoord.data, UnitSphericalRepresentation): | ||
| # need to special-case this because otherwise the new class will | ||
| # think it has a valid distance | ||
| diff_class = dict([(k, diff.__class__) |
There was a problem hiding this comment.
I'm confused by the differentials needing to be transformed if the class stays the same -- I think this should it have been an iteration over fromcoord.data.differentials.items(), no?
| .format(str(rep.differentials))) | ||
|
|
||
| if isinstance(fromcoord.data, UnitSphericalRepresentation): | ||
| # need to special-case this because otherwise the new class will |
There was a problem hiding this comment.
I'm actually confused - why would there be confusion?
More generally, should we always transform back to whatever the input representation and differential were? Or is it sometimes not wanted? ... looking further down, I guess the idea is that there's no need to convert to, say, Spherical, until someone actually wants something with the coordinates. So, this is just so one doesn't have to check the unit on the cartesian representation to know whether it is unitspherical. I almost wonder if we should have a little "marker" on cartesian... Anyway, I think I understand it now, so if the above is indeed correct, no action needed (except if you think you can clarify the comment further).
docs/coordinates/frames.rst
Outdated
|
|
||
| A transformation that includes a linear matrix operation and a translation | ||
| (vector offset). These transformations are defined by a 3x3 matrix and a | ||
| 3-vector for the offset. The transformation is applied to the Cartesian |
There was a problem hiding this comment.
Maybe add "3-vector for the offset (supplied as a cartesian representation)" -- not very important, though.
docs/coordinates/frames.rst
Outdated
| transforming the Cartesian representation of one frame into the | ||
| target frame's Cartesian representation. The static version is for | ||
| The matrix transforms are `~astropy.coordinates.AffineTransform`'s without | ||
| a translation, e.g., a rotation. The static version is for |
|
Very nice! I'm toast, though, and have to get up early, so will leave it at this for tonight. |
|
p.s. Just to be clear, my only real request for change is to go with |
|
@mhvk @eteq In implementing
|
|
@adrn - on your points:
|
astropy/coordinates/baseframe.py
Outdated
| n_diffs = len(self.data.differentials) | ||
| has_diffs = n_diffs > 0 | ||
|
|
||
| if isinstance(new_representation, six.string_types): |
There was a problem hiding this comment.
I don't think you want the if statement. _get_repr_cls will work for classes and strings
| 'represent_as() only accepts a new ' | ||
| 'representation class.') | ||
|
|
||
| if isinstance(new_differential, six.string_types): |
There was a problem hiding this comment.
As above, if statement not needed?
There was a problem hiding this comment.
Here it is needed because new_differential might be a dict. That is, when we pass this to BaseRepresentation.represent_as, it has to be either a class or a dict
|
|
Cool and agreed, working on this logic now. |
|
Yes, indeed, taking But perhaps the answer is to have a configuration parameter that would allow unitspherical=inf.distance to happen. I think that, by default, this should be Obviously, I may be too paranoid, but ideally we don't help people make silly mistakes -- while ignoring distances for observation planning is probably fine, anything to go with, say, galactic motion is not. |
…on when repr subclass overwrites represent_as
…various differentials
…hericalrepresentation
…sformation code to handle unit reprs
…h a unit representation
…arious differentials
…uddled so not reverting...
41d33d6 to
f39e2f1
Compare
eteq
left a comment
There was a problem hiding this comment.
Looking basically there! All minor doc (or PEP8-y) comments except the one about the RadialDifferential
astropy/coordinates/baseframe.py
Outdated
| particular to this frame | ||
|
|
||
| new_differential : subclass of `~astropy.coordinates.BaseDifferential`, str, optional | ||
| Class in which the differential should be represented. May be |
There was a problem hiding this comment.
Oops – copy-pasted from above, will fix both.
astropy/coordinates/baseframe.py
Outdated
| @property | ||
| def sphericalcoslat(self): | ||
| """ | ||
| Shorthand for a spherical representation of the coordinates in this object. |
There was a problem hiding this comment.
Copy-pasted from spherical, fixed that as well.
Also fixed a bug I found in sphericalcoslat and will add a test in the next PR, but I realized it won't actually work in this PR because of some code that needs to be in the frame initializer...
| class BaseAffineTransform(CoordinateTransform): | ||
| """ | ||
| Base class for common functionality between the ``AffineTransform``-type | ||
| subclasses. |
There was a problem hiding this comment.
Maybe add some clarification here for why there needs to be a base? (As opposed to just having AffineTransform) I can imagine someone wanting to subclass being confused which to subclass from...
| Parameters | ||
| ---------- | ||
| transform_func : callable | ||
| A callable that has the signature ``transform_func(fromcoord, toframe)`` |
There was a problem hiding this comment.
My editor says this is 80 chars
There was a problem hiding this comment.
Oh, weird. I could have sworn this was 81, but I guess not...
| and returns a length-2 tuple that contains: [0] a (3, 3) matrix that | ||
| converts ``fromcoord`` in a cartesian representation to the new | ||
| coordinate system, and [1] a (N, 3) set of vectors that contain | ||
| offsets for differentials (or None). |
There was a problem hiding this comment.
Maybe you could add a notes section with a toy example of this functions call/return values (implementation itself not needed although it might be clearer)? I understand it from thinking about it for a while but just reading this description its hard to understand that you mean basically M, (origin, velocity_offset) =transform_func(fromcoord, toframe) (but with an option for more differentials)
There was a problem hiding this comment.
Oops - this docstring was old. I updated and tried to clarify.
| # having a RadialDifferential | ||
| if has_velocity and rad_vel_diff: | ||
| newrep = newrep.represent_as(fromcoord.data.__class__) | ||
| newrep = newrep.with_differentials({'s': fromcoord.data.differentials['s']}) |
There was a problem hiding this comment.
This is confusing to me. If I rotate a RadialDifferential it shouldn't be the same differential again, right? In the extreme case of a 90 degree rotation it should be 0?
There was a problem hiding this comment.
Not a spherical rotation with the same origin. Transforming from ICRS to Galactic coordinates doesn't change a radial velocity, right?
There was a problem hiding this comment.
I take it back! I was thinking about the wrong kind of "radial" here. This is fine, as-you-were
|
Tests passing locally so I canceled the CI runs. Tests from previous commit were all clear, except stalled remote-data tests. |
|
LGTM now, @adrn - thanks! |
|
Thanks for the clarification! |
|
OK, while @mhvk is still "requesting changes", I'm pretty sure that's a hold-over and not intended based on the comment that it looks good pending the one change, which @adrn did indeed make. So I'm going to merge this to make rebasing others easier, but @mhvk, if you had a concern here that I missed, we can still entertain that between now and release! |
|
Thanks @adrn for all the work on this, and @mhvk and @StuartLittlefair for the reviews! It's really good to see this many eyes on this stuff. |
This is the 2nd of several PR's that add support for velocities in
astropy.coordinates.The main feature of this PR is to add a new
CoordinateTransformclass "AffineTransform" for handling rotations plus translations.Next in the PR chain is #6219