Skip to content

Fix for the annoying skyoffset origin bugs - #11730

Merged
adrn merged 4 commits into
astropy:mainfrom
mhvk:coordinate-skyoffset-origin-bugs
May 24, 2021
Merged

adrn merged 4 commits into
astropy:mainfrom
mhvk:coordinate-skyoffset-origin-bugs

Conversation

@mhvk

@mhvk mhvk commented May 11, 2021

Copy link
Copy Markdown
Contributor

Fixes #11277!

It turned out there were two separate bugs: one that the origin validation cannot generally be done, since it varies by SkyOffsetFrame class. For that, I ended up just not propagating 'origin' to SkyCoord. After all, it is not like the other atttributes, which can still be used in a future transformation.

But it turned out 'origin' was also set on the SkyCoord at the end of a transformation (where for reasons we need to fix, the whole initialization gets redone even though everything is perfectly organized already). This was because of a bug in the argument parser, which led to frame attributes to be needlessly set on the SkyCoord.

@eteq - am hoping this can still be backported...

@mhvk

mhvk commented May 11, 2021

Copy link
Copy Markdown
Contributor Author

Am a bit unsure about the milestone - was this a bug in 4.0 as well?

Also, will add a changelog entry once the tests have run.

from astropy.tests.helper import assert_quantity_allclose as assert_allclose


def test_altaz_attribute_transforms():

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The large diff here is just because I swapped test_altaz and test_icrs as that made the bug appear much more reliably. There is no change in code.

@pllim
pllim requested review from adrn and eteq May 11, 2021 20:42
# See gh-11277.
# TODO: Should it be a property of the frame attribute that it can
# or cannot be stored on a SkyCoord?
frame_kwargs.pop('origin', None)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Does this means that SkyCoord(SkyOffsetFrame(...)).origin no longer works?

@nstarman nstarman May 16, 2021 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

#11730 (comment) Answers ↑.

Followup: given that users can make their own frames with arbitrary frame attributes, perhaps this special-casing of "origin" should be further special-cased by first checking if the 'from'-frame is SkyOffsetFrame

Suggested change
frame_kwargs.pop('origin', None)
if self.frame.__class__ is SkyOffsetFrame:
frame_kwargs.pop('origin', None)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

You are right. I never liked this somewhat arbitrary popping of a name - really, SkyOffsetFrame itself should somehow tell that its attribute should not be propagated.

@mhvk

mhvk commented May 16, 2021

Copy link
Copy Markdown
Contributor Author

@nstarman - let me take your question out of the code comments, since it is important:

Does this means that SkyCoord(SkyOffsetFrame(...)).origin no longer works?

I checked and, no, the regular usage works:

sc = SkyCoord(1*u.deg, 1*u.deg, frame=SkyOffsetFrame(origin=SkyCoord(10.*u.deg, 20*u.deg)))
SkyCoord(sc).origin
<ICRS Coordinate: (ra, dec) in deg
    (10., 20.)>

What will fail, though, is

sc_icrs = sc.icrs
sc_icrs.origin
# None

This would seem OK - there is no particular use to still know what the origin is once you have transformed away from it. The reason it is there for other attributes like location, it to make it easy to, e.g., transform back to 'altaz'. But it seems less logical that there is a way to allow one to transform back to an offset frame by passing in an explicit SkyOffsetFrame instance.

Though, this does make me realize this is an API change: what worked before but is now impossible is,

sc_icrs.transform_to('skyoffseticrs')

I think to keep that, we'll need to distinguish between different origin - in the current scheme, that means they will have to have a different name, which seems like an even bigger API break.

@nstarman

nstarman commented May 16, 2021 •

Copy link
Copy Markdown
Member

I checked and, no, the regular usage works:

👍.

What will fail, though, is

sc_icrs = sc.icrs
sc_icrs.origin

This would seem OK - there is no particular use to still know what the origin is once you have transformed away from it.

Agreed. Especially since one can transform from one sky-offset to another.

Though, this does make me realize this is an API change: what worked before but is now impossible is,

sc_icrs.transform_to('skyoffseticrs')

Wow. That's dangerous and seems a good thing to deprecate. As there can be many different skyoffset frames defined, the following would be super confusing

sc1 = SkyCoord(1*u.deg, 1*u.deg, frame=SkyOffsetFrame(origin=SkyCoord(10.*u.deg, 20*u.deg)))
sc2 = SkyCoord(1*u.deg, 1*u.deg, frame=SkyOffsetFrame(origin=SkyCoord(-10.*u.deg, -20*u.deg)))

sc1_icrs = sc1.icrs
sc2_icrs = sc2.icrs

sc1_icrs.transform_to('skyoffseticrs')  # reverts to SOF1
sc2_icrs.transform_to('skyoffseticrs')  # reverts to SOF2

# or even worse
sc3 = sc1.transform_to(sc2.frame)
sc3_icrs  = sc3.icrs
sc3_icrs.transform_to('skyoffseticrs')  # it goes back to SOF2, even though it's sc1 coordinates.

I think to keep that, we'll need to distinguish between different origin - in the current scheme, that means they will have to have a different name, which seems like an even bigger API break.

Perhaps SkyOffsetFrame could have a kwarg "name" to register in frames. Unnamed SkyOffsetFrames cannot be referred to by a string.

namedframe = SkyOffsetFrame(origin=SkyCoord(10.*u.deg, 20*u.deg), name="that thing")
sc_icrs.transform_to("that thing") == sc_icrs.transform_to(namedframe)

@adrn adrn left a comment •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This looks good as a bugfix! I think in the long term, as discussed by @mhvk and @nstarman, we should add support to Attribute to allow a defined attribute to tell the transform machinery not to propagate it through a frame change. For example, maybe it would be sufficient to add a propagate=False argument to Attribute?

Note: see #11782 to track the general case.

@adrn

adrn commented May 24, 2021

Copy link
Copy Markdown
Member

Ah, but this does need a changelog entry!

mhvk added 4 commits May 24, 2021 13:03
This was promised to be the case in _parse_coordinate_arg, but
actually did not happen.
This causes problems with validation, since origin can be defined on
different SkyOffsetFrame classes, which have different rules for what
is valid.  But it also makes little sense to carry it around, as it
is not an attribute that can be used to set defaults for a transformation.
@mhvk
mhvk force-pushed the coordinate-skyoffset-origin-bugs branch from 1d5f5bc to aeb422c Compare May 24, 2021 17:04
@mhvk

mhvk commented May 24, 2021

Copy link
Copy Markdown
Contributor Author

Oops, now added an entry. Running tests again just in case.

@adrn
adrn merged commit e9f732a into astropy:main May 24, 2021
@adrn

adrn commented May 24, 2021

Copy link
Copy Markdown
Member

Thanks @mhvk! Note @eteq - would be great to get this into 4.3 if possible (this is a bugfix)

@mhvk
mhvk deleted the coordinate-skyoffset-origin-bugs branch May 24, 2021 17:54
eteq pushed a commit to eteq/astropy that referenced this pull request Jun 11, 2021
…n-bugs

Fix for the annoying skyoffset origin bugs
eteq pushed a commit that referenced this pull request Jun 11, 2021
Fix for the annoying skyoffset origin bugs
@eteq

eteq commented Jun 11, 2021

Copy link
Copy Markdown
Member

backported to 4.3.x in 2cf2248

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.

Test failure with coordinates: 'NoneType' object has no attribute 'to_geodetic'

4 participants