AstropyDeprecationWarning when making a coordinate transformation that includes a frame

Hello,
I have the following piece of code where I transform coordinates from ICRS to galactocentric:

# Transformation to galactocentric coordinates
    sky_coord = coord.ICRS(ra=alpha*u.degree, dec=delta*u.degree,
                           distance=distance*u.kpc,
                           pm_ra_cosdec=mu_alpha*np.cos(delta*u.degree)*u.mas/u.yr,
                           pm_dec=mu_delta*u.mas/u.yr,
                           radial_velocity=v_los*u.km/u.s)
    galcen_distance = r_sun*u.kpc
    v_sun = coord.CartesianDifferential([11.1, v_circ_sun+12.24, 7.25]*u.km/u.s)
    z_sun = 0.0*u.kpc
    frame = coord.Galactocentric(galcen_distance=galcen_distance,   galcen_v_sun=v_sun, z_sun=z_sun)
    galac_coord = sky_coord.transform_to(frame)

When running I obtain the following warning:

WARNING: AstropyDeprecationWarning: Transforming a frame instance to a frame class (as opposed to another frame instance) will not be supported in the future.  Either explicitly instantiate the target frame, or first convert the source frame instance to a `astropy.coordinates.SkyCoord` and use its `transform_to()` method. [astropy.coordinates.baseframe]

I can’t undertand the explanation. I would like to know which modifications should I make to my piece of code in order to solve that warning.
Thank you very much in advance.

Can you double-check that this code exactly as you have shared is emitting this warning? The normal way to see this warning is if you had instead typed:

galac_coord = sky_coord.transform_to(coord.Galactocentric)

That is, the warning would be because you were trying to transform to a frame class (coord.Galactocentric) rather than to a frame instance (coord.Galactocentric(...)). In the code you have shared, you are correctly transforming to a frame instance, so that warning shouldn’t be emitted, and on my machine it doesn’t emit that warning.

I’ll also point out that your sky_coord is not actually an instance of the SkyCoord class. This warning only appears when you are directly working with frame classes instead of using SkyCoord. Had sky_coord been a SkyCoord instance, this warning would never appear.

Thank you very much for your help. As you say, the warning arises in this two situations:

Iba_coord_ICRS = Iba_coord_GD1.transform_to(coord.ICRS)
Iba_coord_GD1 = Iba_coord_ICRS.transform_to(GD1_class.GD1Koposov10)

How could I previously instantiate each of them? The GD1_class is here:

class GD1Koposov10(coord.BaseCoordinateFrame):
    """
    A Heliocentric spherical coordinate system defined by the orbit of the GD1 stream.

    As described in Koposov et al. 2010 (see: `<http://arxiv.org/abs/0907.1085>`_).
    For more information about this class, see the Astropy documentation
    on coordinate frames in :mod:`~astropy.coordinates`.

    Parameters
    ----------
    representation : :class:`~astropy.coordinates.BaseRepresentation` or None
        A representation object or None to have no data (or use the other keywords)

    phi1 : angle_like, optional, must be keyword
        The longitude-like angle corresponding to GD-1's orbit.
    phi2 : angle_like, optional, must be keyword
        The latitude-like angle corresponding to GD-1's orbit.
    distance : :class:`~astropy.units.Quantity`, optional, must be keyword
        The Distance for this object along the line-of-sight.

    pm_phi1_cosphi2 : :class:`~astropy.units.Quantity`, optional, must be keyword
        The proper motion in the longitude-like direction corresponding to
        the GD-1 stream's orbit.
    pm_phi2 : :class:`~astropy.units.Quantity`, optional, must be keyword
        The proper motion in the latitude-like direction perpendicular to the
        GD-1 stream's orbit.
    radial_velocity : :class:`~astropy.units.Quantity`, optional, must be keyword
        The Distance for this object along the line-of-sight.

    """

    default_representation = coord.SphericalRepresentation
    default_differential = coord.SphericalCosLatDifferential

    frame_specific_representation_info = {
        coord.SphericalRepresentation: [
            coord.RepresentationMapping('lon', 'phi1'),
            coord.RepresentationMapping('lat', 'phi2'),
            coord.RepresentationMapping('distance', 'distance')],
    }

    _default_wrap_angle = 180*u.deg

    def __init__(self, *args, **kwargs):
        """Init."""
        wrap = kwargs.pop('wrap_longitude', True)
        super().__init__(*args, **kwargs)
        if wrap and isinstance(self._data, (coord.UnitSphericalRepresentation,
                                            coord.SphericalRepresentation)):
            self._data.lon.wrap_angle = self._default_wrap_angle

    # TODO: remove this. This is a hack required as of astropy v3.1 in order
    # to have the longitude components wrap at the desired angle

    def represent_as(self, base, s='base', in_frame_units=False):
        """Represent as."""
        r = super().represent_as(base, s=s, in_frame_units=in_frame_units)
        r.lon.wrap_angle = self._default_wrap_angle
        return r


# Rotation matrix as defined in the Appendix of Koposov et al. (2010)
R = np.array([[-0.4776303088, -0.1738432154, 0.8611897727],
              [0.510844589, -0.8524449229, 0.111245042],
              [0.7147776536, 0.4930681392, 0.4959603976]])


@frame_transform_graph.transform(coord.StaticMatrixTransform, coord.ICRS,
                                 GD1Koposov10)
def icrs_to_gd1():
    """Compute the transformation from Galactic spherical to heliocentric GD1 coordinates."""
    return R


@frame_transform_graph.transform(coord.StaticMatrixTransform, GD1Koposov10,
                                 coord.ICRS)
def gd1_to_icrs():
    """Compute the transformation from heliocentric GD1 coordinates to spherical Galactic."""
    return matrix_transpose(icrs_to_gd1())


# TODO: remove this in next version
class GD1(GD1Koposov10):
    """GD-1 class."""

    def __init__(self, *args, **kwargs):
        """Init GD-1 class."""
        import warnings
        warnings.warn("This frame is deprecated. Use GD1Koposov10 instead.",
                      DeprecationWarning)
        super().__init__(*args, **kwargs)


trans = frame_transform_graph.get_transform(GD1Koposov10,
                                            coord.ICRS).transforms[0]
frame_transform_graph.add_transform(GD1, coord.ICRS, trans)
trans = frame_transform_graph.get_transform(coord.ICRS,
                                            GD1Koposov10).transforms[0]
frame_transform_graph.add_transform(coord.ICRS, GD1, trans)

When I did :

sky_coord = coord.ICRS(ra=alpha*u.degree, dec=delta*u.degree,
                           distance=distance*u.kpc,
                           pm_ra_cosdec=mu_alpha*np.cos(delta*u.degree)*u.mas/u.yr,
                           pm_dec=mu_delta*u.mas/u.yr,
                           radial_velocity=v_los*u.km/u.s)

did I instantiate the coord.ICRS class?

How would my example be using SkyCoord ?
Thank you very much @ayshih !

Thanks @ayshih, I made a long answer to your question with further questions and my post was removed by Akismet. I will wait the system to bring it back. Otherwise I will write again.

I restored the message. Sorry for the trouble!

1 Like

In the case of ICRS, and your GD1Koposov10, all you need to instantiate them is to add empty parentheses, e.g.:

Iba_coord_ICRS = Iba_coord_GD1.transform_to(coord.ICRS())
Iba_coord_GD1 = Iba_coord_ICRS.transform_to(GD1_class.GD1Koposov10())

The need to pass in an instance rather than a class may appear silly for these two classes in particular, but that’s because they do not have any frame attributes (e.g., obstime). When a frame has frame attributes, the transform_to() call wants the target frame to be a frame instance so that those frame attributes have actual values (which could be default values).

Yup

There are several ways to create the SkyCoord instance. You could create it much like you create the frame instance:

sky_coord = coord.SkyCoord(ra=..., ..., frame=coord.ICRS)

or if you already have the frame instance, you can feed that directly into the SkyCoord constructor:

sky_coord = coord.SkyCoord(sky_coord)

In your problematic lines, if Iba_coord_GD1 and Iba_coord_ICRS are SkyCoord instances, then the two lines will not emit a warning, even without the parentheses. That’s because SkyCoord.transform_to() makes assumptions for the values of frame attributes for the target frame if only a frame class is provided. In contrast, BaseCoordinateFrame.transform_to() wants the user to be explicit because it is intended as a lower-level call.

If you want to read a little more about when/why this was changed, see here: Modifications to transforming frames with data as opposed to SkyCoord by ayshih · Pull Request #10475 · astropy/astropy · GitHub

1 Like

Thank you very much for the patience and detailed explanation!
I will read the reference.
All the best.

Hi @ayshih, sorry for more related questions.

1- I would like to know if there exist a frame class or a frame is only a particular instantiation of a coordinate class (e.g. coord.galactocentric) where no coordinates are given ?

2- Could you tell me how to define a frame using SkyCoord class ?

3- The objects instantiated using coord.ICRS(…) or SkCoord(…,frame=coord.ICRS) are exactly the same or have different properties?

4- Do you recomend to use SkyCoord or plain coord.ICRS ?

Thank you very much in advance.

I use the terms “frame class”/“frame instance” regardless of whether coordinate data is included. That is, ICRS() is a frame instance without coordinate data, and ICRS(1*u.deg, 2*u.deg) is a frame instance with coordinate data. Note that the target of a transform_to() call can be a frame instance with coordinate data; the coordinate data is simply ignored in that case.

I’m not clear what you mean by “define a frame”. If you mean create a SkyCoord instance for a particular frame and coordinate data, I already gave you examples above. If you mean create a SkyCoord instance without any coordinate data, that’s not possible; unlike the frame classes, a SkyCoord instance must have coordinate data. If you mean define a frame class, that’s not what the SkyCoord class is for. Your GF1Koposov10 class looks to be defined fine, as a subclass of BaseCoordinateFrame.

They do not instantiate the same object. The SkyCoord instance has more functionality than the ICRS instance. I’ll point out that if you have a SkyCoord instance, you can “downgrade” it to a frame instance by accessing the .frame property.

You should use SkyCoord unless you have some reason to intentionally avoid some of the additional logic that SkyCoord provides. SkyCoord is the higher-level interface to using the frame classes directly.

Thank you very much @ayshih ! Now everything clear. I had misunderstood some concepts. Cheers!