Example #1
0
    def __init__(self, input_system, output_system):
        super(CoordinateTransform, self).__init__()
        self._input_system_name = input_system
        self._output_system_name = output_system

        if isinstance(self._input_system_name, WCS):
            self.input_system = wcs_to_celestial_frame(self._input_system_name)
        elif isinstance(self._input_system_name, six.string_types):
            self.input_system = frame_transform_graph.lookup_name(self._input_system_name)
            if self.input_system is None:
                raise ValueError("Frame {0} not found".format(self._input_system_name))
        elif isinstance(self._input_system_name, BaseCoordinateFrame):
            self.input_system = self._input_system_name
        else:
            raise TypeError("input_system should be a WCS instance, string, or a coordinate frame instance")

        if isinstance(self._output_system_name, WCS):
            self.output_system = wcs_to_celestial_frame(self._output_system_name)
        elif isinstance(self._output_system_name, six.string_types):
            self.output_system = frame_transform_graph.lookup_name(self._output_system_name)
            if self.output_system is None:
                raise ValueError("Frame {0} not found".format(self._output_system_name))
        elif isinstance(self._output_system_name, BaseCoordinateFrame):
            self.output_system = self._output_system_name
        else:
            raise TypeError("output_system should be a WCS instance, string, or a coordinate frame instance")

        if self.output_system == self.input_system:
            self.same_frames = True
        else:
            self.same_frames = False
Example #2
0
def test_crop_rotated_celestial(ndcube_4d_ln_lt_l_t):
    # This is a regression test for a highly rotated image where all 4 corners
    # of the spatial ROI have to be used.

    header = dedent("""\
        WCSAXES =                    2 / Number of coordinate axes
        CRPIX1  =          2053.459961 / Pixel coordinate of reference point
        CRPIX2  =          2047.880005 / Pixel coordinate of reference point
        PC1_1   =     0.70734471922412 / Coordinate transformation matrix element
        PC1_2   =     0.70686876305701 / Coordinate transformation matrix element
        PC2_1   =    -0.70686876305701 / Coordinate transformation matrix element
        PC2_2   =     0.70734471922412 / Coordinate transformation matrix element
        CDELT1  =  0.00016652472222222 / [deg] Coordinate increment at reference point
        CDELT2  =  0.00016652472222222 / [deg] Coordinate increment at reference point
        CUNIT1  = 'deg'                / Units of coordinate increment and value
        CUNIT2  = 'deg'                / Units of coordinate increment and value
        CTYPE1  = 'HPLN-TAN'           / Coordinate type codegnomonic projection
        CTYPE2  = 'HPLT-TAN'           / Coordinate type codegnomonic projection
        CRVAL1  =                  0.0 / [deg] Coordinate value at reference point
        CRVAL2  =                  0.0 / [deg] Coordinate value at reference point
        LONPOLE =                180.0 / [deg] Native longitude of celestial pole
        LATPOLE =                  0.0 / [deg] Native latitude of celestial pole
        MJDREF  =                  0.0 / [d] MJD of fiducial time
        DATE-OBS= '2014-04-09T06:00:12.970' / ISO-8601 time of observation
        MJD-OBS =      56756.250150116 / [d] MJD of observation
        RSUN_REF=          696000000.0 / [m] Solar radius
        DSUN_OBS=      149860273889.04 / [m] Distance from centre of Sun to observer
        HGLN_OBS=  -0.0058904803279347 / [deg] Stonyhurst heliographic lng of observer
        HGLT_OBS=     -6.0489216362492 / [deg] Heliographic latitude of observer
        """)
    wcs = WCS(fits.Header.fromstring(header, sep="\n"))
    data = np.zeros((4096, 4096))

    cube = NDCube(data, wcs=wcs)

    bottom_left = SkyCoord(-100,
                           -100,
                           unit=u.arcsec,
                           frame=wcs_to_celestial_frame(wcs))
    bottom_right = SkyCoord(600,
                            -100,
                            unit=u.arcsec,
                            frame=wcs_to_celestial_frame(wcs))
    top_left = SkyCoord(-100,
                        600,
                        unit=u.arcsec,
                        frame=wcs_to_celestial_frame(wcs))
    top_right = SkyCoord(600,
                         600,
                         unit=u.arcsec,
                         frame=wcs_to_celestial_frame(wcs))

    small = cube.crop(bottom_left, bottom_right, top_left, top_right)

    assert small.data.shape == (1652, 1652)
Example #3
0
def _convert_wcs(lon_in, lat_in, frame_in, frame_out):
    """Convert (longitude, latitude) coordinates from the input frame to
    the specified output frame.

    Parameters
    ----------
    lon_in : 1D `~numpy.ndarray`
        The longitude to convert, unit degree, [0, 360)
    lat_in : 1D `~numpy.ndarray`
        The latitude to convert, unit degree, [-90, 90]
    frame_in, frame_out : tuple or `~astropy.wcs.WCS`
        The input and output frames, which can be passed either as a tuple of
        ``(frame, lon_unit, lat_unit)`` or as a `~astropy.wcs.WCS` instance.

    Returns
    -------
    lon_out, lat_out : 1D `~numpy.ndarray`
        Output longitude and latitude in the output frame

    References
    ----------
    [1] reproject - wcs_utils.convert_world_coordinates()
        https://github.com/astrofrog/reproject
    """
    if isinstance(frame_in, WCS):
        coordframe_in = wcs_to_celestial_frame(frame_in)
        lon_in_unit = au.Unit(frame_in.wcs.cunit[0])
        lat_in_unit = au.Unit(frame_in.wcs.cunit[1])
    else:
        coordframe_in, lon_in_unit, lat_in_unit = frame_in
    #
    if isinstance(frame_out, WCS):
        coordframe_out = wcs_to_celestial_frame(frame_out)
        lon_out_unit = au.Unit(frame_out.wcs.cunit[0])
        lat_out_unit = au.Unit(frame_out.wcs.cunit[1])
    else:
        coordframe_out, lon_out_unit, lat_out_unit = frame_out
    #
    logger.info("Convert coordinates from {0} to {1}".format(
        coordframe_in, coordframe_out))
    logger.info("Input coordinates units: "
                "{0} (longitude), {1} (latitude)".format(
                    lon_in_unit, lat_in_unit))
    logger.info("Output coordinates units: "
                "{0} (longitude), {1} (latitude)".format(
                    lon_out_unit, lat_out_unit))
    #
    data = UnitSphericalRepresentation(lon_in * lon_in_unit,
                                       lat_in * lat_in_unit)
    coords_in = coordframe_in.realize_frame(data)
    coords_out = coords_in.transform_to(coordframe_out)
    data_out = coords_out.represent_as("unitspherical")
    lon_out = data_out.lon.to(lon_out_unit).value
    lat_out = data_out.lat.to(lon_out_unit).value
    return lon_out, lat_out
Example #4
0
    def _get_transform_no_transdata(self, frame):
        """
        Return a transform from data to the specified frame
        """

        if self.wcs is None and frame != 'pixel':
            raise ValueError(
                'No WCS specified, so only pixel coordinates are available')

        if isinstance(frame, WCS):

            coord_in = wcs_to_celestial_frame(self.wcs)
            coord_out = wcs_to_celestial_frame(frame)

            if coord_in == coord_out:

                return (WCSPixel2WorldTransform(self.wcs, slice=self.slices) +
                        WCSWorld2PixelTransform(frame))

            else:

                return (WCSPixel2WorldTransform(self.wcs, slice=self.slices) +
                        CoordinateTransform(self.wcs, frame) +
                        WCSWorld2PixelTransform(frame))

        elif frame == 'pixel':

            return Affine2D()

        elif isinstance(frame, Transform):

            pixel2world = WCSPixel2WorldTransform(self.wcs, slice=self.slices)

            return pixel2world + frame

        else:

            pixel2world = WCSPixel2WorldTransform(self.wcs, slice=self.slices)

            if frame == 'world':

                return pixel2world

            else:
                coordinate_transform = CoordinateTransform(self.wcs, frame)

                if coordinate_transform.same_frames:
                    return pixel2world
                else:
                    return pixel2world + CoordinateTransform(self.wcs, frame)
Example #5
0
    def _get_transform_no_transdata(self, frame):
        """
        Return a transform from data to the specified frame
        """

        if self.wcs is None and frame != 'pixel':
            raise ValueError('No WCS specified, so only pixel coordinates are available')

        if isinstance(frame, WCS):

            coord_in = wcs_to_celestial_frame(self.wcs)
            coord_out = wcs_to_celestial_frame(frame)

            if coord_in == coord_out:

                return (WCSPixel2WorldTransform(self.wcs, slice=self.slices)
                        + WCSWorld2PixelTransform(frame))

            else:

                return (WCSPixel2WorldTransform(self.wcs, slice=self.slices)
                        + CoordinateTransform(self.wcs, frame)
                        + WCSWorld2PixelTransform(frame))

        elif frame == 'pixel':

            return Affine2D()

        elif isinstance(frame, Transform):

            pixel2world = WCSPixel2WorldTransform(self.wcs, slice=self.slices)

            return pixel2world + frame

        else:

            pixel2world = WCSPixel2WorldTransform(self.wcs, slice=self.slices)

            if frame == 'world':

                return pixel2world

            else:
                coordinate_transform = CoordinateTransform(self.wcs, frame)

                if coordinate_transform.same_frames:
                    return pixel2world
                else:
                    return pixel2world + CoordinateTransform(self.wcs, frame)
Example #6
0
def convert_world_coordinates(lon_in, lat_in, wcs_in, wcs_out):
    """
    Convert longitude/latitude coordinates from an input frame to an output
    frame.

    Parameters
    ----------
    lon_in, lat_in : `~numpy.ndarray`
        The longitude and latitude to convert
    wcs_in, wcs_out : tuple or `~astropy.wcs.WCS`
        The input and output frames, which can be passed either as a tuple of
        ``(frame, lon_unit, lat_unit)`` or as a `~astropy.wcs.WCS` instance.

    Returns
    -------
    lon_out, lat_out : `~numpy.ndarray`
        The output longitude and latitude
    """

    if isinstance(wcs_in, WCS):
        # Extract the celestial component of the WCS in (lon, lat) order
        wcs_in = wcs_in.celestial
        frame_in = wcs_to_celestial_frame(wcs_in)
        lon_in_unit = u.Unit(wcs_in.wcs.cunit[0])
        lat_in_unit = u.Unit(wcs_in.wcs.cunit[1])
    else:
        frame_in, lon_in_unit, lat_in_unit = wcs_in

    if isinstance(wcs_out, WCS):
        # Extract the celestial component of the WCS in (lon, lat) order
        wcs_out = wcs_out.celestial
        frame_out = wcs_to_celestial_frame(wcs_out)
        lon_out_unit = u.Unit(wcs_out.wcs.cunit[0])
        lat_out_unit = u.Unit(wcs_out.wcs.cunit[1])
    else:
        frame_out, lon_out_unit, lat_out_unit = wcs_out

    data = UnitSphericalRepresentation(lon_in * lon_in_unit,
                                       lat_in * lat_in_unit)

    coords_in = frame_in.realize_frame(data)
    coords_out = coords_in.transform_to(frame_out)

    lon_out = coords_out.represent_as('unitspherical').lon.to(
        lon_out_unit).value
    lat_out = coords_out.represent_as('unitspherical').lat.to(
        lat_out_unit).value

    return lon_out, lat_out
Example #7
0
    def __init__(self,
                 wcs,
                 npix,
                 cdelt=None,
                 crpix=None,
                 axes=None,
                 cutout_info=None):
        self._wcs = wcs
        self._frame = wcs_to_celestial_frame(wcs).name
        self._projection = wcs.wcs.ctype[0][5:]
        self._axes = MapAxes.from_default(axes)

        if cdelt is None:
            cdelt = tuple(np.abs(self.wcs.wcs.cdelt))

        # Shape to use for WCS transformations
        wcs_shape = max([get_shape(t) for t in [npix, cdelt]])
        self._npix = cast_to_shape(npix, wcs_shape, int)
        self._cdelt = cast_to_shape(cdelt, wcs_shape, float)

        # By convention CRPIX is indexed from 1
        if crpix is None:
            crpix = tuple(1.0 + (np.array(self._npix) - 1.0) / 2.0)

        self._crpix = crpix
        self._cutout_info = cutout_info

        # define cached methods
        self.get_coord = lru_cache()(self.get_coord)
        self.get_pix = lru_cache()(self.get_pix)
        self.solid_angle = lru_cache()(self.solid_angle)
        self.bin_volume = lru_cache()(self.bin_volume)
        self.to_image = lru_cache()(self.to_image)
Example #8
0
    def get_xy(self, wcs=None):
        """
        Return the pixel coordinates of the path.

        If the path is defined in world coordinates, the appropriate WCS
        transformation should be passed.

        Parameters
        ----------
        wcs : :class:`~astropy.wcs.WCS`
            The WCS transformation to assume in order to transform the path
            to pixel coordinates.
        """
        if self._xy is not None:
            return self._xy
        else:
            if wcs is None:
                raise ValueError("`wcs` is needed in order to compute "
                                 "the pixel coordinates")
            else:

                # Extract the celestial component of the WCS
                wcs_sky = wcs.sub([WCSSUB_CELESTIAL])

                # Find the astropy name for the coordinates
                celestial_system = wcs_to_celestial_frame(wcs_sky)

                world_coords = self._coords.transform_to(celestial_system)

                xw, yw = world_coords.spherical.lon.degree, world_coords.spherical.lat.degree

                return list(zip(*wcs_sky.wcs_world2pix(xw, yw, 0)))
Example #9
0
    def _set_data_coord_system(self, data):
        """
        Check if data coordinates are in
        RA-DEC first. Then set viewers to
        the default coordinate system.
        :param data: input data
        """
        is_ra_dec = isinstance(wcs_to_celestial_frame(data.coords.wcs),
                               BaseRADecFrame)
        self.ra_dec_format_menu.setDisabled(not is_ra_dec)
        if not is_ra_dec:
            return

        is_coords_in_degrees = False
        for view in self.cube_views:
            viewer = view.widget()
            viewer.init_ra_dec()
            is_coords_in_degrees = viewer._coords_in_degrees

        if is_coords_in_degrees:
            format_name = "Decimal Degrees"
        else:
            format_name = "Sexagesimal"

        menu = self.ra_dec_format_menu
        for action in menu.actions():
            if format_name == action.text():
                action.setChecked(True)
                break
Example #10
0
    def _set_data_coord_system(self, data):
        """
        Check if data coordinates are in
        RA-DEC first. Then set viewers to
        the default coordinate system.
        :param data: input data
        """
        is_ra_dec = isinstance(wcs_to_celestial_frame(data.coords.wcs),
                               BaseRADecFrame)
        self.ra_dec_format_menu.setDisabled(not is_ra_dec)
        if not is_ra_dec:
            return

        is_coords_in_degrees = False
        for view in self.cube_views:
            viewer = view.widget()
            viewer.init_ra_dec()
            is_coords_in_degrees = viewer._coords_in_degrees

        if is_coords_in_degrees:
            format_name = "Decimal Degrees"
        else:
            format_name = "Sexagesimal"

        menu = self.ra_dec_format_menu
        for action in menu.actions():
            if format_name == action.text():
                action.setChecked(True)
                break
Example #11
0
    def get_xy(self, wcs=None):
        """
        Return the pixel coordinates of the path.

        If the path is defined in world coordinates, the appropriate WCS
        transformation should be passed.

        Parameters
        ----------
        wcs : :class:`~astropy.wcs.WCS`
            The WCS transformation to assume in order to transform the path
            to pixel coordinates.
        """
        if self._xy is not None:
            return self._xy
        else:
            if wcs is None:
                raise ValueError("`wcs` is needed in order to compute "
                                 "the pixel coordinates")
            else:

                # Extract the celestial component of the WCS
                wcs_sky = wcs.sub([WCSSUB_CELESTIAL])

                # Find the astropy name for the coordinates
                celestial_system = wcs_to_celestial_frame(wcs_sky)

                world_coords = self._coords.transform_to(celestial_system)

                xw, yw = world_coords.spherical.lon.degree, world_coords.spherical.lat.degree

                return list(zip(*wcs_sky.wcs_world2pix(xw, yw, 0)))
Example #12
0
 def frame(self):
     if self.region is None:
         return "icrs"
     try:
         return self.region.center.frame.name
     except AttributeError:
         return wcs_to_celestial_frame(self.wcs).name
def convert_world_coordinates(lon_in, lat_in, wcs_in, wcs_out):
    """
    Convert longitude/latitude coordinates from an input frame to an output
    frame.

    Parameters
    ----------
    lon_in, lat_in : `~numpy.ndarray`
        The longitude and latitude to convert
    wcs_in, wcs_out : tuple or `~astropy.wcs.WCS`
        The input and output frames, which can be passed either as a tuple of
        ``(frame, lon_unit, lat_unit)`` or as a `~astropy.wcs.WCS` instance.

    Returns
    -------
    lon_out, lat_out : `~numpy.ndarray`
        The output longitude and latitude
    """

    if isinstance(wcs_in, WCS):
        # Extract the celestial component of the WCS in (lon, lat) order
        wcs_in = wcs_in.celestial
        frame_in = wcs_to_celestial_frame(wcs_in)
        lon_in_unit = u.Unit(wcs_in.wcs.cunit[0])
        lat_in_unit = u.Unit(wcs_in.wcs.cunit[1])
    else:
        frame_in, lon_in_unit, lat_in_unit = wcs_in

    if isinstance(wcs_out, WCS):
        # Extract the celestial component of the WCS in (lon, lat) order
        wcs_out = wcs_out.celestial
        frame_out = wcs_to_celestial_frame(wcs_out)
        lon_out_unit = u.Unit(wcs_out.wcs.cunit[0])
        lat_out_unit = u.Unit(wcs_out.wcs.cunit[1])
    else:
        frame_out, lon_out_unit, lat_out_unit = wcs_out

    data = UnitSphericalRepresentation(lon_in * lon_in_unit, lat_in * lat_in_unit)

    coords_in = frame_in.realize_frame(data)
    coords_out = coords_in.transform_to(frame_out)

    lon_out = coords_out.represent_as("unitspherical").lon.to(lon_out_unit).value
    lat_out = coords_out.represent_as("unitspherical").lat.to(lat_out_unit).value

    return lon_out, lat_out
Example #14
0
 def xy(self):
     hpc_frame = wcs_to_celestial_frame(self.wcs)
     skycoord =  SkyCoord(self.sky_coord, frame=hpc_frame, unit=(u.hourangle, u.deg))
     ircs =  np.array([[skycoord.ra.deg, skycoord.dec.deg]])
     coords =  np.array(self.wcs.wcs_world2pix(ircs,1))
     y =   int((coords[0])[0])
     x =   int((coords[0])[1])
     return(x,y)
Example #15
0
 def __init__(self, wcs, slice=None):
     super().__init__()
     self.wcs = wcs
     self.slice = slice
     if self.slice is not None:
         self.x_index = slice.index('x')
         self.y_index = slice.index('y')
     if wcs.has_celestial:
         self.frame_out = wcs_to_celestial_frame(wcs)
Example #16
0
 def frame(self):
     """Coordinate system, either Galactic ("galactic") or Equatorial
         ("icrs")."""
     if self.region is None:
         return "icrs"
     try:
         return self.region.center.frame.name
     except AttributeError:
         return wcs_to_celestial_frame(self.wcs).name
Example #17
0
def skycoord(hdulist, a):
    hdr = hdulist[a].header
    wcs = WCS(hdr)
    hpc_frame = wcs_to_celestial_frame(wcs)
    skycoord = SkyCoord(sky_coord, frame=hpc_frame, unit=(u.hourangle, u.deg))
    ircs = np.array([[skycoord.ra.deg, skycoord.dec.deg]])
    coords = np.array(wcs.wcs_world2pix(ircs, 1))
    y = int((coords[0])[0])
    x = int((coords[0])[1])
    return (x, y, wcs)
Example #18
0
    def __init__(self, input_system, output_system):
        super().__init__()
        self._input_system_name = input_system
        self._output_system_name = output_system

        if isinstance(self._input_system_name, WCS):
            self.input_system = wcs_to_celestial_frame(self._input_system_name)
        elif isinstance(self._input_system_name, str):
            self.input_system = frame_transform_graph.lookup_name(
                self._input_system_name)
            if self.input_system is None:
                raise ValueError("Frame {0} not found".format(
                    self._input_system_name))
        elif isinstance(self._input_system_name, BaseCoordinateFrame):
            self.input_system = self._input_system_name
        else:
            raise TypeError(
                "input_system should be a WCS instance, string, or a coordinate frame instance"
            )

        if isinstance(self._output_system_name, WCS):
            self.output_system = wcs_to_celestial_frame(
                self._output_system_name)
        elif isinstance(self._output_system_name, str):
            self.output_system = frame_transform_graph.lookup_name(
                self._output_system_name)
            if self.output_system is None:
                raise ValueError("Frame {0} not found".format(
                    self._output_system_name))
        elif isinstance(self._output_system_name, BaseCoordinateFrame):
            self.output_system = self._output_system_name
        else:
            raise TypeError(
                "output_system should be a WCS instance, string, or a coordinate frame instance"
            )

        if self.output_system == self.input_system:
            self.same_frames = True
        else:
            self.same_frames = False
Example #19
0
def skycoord_to_pixel(coords, wcs):
    """
    Convert a set of SkyCoord coordinates into pixel coordinates.

    This function assumes that the coordinates are celestial.

    Parameters
    ----------
    coords : `~astropy.coordinates.SkyCoord`
        The coordinates to convert
    wcs : `~astropy.wcs.WCS`
        The WCS transformation to use

    Returns
    -------
    x, y : `~numpy.ndarray`
        The x and y pixel coordinates corresponding to the input coordinates
    """

    # TODO this should be simplified once wcs_world2pix() supports
    # SkyCoord objects as input

    # TODO: remove local wcs_to_celestial_frame once Astropy 1.0 is out
    try:
        from astropy.wcs.utils import wcs_to_celestial_frame
    except ImportError:  # Astropy < 1.0
        from .extern.wcs_utils import wcs_to_celestial_frame

    # Keep only the celestial part of the axes, also re-orders lon/lat
    wcs = wcs.sub([WCSSUB_CELESTIAL])

    if wcs.naxis != 2:
        raise ValueError("WCS should contain celestial component")

    # Check which frame the WCS uses
    frame = wcs_to_celestial_frame(wcs)

    # Check what unit the WCS needs
    xw_unit = u.Unit(wcs.wcs.cunit[0])
    yw_unit = u.Unit(wcs.wcs.cunit[1])

    # Convert positions to frame
    coords = coords.transform_to(frame)

    # Extract longitude and latitude
    lon = coords.spherical.lon.to(xw_unit)
    lat = coords.spherical.lat.to(yw_unit)

    # Convert to pixel coordinates
    xp, yp = wcs.wcs_world2pix(lon, lat, 0)

    return xp, yp
Example #20
0
def skycoord_to_pixel(coords, wcs):
    """
    Convert a set of SkyCoord coordinates into pixel coordinates.

    This function assumes that the coordinates are celestial.

    Parameters
    ----------
    coords : `~astropy.coordinates.SkyCoord`
        The coordinates to convert
    wcs : `~astropy.wcs.WCS`
        The WCS transformation to use

    Returns
    -------
    x, y : `~numpy.ndarray`
        The x and y pixel coordinates corresponding to the input coordinates
    """

    # TODO this should be simplified once wcs_world2pix() supports
    # SkyCoord objects as input

    # TODO: remove local wcs_to_celestial_frame once Astropy 1.0 is out
    try:
        from astropy.wcs.utils import wcs_to_celestial_frame
    except ImportError:  # Astropy < 1.0
        from .extern.wcs_utils import wcs_to_celestial_frame

    # Keep only the celestial part of the axes, also re-orders lon/lat
    wcs = wcs.sub([WCSSUB_CELESTIAL])

    if wcs.naxis != 2:
        raise ValueError("WCS should contain celestial component")

    # Check which frame the WCS uses
    frame = wcs_to_celestial_frame(wcs)

    # Check what unit the WCS needs
    xw_unit = u.Unit(wcs.wcs.cunit[0])
    yw_unit = u.Unit(wcs.wcs.cunit[1])

    # Convert positions to frame
    coords = coords.transform_to(frame)

    # Extract longitude and latitude
    lon = coords.spherical.lon.to(xw_unit)
    lat = coords.spherical.lat.to(yw_unit)

    # Convert to pixel coordinates
    xp, yp = wcs.wcs_world2pix(lon, lat, 0)

    return xp, yp
Example #21
0
def test_wcs_to_celestial_frame_extend():

    mywcs = WCS(naxis=2)
    mywcs.wcs.ctype = ['XOFFSET', 'YOFFSET']
    mywcs.wcs.set()
    with pytest.raises(ValueError):
        wcs_to_celestial_frame(mywcs)

    class OffsetFrame:
        pass

    def identify_offset(wcs):
        if wcs.wcs.ctype[0].endswith('OFFSET') and wcs.wcs.ctype[1].endswith('OFFSET'):
            return OffsetFrame()

    with custom_wcs_to_frame_mappings(identify_offset):
        frame = wcs_to_celestial_frame(mywcs)
    assert isinstance(frame, OffsetFrame)

    # Check that things are back to normal after the context manager
    with pytest.raises(ValueError):
        wcs_to_celestial_frame(mywcs)
Example #22
0
def test_wcs_to_celestial_frame_extend():

    mywcs = WCS(naxis=2)
    mywcs.wcs.ctype = ['XOFFSET', 'YOFFSET']
    mywcs.wcs.set()
    with pytest.raises(ValueError):
        wcs_to_celestial_frame(mywcs)

    class OffsetFrame:
        pass

    def identify_offset(wcs):
        if wcs.wcs.ctype[0].endswith('OFFSET') and wcs.wcs.ctype[1].endswith('OFFSET'):
            return OffsetFrame()

    with custom_wcs_to_frame_mappings(identify_offset):
        frame = wcs_to_celestial_frame(mywcs)
    assert isinstance(frame, OffsetFrame)

    # Check that things are back to normal after the context manager
    with pytest.raises(ValueError):
        wcs_to_celestial_frame(mywcs)
Example #23
0
    def load_header(self, header, fobj=None):
        from astropy.wcs.utils import wcs_to_celestial_frame
        # reconstruct a pyfits header, because otherwise we take an
        # incredible performance hit in astropy.wcs
        self.header = pyfits.Header(header.items())

        try:
            self.logger.debug("Trying to make astropy wcs object")
            self.wcs = pywcs.WCS(self.header, fobj=fobj, relax=True)
            self.coordframe = wcs_to_celestial_frame(self.wcs)

        except Exception as e:
            self.logger.error("Error making WCS object: %s" % (str(e)))
            self.wcs = None
Example #24
0
def test_wcs_to_celestial_frame_correlated():

    # Regression test for a bug that caused wcs_to_celestial_frame to fail when
    # the celestial axes were correlated with other axes.

    # Import astropy.coordinates here to avoid circular imports
    from astropy.coordinates.builtin_frames import ICRS

    mywcs = WCS(naxis=3)
    mywcs.wcs.ctype = 'RA---TAN', 'DEC--TAN', 'FREQ'
    mywcs.wcs.cd = np.ones((3, 3))
    mywcs.wcs.set()
    frame = wcs_to_celestial_frame(mywcs)
    assert isinstance(frame, ICRS)
Example #25
0
def test_wcs_to_celestial_frame_correlated():

    # Regression test for a bug that caused wcs_to_celestial_frame to fail when
    # the celestial axes were correlated with other axes.

    # Import astropy.coordinates here to avoid circular imports
    from astropy.coordinates.builtin_frames import ICRS

    mywcs = WCS(naxis=3)
    mywcs.wcs.ctype = 'RA---TAN', 'DEC--TAN', 'FREQ'
    mywcs.wcs.cd = np.ones((3, 3))
    mywcs.wcs.set()
    frame = wcs_to_celestial_frame(mywcs)
    assert isinstance(frame, ICRS)
Example #26
0
def convert_world_coordinates(xw_in, yw_in, wcs_in, wcs_out):
    """
    Convert world coordinates from an input frame to an output frame.

    Parameters
    ----------
    xw_in, yw_in : `~numpy.ndarray`
        The input coordinates to convert
    wcs_in, wcs_out : tuple or `~astropy.wcs.WCS`
        The input and output frames, which can be passed either as a tuple of
        ``(frame, x_unit, y_unit)`` or as a `~astropy.wcs.WCS` instance.
    """

    if isinstance(wcs_in, WCS):
        frame_in = wcs_to_celestial_frame(wcs_in)
        xw_in_unit = u.Unit(wcs_in.wcs.cunit[0])
        yw_in_unit = u.Unit(wcs_in.wcs.cunit[1])
    else:
        frame_in, xw_in_unit, yw_in_unit = wcs_in

    if isinstance(wcs_out, WCS):
        frame_out = wcs_to_celestial_frame(wcs_out)
        xw_out_unit = u.Unit(wcs_out.wcs.cunit[0])
        yw_out_unit = u.Unit(wcs_out.wcs.cunit[1])
    else:
        frame_out, xw_out_unit, yw_out_unit = wcs_out

    data = UnitSphericalRepresentation(xw_in * xw_in_unit,
                                       yw_in * yw_in_unit)

    coords_in = frame_in.realize_frame(data)
    coords_out = coords_in.transform_to(frame_out)

    xw_out = coords_out.spherical.lon.to(xw_out_unit).value
    yw_out = coords_out.spherical.lat.to(yw_out_unit).value

    return xw_out, yw_out
Example #27
0
    def _to_pixel_params(self, wcs, mode='all'):
        """
        Convert the sky aperture parameters to those for a pixel
        aperture.

        Parameters
        ----------
        wcs : `~astropy.wcs.WCS`
            The world coordinate system (WCS) transformation to use.

        mode : {'all', 'wcs'}, optional
            Whether to do the transformation including distortions
            (``'all'``; default) or only including only the core WCS
            transformation (``'wcs'``).

        Returns
        -------
        pixel_params : `dict`
            A dictionary of parameters for an equivalent pixel aperture.
        """

        pixel_params = {}
        xpos, ypos = skycoord_to_pixel(self.positions, wcs, mode=mode)
        pixel_params['positions'] = np.array([xpos, ypos]).transpose()

        # The aperture object must have a single value for each shape
        # parameter so we must use a single pixel scale for all positions.
        # Here, we define the scale at the WCS CRVAL position.
        crval = SkyCoord(*wcs.wcs.crval,
                         frame=wcs_to_celestial_frame(wcs),
                         unit=wcs.wcs.cunit)
        pixscale, angle = _pixel_scale_angle_at_skycoord(crval, wcs)

        shape_params = list(self._shape_params)

        theta_key = 'theta'
        if theta_key in shape_params:
            pixel_params[theta_key] = (self.theta + angle).to(u.radian).value
            shape_params.remove(theta_key)

        for shape_param in shape_params:
            value = getattr(self, shape_param)
            if value.unit.physical_type == 'angle':
                pixel_params[shape_param] = ((value / pixscale).to(
                    u.pixel).value)
            else:
                pixel_params[shape_param] = value.value

        return pixel_params
Example #28
0
def convert_world_coordinates(xw_in, yw_in, wcs_in, wcs_out):
    """
    Convert world coordinates from an input frame to an output frame.

    Parameters
    ----------
    xw_in, yw_in : `~numpy.ndarray`
        The input coordinates to convert
    wcs_in, wcs_out : tuple or `~astropy.wcs.WCS`
        The input and output frames, which can be passed either as a tuple of
        ``(frame, x_unit, y_unit)`` or as a `~astropy.wcs.WCS` instance.
    """

    if isinstance(wcs_in, WCS):
        frame_in = wcs_to_celestial_frame(wcs_in)
        xw_in_unit = u.Unit(wcs_in.wcs.cunit[0])
        yw_in_unit = u.Unit(wcs_in.wcs.cunit[1])
    else:
        frame_in, xw_in_unit, yw_in_unit = wcs_in

    if isinstance(wcs_out, WCS):
        frame_out = wcs_to_celestial_frame(wcs_out)
        xw_out_unit = u.Unit(wcs_out.wcs.cunit[0])
        yw_out_unit = u.Unit(wcs_out.wcs.cunit[1])
    else:
        frame_out, xw_out_unit, yw_out_unit = wcs_out

    data = UnitSphericalRepresentation(xw_in * xw_in_unit, yw_in * yw_in_unit)

    coords_in = frame_in.realize_frame(data)
    coords_out = coords_in.transform_to(frame_out)

    xw_out = coords_out.spherical.lon.to(xw_out_unit).value
    yw_out = coords_out.spherical.lat.to(yw_out_unit).value

    return xw_out, yw_out
Example #29
0
    def load_header(self, header, fobj=None):
        from astropy.wcs.utils import wcs_to_celestial_frame
        self.header = {}
        self.header.update(header.items())

        self.fix_bad_headers()

        try:
            self.logger.debug("Trying to make astropy wcs object")
            self.wcs = pywcs.WCS(self.header, fobj=fobj, relax=True)
            self.coordframe = wcs_to_celestial_frame(self.wcs)

        except Exception as e:
            self.logger.error("Error making WCS object: %s" % (str(e)))
            self.wcs = None
Example #30
0
    def _to_pixel_params(self, wcs):
        """
        Convert the sky aperture parameters to those for a pixel
        aperture.

        Parameters
        ----------
        wcs : WCS object
            A world coordinate system (WCS) transformation that
            supports the `astropy shared interface for WCS
            <https://docs.astropy.org/en/stable/wcs/wcsapi.html>`_
            (e.g., `astropy.wcs.WCS`, `gwcs.wcs.WCS`).

        Returns
        -------
        pixel_params : `dict`
            A dictionary of parameters for an equivalent pixel aperture.
        """

        pixel_params = {}

        xpos, ypos = _world_to_pixel(self.positions, wcs)
        pixel_params['positions'] = np.array([xpos, ypos]).transpose()

        # The aperture object must have a single value for each shape
        # parameter so we must use a single pixel scale for all positions.
        # Here, we define the scale at the WCS CRVAL position.
        crval = SkyCoord(*wcs.wcs.crval,
                         frame=wcs_to_celestial_frame(wcs),
                         unit=wcs.wcs.cunit)
        pixscale, angle = _pixel_scale_angle_at_skycoord(crval, wcs)

        shape_params = list(self._shape_params)

        theta_key = 'theta'
        if theta_key in shape_params:
            pixel_params[theta_key] = (self.theta + angle).to(u.radian).value
            shape_params.remove(theta_key)

        for shape_param in shape_params:
            value = getattr(self, shape_param)
            if value.unit.physical_type == 'angle':
                pixel_params[shape_param] = ((value / pixscale).to(
                    u.pixel).value)
            else:
                pixel_params[shape_param] = value.value

        return pixel_params
Example #31
0
    def _to_pixel_params(self, wcs, mode='all'):
        """
        Convert the sky aperture parameters to those for a pixel
        aperture.

        Parameters
        ----------
        wcs : `~astropy.wcs.WCS`
            The world coordinate system (WCS) transformation to use.

        mode : {'all', 'wcs'}, optional
            Whether to do the transformation including distortions
            (``'all'``; default) or only including only the core WCS
            transformation (``'wcs'``).

        Returns
        -------
        pixel_params : dict
            A dictionary of parameters for an equivalent pixel aperture.
        """

        pixel_params = {}
        x, y = skycoord_to_pixel(self.positions, wcs, mode=mode)
        pixel_params['positions'] = np.array([x, y]).transpose()

        # The aperture object must have a single value for each shape
        # parameter so we must use a single pixel scale for all positions.
        # Here, we define the scale at the WCS CRVAL position.
        crval = SkyCoord([wcs.wcs.crval],
                         frame=wcs_to_celestial_frame(wcs),
                         unit=wcs.wcs.cunit)
        scale, angle = pixel_scale_angle_at_skycoord(crval, wcs)

        params = self._params[:]
        theta_key = 'theta'
        if theta_key in self._params:
            pixel_params[theta_key] = (self.theta + angle).to(u.radian).value
            params.remove(theta_key)

        param_vals = [getattr(self, param) for param in params]
        if param_vals[0].unit.physical_type == 'angle':
            for param, param_val in zip(params, param_vals):
                pixel_params[param] = (param_val / scale).to(u.pixel).value
        else:  # pixels
            for param, param_val in zip(params, param_vals):
                pixel_params[param] = param_val.value

        return pixel_params
Example #32
0
    def _to_pixel_params(self, wcs, mode='all'):
        """
        Convert the sky aperture parameters to those for a pixel
        aperture.

        Parameters
        ----------
        wcs : `~astropy.wcs.WCS`
            The world coordinate system (WCS) transformation to use.

        mode : {'all', 'wcs'}, optional
            Whether to do the transformation including distortions
            (``'all'``; default) or only including only the core WCS
            transformation (``'wcs'``).

        Returns
        -------
        pixel_params : dict
            A dictionary of parameters for an equivalent pixel aperture.
        """

        pixel_params = {}
        x, y = skycoord_to_pixel(self.positions, wcs, mode=mode)
        pixel_params['positions'] = np.array([x, y]).transpose()

        # The aperture object must have a single value for each shape
        # parameter so we must use a single pixel scale for all positions.
        # Here, we define the scale at the WCS CRVAL position.
        crval = SkyCoord([wcs.wcs.crval], frame=wcs_to_celestial_frame(wcs),
                         unit=wcs.wcs.cunit)
        scale, angle = pixel_scale_angle_at_skycoord(crval, wcs)

        params = self._params[:]
        theta_key = 'theta'
        if theta_key in self._params:
            pixel_params[theta_key] = (self.theta + angle).to(u.radian).value
            params.remove(theta_key)

        param_vals = [getattr(self, param) for param in params]
        if param_vals[0].unit.physical_type == 'angle':
            for param, param_val in zip(params, param_vals):
                pixel_params[param] = (param_val / scale).to(u.pixel).value
        else:    # pixels
            for param, param_val in zip(params, param_vals):
                pixel_params[param] = param_val.value

        return pixel_params
Example #33
0
    def _to_sky_params(self, wcs, mode='all'):
        """
        Convert the pixel aperture parameters to those for a sky
        aperture.

        Parameters
        ----------
        wcs : `~astropy.wcs.WCS`
            The world coordinate system (WCS) transformation to use.

        mode : {'all', 'wcs'}, optional
            Whether to do the transformation including distortions
            (``'all'``; default) or only including only the core WCS
            transformation (``'wcs'``).

        Returns
        -------
        sky_params : `dict`
            A dictionary of parameters for an equivalent sky aperture.
        """

        sky_params = {}
        xpos, ypos = np.transpose(self.positions)
        sky_params['positions'] = pixel_to_skycoord(xpos, ypos, wcs, mode=mode)

        # The aperture object must have a single value for each shape
        # parameter so we must use a single pixel scale for all positions.
        # Here, we define the scale at the WCS CRVAL position.
        crval = SkyCoord(*wcs.wcs.crval,
                         frame=wcs_to_celestial_frame(wcs),
                         unit=wcs.wcs.cunit)
        pixscale, angle = _pixel_scale_angle_at_skycoord(crval, wcs)

        shape_params = list(self._shape_params)

        theta_key = 'theta'
        if theta_key in shape_params:
            sky_params[theta_key] = (self.theta * u.rad) - angle.to(u.rad)
            shape_params.remove(theta_key)

        for shape_param in shape_params:
            value = getattr(self, shape_param)
            sky_params[shape_param] = (value * u.pix * pixscale).to(u.arcsec)

        return sky_params
Example #34
0
    def _to_sky_params(self, wcs, mode='all'):
        """
        Convert the pixel aperture parameters to those for a sky
        aperture.

        Parameters
        ----------
        wcs : `~astropy.wcs.WCS`
            The world coordinate system (WCS) transformation to use.

        mode : {'all', 'wcs'}, optional
            Whether to do the transformation including distortions
            (``'all'``; default) or only including only the core WCS
            transformation (``'wcs'``).

        Returns
        -------
        sky_params : dict
            A dictionary of parameters for an equivalent sky aperture.
        """

        sky_params = {}
        x, y = np.transpose(self.positions)
        sky_params['positions'] = pixel_to_skycoord(x, y, wcs, mode=mode)

        # The aperture object must have a single value for each shape
        # parameter so we must use a single pixel scale for all positions.
        # Here, we define the scale at the WCS CRVAL position.
        crval = SkyCoord([wcs.wcs.crval], frame=wcs_to_celestial_frame(wcs),
                         unit=wcs.wcs.cunit)
        scale, angle = pixel_scale_angle_at_skycoord(crval, wcs)

        params = self._params[:]
        theta_key = 'theta'
        if theta_key in self._params:
            sky_params[theta_key] = (self.theta * u.rad) - angle.to(u.rad)
            params.remove(theta_key)

        param_vals = [getattr(self, param) for param in params]
        for param, param_val in zip(params, param_vals):
            sky_params[param] = (param_val * u.pix * scale).to(u.arcsec)

        return sky_params
Example #35
0
    def _to_sky_params(self, wcs):
        """
        Convert the pixel aperture parameters to those for a sky
        aperture.

        Parameters
        ----------
        wcs : WCS object
            A world coordinate system (WCS) transformation that
            supports the `astropy shared interface for WCS
            <https://docs.astropy.org/en/stable/wcs/wcsapi.html>`_
            (e.g., `astropy.wcs.WCS`, `gwcs.wcs.WCS`).

        Returns
        -------
        sky_params : `dict`
            A dictionary of parameters for an equivalent sky aperture.
        """

        sky_params = {}
        xpos, ypos = np.transpose(self.positions)
        sky_params['positions'] = _pixel_to_world(xpos, ypos, wcs)

        # The aperture object must have a single value for each shape
        # parameter so we must use a single pixel scale for all positions.
        # Here, we define the scale at the WCS CRVAL position.
        crval = SkyCoord(*wcs.wcs.crval,
                         frame=wcs_to_celestial_frame(wcs),
                         unit=wcs.wcs.cunit)
        pixscale, angle = _pixel_scale_angle_at_skycoord(crval, wcs)

        shape_params = list(self._shape_params)

        theta_key = 'theta'
        if theta_key in shape_params:
            sky_params[theta_key] = (self.theta * u.rad) - angle.to(u.rad)
            shape_params.remove(theta_key)

        for shape_param in shape_params:
            value = getattr(self, shape_param)
            sky_params[shape_param] = (value * u.pix * pixscale).to(u.arcsec)

        return sky_params
Example #36
0
 def __init__(self, wcs, slice=None):
     super().__init__()
     self.wcs = wcs
     if self.wcs.wcs.naxis > 2:
         if slice is None:
             raise ValueError(
                 "WCS has more than 2 dimensions, so ``slice`` should be set"
             )
         elif len(slice) != self.wcs.wcs.naxis:
             raise ValueError("slice should have as many elements as WCS "
                              "has dimensions (should be {})".format(
                                  self.wcs.wcs.naxis))
         else:
             self.slice = slice
             self.x_index = slice.index('x')
             self.y_index = slice.index('y')
     else:
         self.slice = None
     if wcs.has_celestial:
         self.frame_in = wcs_to_celestial_frame(wcs)
Example #37
0
def _convert_world_coordinates(lon_in, lat_in, wcs_in, wcs_out):

    frame_in, lon_in_unit, lat_in_unit = wcs_in

    wcs_out = wcs_out.celestial
    frame_out = wcs_to_celestial_frame(wcs_out)
    lon_out_unit = u.Unit(wcs_out.wcs.cunit[0])
    lat_out_unit = u.Unit(wcs_out.wcs.cunit[1])

    data = UnitSphericalRepresentation(lon_in * lon_in_unit,
                                       lat_in * lat_in_unit)

    coords_in = frame_in.realize_frame(data)
    coords_out = coords_in.transform_to(frame_out)

    lon_out = coords_out.represent_as('unitspherical').lon.to(
        lon_out_unit).value
    lat_out = coords_out.represent_as('unitspherical').lat.to(
        lat_out_unit).value

    return lon_out, lat_out
Example #38
0
 def toggle_coords_in_degrees(self):
     """
     Switch coords_in_degrees state
     """
     data = self.state.layers_data[0]
     is_ra_dec = isinstance(wcs_to_celestial_frame(data.coords.wcs),
                            BaseRADecFrame)
     if self._coords_in_degrees:
         self._coords_in_degrees = False
         self._coords_format_function = self._format_to_hex_string
         if is_ra_dec:
             self.axes.coords[0].set_major_formatter('hh:mm:ss.s')
             self.axes.coords[1].set_major_formatter('dd:mm:ss')
             self.figure.canvas.draw()
     else:
         self._coords_in_degrees = True
         self._coords_format_function = self._format_to_degree_string
         if is_ra_dec:
             self.axes.coords[0].set_major_formatter('d.dddd')
             self.axes.coords[1].set_major_formatter('d.dddd')
             self.figure.canvas.draw()
Example #39
0
 def toggle_coords_in_degrees(self):
     """
     Switch coords_in_degrees state
     """
     data = self.state.layers_data[0]
     is_ra_dec = isinstance(wcs_to_celestial_frame(data.coords.wcs),
                            BaseRADecFrame)
     if self._coords_in_degrees:
         self._coords_in_degrees = False
         self._coords_format_function = self._format_to_hex_string
         if is_ra_dec:
             self.axes.coords[0].set_major_formatter('hh:mm:ss.s')
             self.axes.coords[1].set_major_formatter('dd:mm:ss')
             self.figure.canvas.draw()
     else:
         self._coords_in_degrees = True
         self._coords_format_function = self._format_to_degree_string
         if is_ra_dec:
             self.axes.coords[0].set_major_formatter('d.dddd')
             self.axes.coords[1].set_major_formatter('d.dddd')
             self.figure.canvas.draw()
Example #40
0
    def load_header(self, header, fobj=None):
        from astropy.wcs.utils import wcs_to_celestial_frame
        try:
            # reconstruct a pyfits header, because otherwise we take an
            # incredible performance hit in astropy.wcs
            self.header = pyfits.Header(header.items())

            self.logger.debug("Trying to make astropy wcs object")
            self.wcs = pywcs.WCS(self.header, fobj=fobj, relax=True)
            try:
                self.coordframe = wcs_to_celestial_frame(self.wcs)
            except ValueError:
                sysname = get_coord_system_name(self.header)
                if sysname in ('raw', 'pixel'):
                    self.coordframe = sysname
                else:
                    raise

        except Exception as e:
            self.logger.error("Error making WCS object: %s" % (str(e)))
            self.wcs = None
Example #41
0
def test_wcs_to_celestial_frame():

    # Import astropy.coordinates here to avoid circular imports
    from astropy.coordinates.builtin_frames import ICRS, ITRS, FK5, FK4, Galactic

    mywcs = WCS(naxis=2)
    mywcs.wcs.set()
    with pytest.raises(ValueError,
                       match="Could not determine celestial frame "
                       "corresponding to the specified WCS object"):
        assert wcs_to_celestial_frame(mywcs) is None

    mywcs = WCS(naxis=2)
    mywcs.wcs.ctype = ['XOFFSET', 'YOFFSET']
    mywcs.wcs.set()
    with pytest.raises(ValueError):
        assert wcs_to_celestial_frame(mywcs) is None

    mywcs = WCS(naxis=2)
    mywcs.wcs.ctype = ['RA---TAN', 'DEC--TAN']
    mywcs.wcs.set()
    frame = wcs_to_celestial_frame(mywcs)
    assert isinstance(frame, ICRS)

    mywcs = WCS(naxis=2)
    mywcs.wcs.ctype = ['RA---TAN', 'DEC--TAN']
    mywcs.wcs.equinox = 1987.
    mywcs.wcs.set()
    print(mywcs.to_header())
    frame = wcs_to_celestial_frame(mywcs)
    assert isinstance(frame, FK5)
    assert frame.equinox == Time(1987., format='jyear')

    mywcs = WCS(naxis=2)
    mywcs.wcs.ctype = ['RA---TAN', 'DEC--TAN']
    mywcs.wcs.equinox = 1982
    mywcs.wcs.set()
    frame = wcs_to_celestial_frame(mywcs)
    assert isinstance(frame, FK4)
    assert frame.equinox == Time(1982., format='byear')

    mywcs = WCS(naxis=2)
    mywcs.wcs.ctype = ['GLON-SIN', 'GLAT-SIN']
    mywcs.wcs.set()
    frame = wcs_to_celestial_frame(mywcs)
    assert isinstance(frame, Galactic)

    mywcs = WCS(naxis=2)
    mywcs.wcs.ctype = ['TLON-CAR', 'TLAT-CAR']
    mywcs.wcs.dateobs = '2017-08-17T12:41:04.430'
    mywcs.wcs.set()
    frame = wcs_to_celestial_frame(mywcs)
    assert isinstance(frame, ITRS)
    assert frame.obstime == Time('2017-08-17T12:41:04.430')

    for equinox in [np.nan, 1987, 1982]:
        mywcs = WCS(naxis=2)
        mywcs.wcs.ctype = ['RA---TAN', 'DEC--TAN']
        mywcs.wcs.radesys = 'ICRS'
        mywcs.wcs.equinox = equinox
        mywcs.wcs.set()
        frame = wcs_to_celestial_frame(mywcs)
        assert isinstance(frame, ICRS)

    # Flipped order
    mywcs = WCS(naxis=2)
    mywcs.wcs.ctype = ['DEC--TAN', 'RA---TAN']
    mywcs.wcs.set()
    frame = wcs_to_celestial_frame(mywcs)
    assert isinstance(frame, ICRS)

    # More than two dimensions
    mywcs = WCS(naxis=3)
    mywcs.wcs.ctype = ['DEC--TAN', 'VELOCITY', 'RA---TAN']
    mywcs.wcs.set()
    frame = wcs_to_celestial_frame(mywcs)
    assert isinstance(frame, ICRS)

    mywcs = WCS(naxis=3)
    mywcs.wcs.ctype = ['GLAT-CAR', 'VELOCITY', 'GLON-CAR']
    mywcs.wcs.set()
    frame = wcs_to_celestial_frame(mywcs)
    assert isinstance(frame, Galactic)
Example #42
0
def find_optimal_celestial_wcs(input_data,
                               frame=None,
                               auto_rotate=False,
                               projection='TAN',
                               resolution=None,
                               reference=None):
    """
    Given one or more images, return an optimal WCS projection object and
    shape.

    This currently only works with 2-d images with celestial WCS.

    Parameters
    ----------
    input_data : iterable
        One or more input data specifications to include in the calculation of
        the final WCS. This should be an iterable containing one entry for each
        specification, where a single data specification is one of:

            * The name of a FITS file
            * An `~astropy.io.fits.HDUList` object
            * An image HDU object such as a `~astropy.io.fits.PrimaryHDU`,
              `~astropy.io.fits.ImageHDU`, or `~astropy.io.fits.CompImageHDU`
              instance
            * A tuple where the first element is an Numpy array shape tuple
              the second element is either a `~astropy.wcs.WCS` or a
              `~astropy.io.fits.Header` object
            * A tuple where the first element is a `~numpy.ndarray` and the
              second element is either a `~astropy.wcs.WCS` or a
              `~astropy.io.fits.Header` object

    frame : str or `~astropy.coordinates.BaseCoordinateFrame`
        The coordinate system for the final image (defaults to the frame of
        the first image specified)
    auto_rotate : bool
        Whether to rotate the header to minimize the final image area (if
        `True`, requires shapely>=1.6 to be installed)
    projection : str
        Three-letter code for the WCS projection
    resolution : `~astropy.units.Quantity`
        The resolution of the final image. If not specified, this is the
        smallest resolution of the input images.
    reference : `~astropy.coordinates.SkyCoord`
        The reference coordinate for the final header. If not specified, this
        is determined automatically from the input images.

    Returns
    -------
    wcs : :class:`~astropy.wcs.WCS`
        The optimal WCS determined from the input images.
    shape : tuple
        The optimal shape required to cover all the output.
    """

    # TODO: support higher-dimensional datasets in future
    # TODO: take into account NaN values when determining the extent of the
    #       final WCS

    if isinstance(frame, str):
        frame = frame_transform_graph.lookup_name(frame)()

    input_shapes = [parse_input_shape(shape) for shape in input_data]

    # We start off by looping over images, checking that they are indeed
    # celestial images, and building up a list of all corners and all reference
    # coordinates in celestial (ICRS) coordinates.

    corners = []
    references = []
    resolutions = []

    for shape, wcs in input_shapes:

        if len(shape) != 2:
            raise ValueError(
                "Input data is not 2-dimensional (got shape {!r})".format(
                    shape))

        if wcs.naxis != 2:
            raise ValueError("Input WCS is not 2-dimensional")

        if not wcs.has_celestial:
            raise TypeError("WCS does not have celestial components")

        # Determine frame if it wasn't specified
        if frame is None:
            frame = wcs_to_celestial_frame(wcs)

        # Find pixel coordinates of corners. In future if we are worried about
        # significant distortions of the edges in the reprojection process we
        # could simply add arbitrary numbers of midpoints to this list.
        ny, nx = shape
        xc = np.array([-0.5, nx - 0.5, nx - 0.5, -0.5])
        yc = np.array([-0.5, -0.5, ny - 0.5, ny - 0.5])

        # We have to do .frame here to make sure that we get an ICRS object
        # without any 'hidden' attributes, otherwise the stacking below won't
        # work. TODO: check if we need to enable distortions here.
        corners.append(pixel_to_skycoord(xc, yc, wcs, origin=0).icrs.frame)

        # We now figure out the reference coordinate for the image in ICRS. The
        # easiest way to do this is actually to use pixel_to_skycoord with the
        # reference position in pixel coordinates. We have to set origin=1
        # because crpix values are 1-based.
        xp, yp = wcs.wcs.crpix
        references.append(pixel_to_skycoord(xp, yp, wcs, origin=1).icrs.frame)

        # Find the pixel scale at the reference position - we take the minimum
        # since we are going to set up a header with 'square' pixels with the
        # smallest resolution specified.
        scales = proj_plane_pixel_scales(wcs)
        resolutions.append(np.min(np.abs(scales)))

    # We now stack the coordinates - however the ICRS class can't do this
    # so we have to use the high-level SkyCoord class.
    corners = SkyCoord(corners)
    references = SkyCoord(references)

    # If no reference coordinate has been passed in for the final header, we
    # determine the reference coordinate as the mean of all the reference
    # positions. This choice is as good as any and if the user really cares,
    # they can set  it manually.
    if reference is None:
        reference = SkyCoord(references.data.mean(), frame=references.frame)

    # In any case, we need to convert the reference coordinate (either
    # specified or automatically determined) to the requested final frame.
    reference = reference.transform_to(frame)

    # Determine resolution if not specified
    if resolution is None:
        resolution = np.min(resolutions) * u.deg

    # Determine the resolution in degrees
    cdelt = resolution.to(u.deg).value

    # Construct WCS object centered on position
    wcs_final = celestial_frame_to_wcs(frame, projection=projection)

    rep = reference.represent_as('unitspherical')
    wcs_final.wcs.crval = rep.lon.degree, rep.lat.degree
    wcs_final.wcs.cdelt = -cdelt, cdelt

    # For now, set crpix to (1, 1) and we'll then figure out where all the
    # images fall in this projection, then we'll adjust crpix.
    wcs_final.wcs.crpix = (1, 1)

    # Find pixel coordinates of all corners in the final WCS projection. We use
    # origin=1 since we are trying to determine crpix values.
    xp, yp = skycoord_to_pixel(corners, wcs_final, origin=1)

    if auto_rotate:

        # Use shapely to represent the points and find the minimum rotated
        # rectangle
        from shapely.geometry import MultiPoint
        mp = MultiPoint(list(zip(xp, yp)))

        # The following returns a list of rectangle vertices - in fact there
        # are 5 coordinates because shapely represents it as a closed polygon
        # with the same first/last vertex.
        xr, yr = mp.minimum_rotated_rectangle.exterior.coords.xy
        xr, yr = xr[:4], yr[:4]

        # The order of the vertices is not guaranteed to be constant so we
        # take the vertices with the two smallest y values (which, for a
        # rectangle, guarantees that the vertices are neighboring)
        order = np.argsort(yr)
        x1, y1, x2, y2 = xr[order[0]], yr[order[0]], xr[order[1]], yr[order[1]]

        # Determine angle between two of the vertices. It doesn't matter which
        # ones they are, we just want to know how far from being straight the
        # rectangle is.
        angle = np.arctan2(y2 - y1, x2 - x1)

        # Determine the smallest angle that would cause the rectangle to be
        # lined up with the axes.
        angle = angle % (np.pi / 2)
        if angle > np.pi / 4:
            angle -= np.pi / 2

        # Set rotation matrix (use PC instead of CROTA2 since PC is the
        # recommended approach)
        pc = np.array([[np.cos(angle), -np.sin(angle)],
                       [np.sin(angle), np.cos(angle)]])
        wcs_final.wcs.pc = pc

        # Recompute pixel coordinates (more accurate than simply rotating xp, yp)
        xp, yp = skycoord_to_pixel(corners, wcs_final, origin=1)

    # Find the full range of values
    xmin = xp.min()
    xmax = xp.max()
    ymin = yp.min()
    ymax = yp.max()

    # Update crpix so that the lower range falls on the bottom and left. We add
    # 0.5 because in the final image the bottom left corner should be at (0.5,
    # 0.5) not (1, 1).
    wcs_final.wcs.crpix = (1 - xmin) + 0.5, (1 - ymin) + 0.5

    # Return the final image shape too
    naxis1 = int(round(xmax - xmin))
    naxis2 = int(round(ymax - ymin))

    return wcs_final, (naxis2, naxis1)
Example #43
0
    def __init__(self, parent):

        self._ax = parent.ax
        self._wcs = parent.ax.wcs
        self.x = parent.x
        self.y = parent.y

        xcoord_type = self._ax.coords[self.x].coord_type
        ycoord_type = self._ax.coords[self.y].coord_type

        if xcoord_type == 'longitude' and ycoord_type == 'latitude':
            celestial = True
            inverted = False
        elif xcoord_type == 'latitude' and ycoord_type == 'longitude':
            celestial = True
            inverted = True
        else:
            celestial = inverted = False

        if celestial:
            frame = wcs_to_celestial_frame(self._wcs)
        else:
            frame = None

        if isinstance(frame, ICRS):

            xtext = 'RA (ICRS)'
            ytext = 'Dec (ICRS)'

        elif isinstance(frame, FK5):

            equinox = "{:g}".format(FK5.equinox.jyear)
            xtext = 'RA (J{0})'.format(equinox)
            ytext = 'Dec (J{0})'.format(equinox)

        elif isinstance(frame, FK4):

            equinox = "{:g}".format(FK4.equinox.byear)
            xtext = 'RA (B{0})'.format(equinox)
            ytext = 'Dec (B{0})'.format(equinox)

        elif isinstance(frame, Galactic):

            xtext = 'Galactic Longitude'
            ytext = 'Galactic Latitude'

        elif isinstance(frame, (HeliocentricTrueEcliptic, BarycentricTrueEcliptic)):

            # NOTE: once we support only Astropy 2.0+, we can use BaseEclipticFrame

            xtext = 'Ecliptic Longitude'
            ytext = 'Ecliptic Latitude'

        else:

            cunit_x = self._wcs.wcs.cunit[self.x]
            cunit_y = self._wcs.wcs.cunit[self.y]

            cname_x = self._wcs.wcs.cname[self.x]
            cname_y = self._wcs.wcs.cname[self.y]

            ctype_x = self._wcs.wcs.ctype[self.x]
            ctype_y = self._wcs.wcs.ctype[self.y]

            xunit = " (%s)" % cunit_x if cunit_x not in ["", None] else ""
            yunit = " (%s)" % cunit_y if cunit_y not in ["", None] else ""

            if len(cname_x) > 0:
                xtext = cname_x + xunit
            else:
                if len(ctype_x) == 8 and ctype_x[4] == '-':
                    xtext = ctype_x[:4].replace('-', '') + xunit
                else:
                    xtext = ctype_x + xunit

            if len(cname_y) > 0:
                ytext = cname_y + yunit
            else:
                if len(ctype_y) == 8 and ctype_y[4] == '-':
                    ytext = ctype_y[:4].replace('-', '') + yunit
                else:
                    ytext = ctype_y + yunit

        if inverted:
            xtext, ytext = ytext, xtext

        self.set_xtext(xtext)
        self.set_ytext(ytext)

        self.set_xposition('bottom')
        self.set_yposition('left')
Example #44
0
def pixel_to_skycoord(xp, yp, wcs, origin=0, mode='all'):
    """
    Convert a set of pixel coordinates into a SkyCoord coordinate.

    Parameters
    ----------
    xp, yp : float or `numpy.ndarray`
        The coordinates to convert.
    wcs : `~astropy.wcs.WCS`
        The WCS transformation to use.
    origin : int
        Whether to return 0 or 1-based pixel coordinates.
    mode : 'all' or 'wcs'
        Whether to do the transformation including distortions (``'all'``) or
        only including only the core WCS transformation (``'wcs'``).

    Returns
    -------
    coords : `~astropy.coordinates.SkyCoord`
        The celestial coordinates
    """

    # temporary workaround
    has_distortion = any(getattr(wcs, dist_attr) is not None
                         for dist_attr in ['cpdis1', 'cpdis2', 'det2im1', 'det2im2', 'sip'])

    if has_distortion and wcs.naxis != 2:
        raise ValueError("Can only handle WCS with distortions for 2-dimensional WCS")

    # Keep only the celestial part of the axes, also re-orders lon/lat
    wcs = wcs.sub([WCSSUB_CELESTIAL])

    if wcs.naxis != 2:
        raise ValueError("WCS should contain celestial component")

    # TODO: remove local wcs_to_celestial_frame once Astropy 1.0 is out
    try:
        from astropy.wcs.utils import wcs_to_celestial_frame
    except ImportError:  # Astropy < 1.0
        from .extern.wcs_utils import wcs_to_celestial_frame

    # Check which frame the WCS uses
    frame = wcs_to_celestial_frame(wcs)

    # Check what unit the WCS gives
    lon_unit = u.Unit(wcs.wcs.cunit[0])
    lat_unit = u.Unit(wcs.wcs.cunit[1])

    # Convert pixel coordinates to celestial coordinates
    if mode == 'all':
        lon, lat = wcs.all_pix2world(xp, yp, origin)
    elif mode == 'wcs':
        lon, lat = wcs.wcs_pix2world(xp, yp, origin)
    else:
        raise ValueError("mode should be either 'all' or 'wcs'")

    # Add units to longitude/latitude
    lon = lon * lon_unit
    lat = lat * lat_unit

    # Create SkyCoord object
    data = UnitSphericalRepresentation(lon=lon, lat=lat)
    coords = SkyCoord(frame.realize_frame(data))

    return coords
Example #45
0
def test_wcs_to_celestial_frame():

    # Import astropy.coordinates here to avoid circular imports
    from astropy.coordinates.builtin_frames import ICRS, ITRS, FK5, FK4, Galactic

    mywcs = WCS(naxis=2)
    mywcs.wcs.set()
    with pytest.raises(ValueError) as exc:
        assert wcs_to_celestial_frame(mywcs) is None
    assert exc.value.args[0] == "Could not determine celestial frame corresponding to the specified WCS object"

    mywcs = WCS(naxis=2)
    mywcs.wcs.ctype = ['XOFFSET', 'YOFFSET']
    mywcs.wcs.set()
    with pytest.raises(ValueError):
        assert wcs_to_celestial_frame(mywcs) is None

    mywcs = WCS(naxis=2)
    mywcs.wcs.ctype = ['RA---TAN', 'DEC--TAN']
    mywcs.wcs.set()
    frame = wcs_to_celestial_frame(mywcs)
    assert isinstance(frame, ICRS)

    mywcs = WCS(naxis=2)
    mywcs.wcs.ctype = ['RA---TAN', 'DEC--TAN']
    mywcs.wcs.equinox = 1987.
    mywcs.wcs.set()
    print(mywcs.to_header())
    frame = wcs_to_celestial_frame(mywcs)
    assert isinstance(frame, FK5)
    assert frame.equinox == Time(1987., format='jyear')

    mywcs = WCS(naxis=2)
    mywcs.wcs.ctype = ['RA---TAN', 'DEC--TAN']
    mywcs.wcs.equinox = 1982
    mywcs.wcs.set()
    frame = wcs_to_celestial_frame(mywcs)
    assert isinstance(frame, FK4)
    assert frame.equinox == Time(1982., format='byear')

    mywcs = WCS(naxis=2)
    mywcs.wcs.ctype = ['GLON-SIN', 'GLAT-SIN']
    mywcs.wcs.set()
    frame = wcs_to_celestial_frame(mywcs)
    assert isinstance(frame, Galactic)

    mywcs = WCS(naxis=2)
    mywcs.wcs.ctype = ['TLON-CAR', 'TLAT-CAR']
    mywcs.wcs.dateobs = '2017-08-17T12:41:04.430'
    mywcs.wcs.set()
    frame = wcs_to_celestial_frame(mywcs)
    assert isinstance(frame, ITRS)
    assert frame.obstime == Time('2017-08-17T12:41:04.430')

    for equinox in [np.nan, 1987, 1982]:
        mywcs = WCS(naxis=2)
        mywcs.wcs.ctype = ['RA---TAN', 'DEC--TAN']
        mywcs.wcs.radesys = 'ICRS'
        mywcs.wcs.equinox = equinox
        mywcs.wcs.set()
        frame = wcs_to_celestial_frame(mywcs)
        assert isinstance(frame, ICRS)

    # Flipped order
    mywcs = WCS(naxis=2)
    mywcs.wcs.ctype = ['DEC--TAN', 'RA---TAN']
    mywcs.wcs.set()
    frame = wcs_to_celestial_frame(mywcs)
    assert isinstance(frame, ICRS)

    # More than two dimensions
    mywcs = WCS(naxis=3)
    mywcs.wcs.ctype = ['DEC--TAN', 'VELOCITY', 'RA---TAN']
    mywcs.wcs.set()
    frame = wcs_to_celestial_frame(mywcs)
    assert isinstance(frame, ICRS)

    mywcs = WCS(naxis=3)
    mywcs.wcs.ctype = ['GLAT-CAR', 'VELOCITY', 'GLON-CAR']
    mywcs.wcs.set()
    frame = wcs_to_celestial_frame(mywcs)
    assert isinstance(frame, Galactic)
Example #46
0
    def _get_components_and_classes(self):

        # The aim of this function is to return whatever is needed for
        # world_axis_object_components and world_axis_object_classes. It's easier
        # to figure it out in one go and then return the values and let the
        # properties return part of it.

        # Since this method might get called quite a few times, we need to cache
        # it. We start off by defining a hash based on the attributes of the
        # WCS that matter here (we can't just use the WCS object as a hash since
        # it is mutable)
        wcs_hash = (self.naxis,
                    list(self.wcs.ctype),
                    list(self.wcs.cunit),
                    self.wcs.radesys,
                    self.wcs.equinox,
                    self.wcs.dateobs,
                    self.wcs.lng,
                    self.wcs.lat)

        # If the cache is present, we need to check that the 'hash' matches.
        if getattr(self, '_components_and_classes_cache', None) is not None:
            cache = self._components_and_classes_cache
            if cache[0] == wcs_hash:
                return cache[1]
            else:
                self._components_and_classes_cache = None

        # Avoid circular imports by importing here
        from astropy.wcs.utils import wcs_to_celestial_frame
        from astropy.coordinates import SkyCoord

        components = [None] * self.naxis
        classes = {}

        # Let's start off by checking whether the WCS has a pair of celestial
        # components

        if self.has_celestial:

            frame = wcs_to_celestial_frame(self)

            kwargs = {}
            kwargs['frame'] = frame
            kwargs['unit'] = u.deg

            classes['celestial'] = (SkyCoord, (), kwargs)

            components[self.wcs.lng] = ('celestial', 0, 'spherical.lon.degree')
            components[self.wcs.lat] = ('celestial', 1, 'spherical.lat.degree')

        # Fallback: for any remaining components that haven't been identified, just
        # return Quantity as the class to use

        if 'time' in self.world_axis_physical_types:
            warnings.warn('In future, times will be represented by the Time class '
                          'instead of Quantity', FutureWarning)

        for i in range(self.naxis):
            if components[i] is None:
                name = self.axis_type_names[i].lower()
                if name == '':
                    name = 'world'
                while name in classes:
                    name += "_"
                classes[name] = (u.Quantity, (), {'unit': self.wcs.cunit[i]})
                components[i] = (name, 0, 'value')

        # Keep a cached version of result
        self._components_and_classes_cache = wcs_hash, (components, classes)

        return components, classes
Example #47
0
def aperture_photometry(data, positions, apertures, unit=None, wcs=None,
                        error=None, gain=None, mask=None, method='exact',
                        subpixels=5, pixelcoord=True, pixelwise_error=True,
                        mask_method='skip'):
    """
    Sum flux within an aperture at the given position(s).

    Parameters
    ----------
    data : array_like, `~astropy.io.fits.ImageHDU`, `~astropy.io.fits.HDUList`
        The 2-d array on which to perform photometry. Units are used during
        the photometry, either provided along with the data array, or stored
        in the header keyword ``'BUNIT'``.
    positions : list, tuple, nd.array or `~astropy.coordinates.SkyCoord`
        Positions of the aperture centers, either in pixel or sky
        coordinates. If positions is `~astropy.coordinates.SkyCoord` or
        ``pixelcoord`` is `False` a wcs transformation is also needed to
        convert the input positions to pixel positions. If ``positions`` are
        sky positions but not an `~astropy.coordinates.SkyCoord` object, it
        need to be in the same celestial frame as the wcs transformation.
    apertures : tuple
        First element of the tuple is the mode, the currently supported ones
        are: ``'circular'``, ``'elliptical'``, ``'circular_annulus'``,
        ``'elliptical_annulus'``, ``'rectangular'``. The remaining (1 to 4)
        elements are the parameters for the given mode. Check the
        documentation of the relevant ``Aperture`` classes for more
        information.
    unit : `~astropy.units.UnitBase` instance, str
        An object that represents the unit associated with ``data``.  Must
        be an `~astropy.units.UnitBase` object or a string parseable by the
        :mod:`~astropy.units` package. An error is raised if ``data``
        already has a different unit.
    wcs : `~astropy.wcs.WCS`, optional
        Use this as the wcs transformation when either ``pixelcoord`` is
        `False` or ``positions`` is `~astropy.coordinates.SkyCoord`. It
        overrides any wcs transformation passed along with ``data`` either
        in the header or in an attribute.
    error : float or array_like, optional
        Error in each pixel, interpreted as Gaussian 1-sigma uncertainty.
    gain : float or array_like, optional
        Ratio of counts (e.g., electrons or photons) to units of the data
        (e.g., ADU), for the purpose of calculating Poisson error from the
        object itself. If ``gain`` is `None` (default), ``error`` is assumed to
        include all uncertainty in each pixel. If ``gain`` is given, ``error``
        is assumed to be the "background error" only (not accounting for
        Poisson error in the flux in the apertures).
    mask : array_like (bool), optional
        Mask to apply to the data.
    method : str, optional
        Method to use for determining overlap between the aperture and pixels.
        Options include ['center', 'subpixel', 'exact'], but not all options
        are available for all types of apertures. More precise methods will
        generally be slower.

        * ``'center'``
            A pixel is considered to be entirely in or out of the aperture
            depending on whether its center is in or out of the aperture.
        * ``'subpixel'``
            A pixel is divided into subpixels and the center of each
            subpixel is tested (as above). With ``subpixels`` set to 1, this
            method is equivalent to 'center'. Note that for subpixel
            sampling, the input array is only resampled once for each
            object.
        * ``'exact'`` (default)
            The exact overlap between the aperture and each pixel is
            calculated.
    subpixels : int, optional
        For the ``'subpixel'`` method, resample pixels by this factor (in
        each dimension). That is, each pixel is divided into
        ``subpixels ** 2`` subpixels.
    pixelcoord : bool, optional
        If `True` (default), assume ``positions`` are pixel positions. If
        `False`, assume the input positions are sky coordinates and uses the
        wcs transformation (provided either via ``wcs`` or along with
        ``data``) to convert them to pixel positions.
    pixelwise_error : bool, optional
        For error and/or gain arrays. If `True`, assume error and/or gain
        vary significantly within an aperture: sum contribution from each
        pixel. If `False`, assume error and gain do not vary significantly
        within an aperture. Use the single value of error and/or gain at
        the center of each aperture as the value for the entire aperture.
        Default is `True`.
    mask_method : str, optional
        Method to treat masked pixels. Currently supported methods:

        * ``'skip'``
            Leave out the masked pixels from all calculations.
        * ``'interpolation'``
            The value of the masked pixels are replaced by the mean value of
            the neighbouring non-masked pixels.

    Returns
    -------
    phot_table : `~astropy.table.Table`
        A table of the photometry with the following columns:

        * ``'aperture_sum'``: Sum of the values within the aperture.
        * ``'aperture_sum_err'``: Corresponding uncertainty in
          ``'aperture_sum'`` values.  Returned only if input ``error`` is not
          `None`.
        * ``'pixel_center'``: pixel coordinate pairs of the center of the
          apertures. Unit is pixel.
        * ``'input_center'``: input coordinate pairs as they were given in the
          ``positions`` parameter.

        The metadata of the table stores the version numbers of both astropy
        and photutils, as well as the calling arguments.
    aux_dict : dict
        Auxilary dictionary storing all the auxilary information
        available. The element are the following:

        * ``'apertures'``
            The `~photutils.Aperture` object containing the apertures to use
            during the photometry.

    """
    dataunit = None
    datamask = None
    wcs_transformation = wcs

    if isinstance(data, (fits.PrimaryHDU, fits.ImageHDU)):
        header = data.header
        data = data.data

        if 'BUNIT' in header:
            dataunit = header['BUNIT']

        # TODO check how a mask can be stored in the header, it seems like
        # full pixel masks are not supported by the FITS standard, look for
        # real life examples (e.g. header value stores the fits number of
        # fits extension where the pixelmask is stored?)
        if 'MASK' in header:
            datamask = header.mask

    elif isinstance(data, fits.HDUList):
        # TODO: do it in a 2d array, and thus get the light curves as a
        # side-product? Although it's not usual to store time series as
        # HDUList

        for i in range(len(data)):
            if data[i].data is not None:
                warnings.warn("Input data is a HDUList object, photometry is "
                              "only run for the {0}. HDU."
                              .format(i), AstropyUserWarning)
                return aperture_photometry(data[i], positions, apertures, unit,
                                           wcs, error, gain, mask, method,
                                           subpixels, pixelcoord,
                                           pixelwise_error, mask_method)

    # this is basically for NDData inputs and alike
    elif hasattr(data, 'data') and not isinstance(data, np.ndarray):
        if data.wcs is not None and wcs_transformation is None:
            wcs_transformation = data.wcs
        datamask = data.mask

    if hasattr(data, 'unit'):
        dataunit = data.unit

    if unit is not None and dataunit is not None:
        if unit != dataunit:
            raise u.UnitsError('Unit of input data ({0}) and unit given by '
                               'unit argument ({1}) are not identical.'.
                               format(dataunit, unit))
        data = u.Quantity(data, unit=dataunit, copy=False)
    elif unit is None:
        if dataunit is not None:
            data = u.Quantity(data, unit=dataunit, copy=False)
        else:
            data = u.Quantity(data, copy=False)
    else:
        data = u.Quantity(data, unit=unit, copy=False)

    if datamask is None:
        data.mask = datamask

    # Check input array type and dimension.
    if np.iscomplexobj(data):
        raise TypeError('Complex type not supported')
    if data.ndim != 2:
        raise ValueError('{0}-d array not supported. '
                         'Only 2-d arrays supported.'.format(data.ndim))

    # Deal with the mask if it exist
    if mask is not None or datamask is not None:
        if mask is None:
            mask = datamask
        else:
            mask = np.asarray(mask)
            if np.iscomplexobj(mask):
                raise TypeError('Complex type not supported')
            if mask.ndim != 2:
                raise ValueError('{0}-d array not supported. '
                                 'Only 2-d arrays supported.'
                                 .format(mask.ndim))
            if mask.shape != data.shape:
                raise ValueError('Shapes of mask array and data array '
                                 'must match')

            if datamask is not None:
                mask *= datamask

        if mask_method == 'skip':
            data *= ~mask

        if mask_method == 'interpolation':
            for i, j in zip(*np.nonzero(mask)):
                y0, y1 = max(i - 1, 0), min(i + 2, data.shape[0])
                x0, x1 = max(j - 1, 0), min(j + 2, data.shape[1])
                data[i, j] = np.mean(data[y0:y1, x0:x1][~mask[y0:y1, x0:x1]])

    # Check whether we really need to calculate pixelwise errors, even if
    # requested. (If neither error nor gain is an array, we don't need to.)
    if ((error is None) or
        (np.isscalar(error) and gain is None) or
        (np.isscalar(error) and np.isscalar(gain))):
        pixelwise_error = False

    # Check error shape.
    if error is not None:
        if isinstance(error, u.Quantity):
            if np.isscalar(error.value):
                error = u.Quantity(np.broadcast_arrays(error, data),
                                   unit=error.unit)[0]
        elif np.isscalar(error):
            error = u.Quantity(np.broadcast_arrays(error, data),
                               unit=data.unit)[0]
        else:
            error = u.Quantity(error, unit=data.unit, copy=False)

        if error.shape != data.shape:
            raise ValueError('shapes of error array and data array must'
                             ' match')

    # Check gain shape.
    if gain is not None:
        # Gain doesn't do anything without error set, so raise an exception.
        # (TODO: instead, should we just set gain = None and ignore it?)
        if error is None:
            raise ValueError('gain requires error')

        if isinstance(gain, u.Quantity):
            if np.isscalar(gain.value):
                gain = u.Quantity(np.broadcast_arrays(gain, data),
                                  unit=gain.unit)[0]

        elif np.isscalar(gain):
            gain = np.broadcast_arrays(gain, data)[0]
        if gain.shape != data.shape:
            raise ValueError('shapes of gain array and data array must match')

    # Check that 'subpixels' is an int and is 1 or greater.
    if method == 'subpixel':
        subpixels = int(subpixels)
        if subpixels < 1:
            raise ValueError('subpixels: an integer greater than 0 is '
                             'required')

    if not pixelcoord or isinstance(positions, SkyCoord):

        from astropy.wcs import wcs

        try:
            from astropy.wcs.utils import wcs_to_celestial_frame
        except ImportError:  # Astropy < 1.0
            from .extern.wcs_utils import wcs_to_celestial_frame

        if wcs_transformation is None:
            wcs_transformation = wcs.WCS(header)

        # TODO this should be simplified once wcs_world2pix() supports
        # SkyCoord objects as input
        if isinstance(positions, SkyCoord):
            # Check which frame the wcs uses
            framename = wcs_to_celestial_frame(wcs_transformation).name
            frame = getattr(positions, framename)
            component_names = list(frame.representation_component_names.keys())[0:2]
            if len(positions.shape) > 0:
                positions_repr = u.Quantity(zip(getattr(frame,
                                                        component_names[0]).deg,
                                                getattr(frame,
                                                        component_names[1]).deg),
                                            unit=u.deg)
            else:
                positions_repr = (u.Quantity((getattr(frame,
                                                      component_names[0]).deg,
                                              getattr(frame,
                                                      component_names[1]).deg),
                                             unit=u.deg), )
        elif not isinstance(positions, u.Quantity):
            # TODO figure out the unit of the input positions for this case
            # TODO revise this once wcs_world2pix() accepts more input formats
            if len(positions) > 1 and not isinstance(positions, tuple):
                positions_repr = u.Quantity(positions, copy=False)
            else:
                positions_repr = (u.Quantity(positions, copy=False), )
        pixelpositions = u.Quantity(wcs_transformation.wcs_world2pix
                                    (positions_repr, 0), unit=u.pixel,
                                    copy=False)
    else:
        positions = u.Quantity(positions, unit=u.pixel, copy=False)
        pixelpositions = positions

    if apertures[0] == 'circular':
        ap = CircularAperture(pixelpositions.value, apertures[1])
    elif apertures[0] == 'circular_annulus':
        ap = CircularAnnulus(pixelpositions.value, *apertures[1:3])
    elif apertures[0] == 'elliptical':
        ap = EllipticalAperture(pixelpositions.value, *apertures[1:4])
    elif apertures[0] == 'elliptical_annulus':
        ap = EllipticalAnnulus(pixelpositions.value, *apertures[1:5])
    elif apertures[0] == 'rectangular':
        if method == 'exact':
            warnings.warn("'exact' method is not implemented, defaults to "
                          "'subpixel' instead", AstropyUserWarning)
            method = 'subpixel'
        ap = RectangularAperture(pixelpositions.value, *apertures[1:4])

    # Prepare version return data
    from astropy import __version__
    astropy_version = __version__

    from photutils import __version__
    photutils_version = __version__

    photometry_result = ap.do_photometry(data, method=method,
                                         subpixels=subpixels,
                                         error=error, gain=gain,
                                         pixelwise_error=pixelwise_error)
    if error is None:
        phot_col_names = ('aperture_sum', )
    else:
        phot_col_names = ('aperture_sum', 'aperture_sum_err')

    # Note: if wcs_transformation is None, 'pixel_center' will be the same
    # as 'input_center'

    # check whether single or multiple positions
    if len(pixelpositions) > 1 and pixelpositions[0].size >= 2:
        coord_columns = (pixelpositions, positions)
    else:
        coord_columns = ((pixelpositions,), (positions,))

    coord_col_names = ('pixel_center', 'input_center')

    return (Table(data=(photometry_result + coord_columns),
                  names=(phot_col_names + coord_col_names),
                  meta={'name': 'Aperture photometry results',
                        'version': 'astropy: {0}, photutils: {1}'
                        .format(astropy_version, photutils_version),
                        'calling_args': ('method={0}, subpixels={1}, '
                                         'error={2}, gain={3}, '
                                         'pixelwise_error={4}')
                        .format(method, subpixels, error is not None,
                                gain is not None, pixelwise_error)}),
            {'apertures': ap})
Example #48
0
def maskFits(fitsfile,
             out='maskedimage.fits',
             img_hdu=None,
             mask_type=None,
             radius=180.,
             radius2=None,
             angle=0.,
             center=(0., 0.),
             extent=[-180., 180., -90., 90.],
             frame='galactic',
             unit='degree',
             clobber=False,
             float_min=1.17549e-38):
    """Mask the fits image

    Parameters
    ----------
    fitsfile : str
        Path to the FITS file containing the the image which will be masked.
    out : str (Optional)
        Name of the output FITS file for which the mask wiil be applied
    img_hud : int or float (Optional)
        Name or integer for the FITS hdu containing the image. Default is 'Primary'.
    mask_type : str
        The geometry of the mask to be applied. Choices are 'radial' or 'square'.
    radius : float (Optional)
        Radius of the mask if mask_type is radial. Default is 180.
    radius2 : float (Optional)
        Second radius of the mask if the mask is not symmetric. Default is to use a symmetric mask.
    angle : float (Optional)
        Rotation angle of the ellipse. Default is 0.
    center : tuple (Optional)
        Center coordinates (C1, C2) of the radial mask. Default is (0., 0.).
    extent : list (Optional)
        [xmin, xmax, ymin, ymax] extent of the square mask. Default is [-180., 180., -90., 90.].
    frame : str (Optional)
        Coordinate frame to use with mask coordinates. Choices are 'galactic', 'icrs', 'fk5', 'pixel'. Default is 'galactic'.
    unit : str (Optional)
        Units of coordinates. Default is 'degree'.
    clobber : bool
        Flag to overwrite a file of the same name if it exists. Default is False.
    float_min : float
        Minimum float value to use for pixels in the image after masking. gtobssim doesn't like pixel values <= 0. Default is 1.17549e-38.

    Returns
    -------
    out : str or tuple
        If the input fits image is 2D the output is the full path to the masked fits image. If the input fits image is 3D the output is a tuple whose first entry is the full path to the masked fits image and whose second entry is the integrated flux of the masked fits image.

    """
    if frame == 'galactic':
        frame_str = '(GLAT, GLON)'
    elif frame == 'icrs':
        frame_str = '(RA, DEC)'
    elif frame == 'fk5':
        frame_str = '(RAJ2000, DECJ2000)'
    elif frame == 'pixel':
        frame_str = '(PIX1, PIX2)'
    else:
        raise IOError("Invalid frame {0}".format(frame))

    fitsfile = os.path.expandvars(fitsfile).replace(
        "$(FERMI_DIR)", os.environ.get("FERMI_DIR")) if os.environ.get(
            "FERMI_DIR") is not None else os.path.expandvars(fitsfile)
    hdu_list = pyfits.open(fitsfile)

    if img_hdu is None:
        # Assume the data image is in the primary hdu
        header = hdu_list['PRIMARY'].header
        img_hdu = 'PRIMARY'
    else:
        try:
            img_hdu = int(img_hdu)
        except ValueError:
            pass
        header = hdu_list[img_hdu].header

    wcs = WCS(header, naxis=2)
    # Probably shouldn't use these private attributes, but there doesn't seem to be another way to get the length of each axis from the WCS object.
    naxis1 = wcs._naxis1
    naxis2 = wcs._naxis2

    xgrid, ygrid = np.meshgrid(np.arange(naxis1), np.arange(naxis2))

    if frame != 'pixel':
        flat_xcoords, flat_ycoords = wcs.wcs_pix2world(xgrid.flatten(),
                                                       ygrid.flatten(), 0)
        xcoords = flat_xcoords.reshape(naxis2, naxis1)
        ycoords = flat_ycoords.reshape(naxis2, naxis1)
    else:
        xcoords = xgrid
        xcoords = ygrid

    del xgrid
    del ygrid

    if mask_type == 'radial':
        if frame != 'pixel':
            c = SkyCoord(center[0], center[1], frame=frame, unit=unit)
            c_new = c.transform_to(wcs_to_celestial_frame(wcs))
            center_new = map(float, c_new.to_string().split(' '))
        else:
            center_new = center

        mask = genRadialMask(xcoords,
                             ycoords,
                             radius,
                             radius2,
                             angle,
                             center_new,
                             frame=frame)
        hdu_list[img_hdu].header[
            'history'] = '{0} Applied radial mask to data.'.format(
                datetime.datetime.today().strftime('%d %B %Y'))
        hdu_list[img_hdu].header[
            'history'] = 'radius={0}, radius2={1}, angle={2}, center={3} {4}'.format(
                radius, radius2, angle, center, frame_str)
    elif mask_type == 'square':
        if frame != 'pixel':
            c = SkyCoord([extent[0], extent[2]], [extent[1], extent[3]],
                         frame=frame,
                         unit=unit)
            c_new = c.transform_to(wcs_to_celestial_frame(wcs))
            l, t = map(float, c_new[0].to_string().split(' '))
            r, b = map(float, c_new[1].to_string().split(' '))
            extent_new = [l, r, b, t]
        else:
            extent_new = extent
        if 'GLON' in wcs.wcs.ctype[0]:
            mask = genSquareMask(
                np.where(xcoords < 180., xcoords, xcoords - 360.), ycoords,
                extent_new)
        else:
            mask = genSquarMask(xcoords, ycoords, extent_new)
        hdu_list[img_hdu].header[
            'history'] = '{0} Applied square mask to data.'.format(
                datetime.datetime.today().strftime('%d %B %Y'))
        hdu_list[img_hdu].header[
            'history'] = '[left, right, top, bottom]={0} {1}'.format(
                extent, frame_str)
    else:
        raise MaskTypeError(
            "{0} is not a supported mask geometry.".format(mask_type))

    data_shape = hdu_list[img_hdu].data.shape
    xcoords_shape = xcoords.shape
    ycoords_shape = ycoords.shape

    naxis = header['NAXIS']
    if naxis > 2:
        tile_shape = [1, 1, 1]
        E_idx = 0
        for idx in range(1, 4):
            naxis_E = None
            if header['CTYPE{0}'.format(idx)] not in wcs.wcs.ctype:
                E_idx = idx
                naxis_E = header['NAXIS{0}'.format(idx)]
                tile_shape[naxis - idx] = naxis_E
        if naxis_E is None:
            raise NaxisError("Could not find the energy axis of the image.")

        try:
            # hdu_list[img_hdu].data = hdu_list[img_hdu].data*np.tile(mask, tuple(tile_shape))
            hdu_list[img_hdu].data *= np.tile(mask, tuple(tile_shape))
        except MemoryError:
            for idx in range(naxis_E):
                hdu_list[img_hdu].data[idx, :, :] *= mask
        del mask

        # Have to memmap data arrays because of intermediate arrays created by trapz
        tmp_dir = mkdtemp()
        try:
            energies_shape = hdu_list['ENERGIES'].data.energy.shape
        except AttributeError:
            energies_shape = hdu_list['ENERGIES'].data.Energy.shape

        m_data_path = os.path.join(tmp_dir, 'data_array.dat')
        m_energies_path = os.path.join(tmp_dir, 'energy_array.dat')
        m_xcoords_path = os.path.join(tmp_dir, 'xcoords_array.dat')
        m_ycoords_path = os.path.join(tmp_dir, 'ycoords_array.dat')

        m_data = np.memmap(m_data_path,
                           dtype=np.float32,
                           mode='w+',
                           shape=data_shape)
        m_energies = np.memmap(m_energies_path,
                               dtype=np.float32,
                               mode='w+',
                               shape=energies_shape)
        m_xcoords = np.memmap(m_xcoords_path,
                              dtype=np.float32,
                              mode='w+',
                              shape=xcoords_shape)
        m_ycoords = np.memmap(m_ycoords_path,
                              dtype=np.float32,
                              mode='w+',
                              shape=ycoords_shape)

        if wcs.wcs.cdelt[0] < 0.:
            m_data[:, :, :] = np.flip(hdu_list[img_hdu].data, axis=2)[:, :, :]
            m_xcoords[:, :] = np.flip(np.where(xcoords <= 180., xcoords,
                                               xcoords - 360.),
                                      axis=1)[:, :]
        else:
            m_data[:, :, :] = hdu_list[img_hdu].data[:, :, :]
            m_xcoords[:, :] = np.where(xcoords <= 180, xcoords,
                                       xcoords - 360.)[:, :]
        m_ycoords[:, :] = ycoords[:, :]
        try:
            m_energies[:] = hdu_list['ENERGIES'].data.energy[:]
        except AttributeError:
            m_energies[:] = hdu_list['ENERGIES'].data.Energy[:]
        del m_data
        del m_xcoords
        del m_ycoords
        del m_energies

    else:
        hdu_list[img_hdu].data *= mask
        del mask

    if not os.path.isabs(out):
        out = os.path.join(os.getcwd(), out)

    try:
        hdu_list[img_hdu].data[hdu_list[img_hdu].data < float_min] = float_min
    except MemoryError:
        for idx in range(naxis_E):
            hdu_list[img_hdu].data[idx, :, :][
                hdu_list[img_hdu].data[idx, :, :] < float_min] = float_min

    try:
        hdu_list.writeto(out)
    except IOError:
        if clobber:
            os.remove(out)
            hdu_list.writeto(out)
        else:
            raise
    hdu_list.close()

    if naxis > 2:
        m_data = np.memmap(m_data_path,
                           dtype=np.float32,
                           mode='r',
                           shape=data_shape)
        m_xcoords = np.memmap(m_xcoords_path,
                              dtype=np.float32,
                              mode='r',
                              shape=xcoords_shape)
        m_ycoords = np.memmap(m_ycoords_path,
                              dtype=np.float32,
                              mode='r',
                              shape=ycoords_shape)
        m_energies = np.memmap(m_energies_path,
                               dtype=np.float32,
                               mode='r',
                               shape=energies_shape)

        flux = integrateMapCube(m_data, m_xcoords, m_ycoords, m_energies)

        del m_data
        del m_xcoords
        del m_ycoords
        del m_energies
        del xcoords
        del ycoords
        return out, flux
    else:
        return out
Example #49
0
    def _get_components_and_classes(self):

        # The aim of this function is to return whatever is needed for
        # world_axis_object_components and world_axis_object_classes. It's easier
        # to figure it out in one go and then return the values and let the
        # properties return part of it.

        # Since this method might get called quite a few times, we need to cache
        # it. We start off by defining a hash based on the attributes of the
        # WCS that matter here (we can't just use the WCS object as a hash since
        # it is mutable)
        wcs_hash = (self.naxis, list(self.wcs.ctype), list(self.wcs.cunit),
                    self.wcs.radesys, self.wcs.specsys, self.wcs.equinox,
                    self.wcs.dateobs, self.wcs.lng, self.wcs.lat)

        # If the cache is present, we need to check that the 'hash' matches.
        if getattr(self, '_components_and_classes_cache', None) is not None:
            cache = self._components_and_classes_cache
            if cache[0] == wcs_hash:
                return cache[1]
            else:
                self._components_and_classes_cache = None

        # Avoid circular imports by importing here
        from astropy.wcs.utils import wcs_to_celestial_frame
        from astropy.coordinates import SkyCoord, EarthLocation
        from astropy.time.formats import FITS_DEPRECATED_SCALES
        from astropy.time import Time, TimeDelta

        components = [None] * self.naxis
        classes = {}

        # Let's start off by checking whether the WCS has a pair of celestial
        # components

        if self.has_celestial:

            try:
                celestial_frame = wcs_to_celestial_frame(self)
            except ValueError:
                # Some WCSes, e.g. solar, can be recognized by WCSLIB as being
                # celestial but we don't necessarily have frames for them.
                celestial_frame = None
            else:

                kwargs = {}
                kwargs['frame'] = celestial_frame
                kwargs['unit'] = u.deg

                classes['celestial'] = (SkyCoord, (), kwargs)

                components[self.wcs.lng] = ('celestial', 0,
                                            'spherical.lon.degree')
                components[self.wcs.lat] = ('celestial', 1,
                                            'spherical.lat.degree')

        # Next, we check for spectral components

        if self.has_spectral:

            # Find index of spectral coordinate
            ispec = self.wcs.spec
            ctype = self.wcs.ctype[ispec][:4]
            ctype = ctype.upper()

            kwargs = {}

            # Determine observer location and velocity

            # TODO: determine how WCS standard would deal with observer on a
            # spacecraft far from earth. For now assume the obsgeo parameters,
            # if present, give the geocentric observer location.

            if np.isnan(self.wcs.obsgeo[0]):
                observer = None
            else:

                earth_location = EarthLocation(*self.wcs.obsgeo[:3], unit=u.m)
                obstime = Time(self.wcs.mjdobs,
                               format='mjd',
                               scale='utc',
                               location=earth_location)
                observer_location = SkyCoord(
                    earth_location.get_itrs(obstime=obstime))

                if self.wcs.specsys in VELOCITY_FRAMES:
                    frame = VELOCITY_FRAMES[self.wcs.specsys]
                    observer = observer_location.transform_to(frame)
                    if isinstance(frame, str):
                        observer = attach_zero_velocities(observer)
                    else:
                        observer = update_differentials_to_match(
                            observer_location,
                            VELOCITY_FRAMES[self.wcs.specsys],
                            preserve_observer_frame=True)
                elif self.wcs.specsys == 'TOPOCENT':
                    observer = attach_zero_velocities(observer_location)
                else:
                    raise NotImplementedError(
                        f'SPECSYS={self.wcs.specsys} not yet supported')

            # Determine target

            # This is tricker. In principle the target for each pixel is the
            # celestial coordinates of the pixel, but we then need to be very
            # careful about SSYSOBS which is tricky. For now, we set the
            # target using the reference celestial coordinate in the WCS (if
            # any).

            if self.has_celestial and celestial_frame is not None:

                # NOTE: celestial_frame was defined higher up

                # NOTE: we set the distance explicitly to avoid warnings in SpectralCoord

                target = SkyCoord(self.wcs.crval[self.wcs.lng] *
                                  self.wcs.cunit[self.wcs.lng],
                                  self.wcs.crval[self.wcs.lat] *
                                  self.wcs.cunit[self.wcs.lat],
                                  frame=celestial_frame,
                                  distance=1000 * u.kpc)

                target = attach_zero_velocities(target)

            else:

                target = None

            # SpectralCoord does not work properly if either observer or target
            # are not convertible to ICRS, so if this is the case, we (for now)
            # drop the observer and target from the SpectralCoord and warn the
            # user.

            if observer is not None:
                try:
                    observer.transform_to(ICRS())
                except Exception:
                    warnings.warn(
                        'observer cannot be converted to ICRS, so will '
                        'not be set on SpectralCoord', AstropyUserWarning)
                    observer = None

            if target is not None:
                try:
                    target.transform_to(ICRS())
                except Exception:
                    warnings.warn(
                        'target cannot be converted to ICRS, so will '
                        'not be set on SpectralCoord', AstropyUserWarning)
                    target = None

            # NOTE: below we include Quantity in classes['spectral'] instead
            # of SpectralCoord - this is because we want to also be able to
            # accept plain quantities.

            if ctype == 'ZOPT':

                def spectralcoord_from_redshift(redshift):
                    return SpectralCoord((redshift + 1) * self.wcs.restwav,
                                         unit=u.m,
                                         observer=observer,
                                         target=target)

                def redshift_from_spectralcoord(spectralcoord):
                    # TODO: check target is consistent
                    if observer is None:
                        warnings.warn(
                            'No observer defined on WCS, SpectralCoord '
                            'will be converted without any velocity '
                            'frame change', AstropyUserWarning)
                        return spectralcoord.to_value(
                            u.m) / self.wcs.restwav - 1.
                    else:
                        return spectralcoord.in_observer_velocity_frame(
                            observer).to_value(u.m) / self.wcs.restwav - 1.

                classes['spectral'] = (u.Quantity, (), {},
                                       spectralcoord_from_redshift)
                components[self.wcs.spec] = ('spectral', 0,
                                             redshift_from_spectralcoord)

            elif ctype == 'BETA':

                def spectralcoord_from_beta(beta):
                    return SpectralCoord(beta * C_SI,
                                         unit=u.m / u.s,
                                         doppler_convention='relativistic',
                                         doppler_rest=self.wcs.restwav * u.m,
                                         observer=observer,
                                         target=target)

                def beta_from_spectralcoord(spectralcoord):
                    # TODO: check target is consistent
                    doppler_equiv = u.doppler_relativistic(self.wcs.restwav *
                                                           u.m)
                    if observer is None:
                        warnings.warn(
                            'No observer defined on WCS, SpectralCoord '
                            'will be converted without any velocity '
                            'frame change', AstropyUserWarning)
                        return spectralcoord.to_value(u.m / u.s,
                                                      doppler_equiv) / C_SI
                    else:
                        return spectralcoord.in_observer_velocity_frame(
                            observer).to_value(u.m / u.s, doppler_equiv) / C_SI

                classes['spectral'] = (u.Quantity, (), {},
                                       spectralcoord_from_beta)
                components[self.wcs.spec] = ('spectral', 0,
                                             beta_from_spectralcoord)

            else:

                kwargs['unit'] = self.wcs.cunit[ispec]

                if self.wcs.restfrq > 0:
                    if ctype == 'VELO':
                        kwargs['doppler_convention'] = 'relativistic'
                        kwargs['doppler_rest'] = self.wcs.restfrq * u.Hz
                    elif ctype == 'VRAD':
                        kwargs['doppler_convention'] = 'radio'
                        kwargs['doppler_rest'] = self.wcs.restfrq * u.Hz
                    elif ctype == 'VOPT':
                        kwargs['doppler_convention'] = 'optical'
                        kwargs['doppler_rest'] = self.wcs.restwav * u.m

                def spectralcoord_from_value(value):
                    return SpectralCoord(value,
                                         observer=observer,
                                         target=target,
                                         **kwargs)

                def value_from_spectralcoord(spectralcoord):
                    # TODO: check target is consistent
                    if observer is None:
                        warnings.warn(
                            'No observer defined on WCS, SpectralCoord '
                            'will be converted without any velocity '
                            'frame change', AstropyUserWarning)
                        return spectralcoord.to_value(**kwargs)
                    else:
                        return spectralcoord.in_observer_velocity_frame(
                            observer).to_value(**kwargs)

                classes['spectral'] = (u.Quantity, (), {},
                                       spectralcoord_from_value)
                components[self.wcs.spec] = ('spectral', 0,
                                             value_from_spectralcoord)

        # We can then make sure we correctly return Time objects where appropriate
        # (https://www.aanda.org/articles/aa/pdf/2015/02/aa24653-14.pdf)

        if 'time' in self.world_axis_physical_types:

            multiple_time = self.world_axis_physical_types.count('time') > 1

            for i in range(self.naxis):

                if self.world_axis_physical_types[i] == 'time':

                    if multiple_time:
                        name = f'time.{i}'
                    else:
                        name = 'time'

                    # Initialize delta
                    reference_time_delta = None

                    # Extract time scale
                    scale = self.wcs.ctype[i].lower()

                    if scale == 'time':
                        if self.wcs.timesys:
                            scale = self.wcs.timesys.lower()
                        else:
                            scale = 'utc'

                    # Drop sub-scales
                    if '(' in scale:
                        pos = scale.index('(')
                        scale, subscale = scale[:pos], scale[pos + 1:-1]
                        warnings.warn(
                            f'Dropping unsupported sub-scale '
                            f'{subscale.upper()} from scale {scale.upper()}',
                            UserWarning)

                    # TODO: consider having GPS as a scale in Time
                    # For now GPS is not a scale, we approximate this by TAI - 19s
                    if scale == 'gps':
                        reference_time_delta = TimeDelta(19, format='sec')
                        scale = 'tai'

                    elif scale.upper() in FITS_DEPRECATED_SCALES:
                        scale = FITS_DEPRECATED_SCALES[scale.upper()]

                    elif scale not in Time.SCALES:
                        raise ValueError(
                            f'Unrecognized time CTYPE={self.wcs.ctype[i]}')

                    # Determine location
                    trefpos = self.wcs.trefpos.lower()

                    if trefpos.startswith('topocent'):
                        # Note that some headers use TOPOCENT instead of TOPOCENTER
                        if np.any(np.isnan(self.wcs.obsgeo[:3])):
                            warnings.warn(
                                'Missing or incomplete observer location '
                                'information, setting location in Time to None',
                                UserWarning)
                            location = None
                        else:
                            location = EarthLocation(*self.wcs.obsgeo[:3],
                                                     unit=u.m)
                    elif trefpos == 'geocenter':
                        location = EarthLocation(0, 0, 0, unit=u.m)
                    elif trefpos == '':
                        location = None
                    else:
                        # TODO: implement support for more locations when Time supports it
                        warnings.warn(
                            f"Observation location '{trefpos}' is not "
                            "supported, setting location in Time to None",
                            UserWarning)
                        location = None

                    reference_time = Time(np.nan_to_num(self.wcs.mjdref[0]),
                                          np.nan_to_num(self.wcs.mjdref[1]),
                                          format='mjd',
                                          scale=scale,
                                          location=location)

                    if reference_time_delta is not None:
                        reference_time = reference_time + reference_time_delta

                    def time_from_reference_and_offset(offset):
                        if isinstance(offset, Time):
                            return offset
                        return reference_time + TimeDelta(offset, format='sec')

                    def offset_from_time_and_reference(time):
                        return (time - reference_time).sec

                    classes[name] = (Time, (), {},
                                     time_from_reference_and_offset)
                    components[i] = (name, 0, offset_from_time_and_reference)

        # Fallback: for any remaining components that haven't been identified, just
        # return Quantity as the class to use

        for i in range(self.naxis):
            if components[i] is None:
                name = self.wcs.ctype[i].split('-')[0].lower()
                if name == '':
                    name = 'world'
                while name in classes:
                    name += "_"
                classes[name] = (u.Quantity, (), {'unit': self.wcs.cunit[i]})
                components[i] = (name, 0, 'value')

        # Keep a cached version of result
        self._components_and_classes_cache = wcs_hash, (components, classes)

        return components, classes
Example #50
0
def make_cutouts(data, catalog, wcs=None, origin=0, verbose=True):
    """Make cutouts of catalog targets from a 2D image array.
    Expects input image WCS to be in the TAN projection.

    Parameters
    ----------
    data : 2D `~numpy.ndarray` or `~astropy.nddata.NDData`
        The 2D cutout array.
    catalog : `~astropy.table.table.Table`
        Catalog table defining the sources to cut out. Must contain
        unit information as the cutout tool does not assume default units.
    wcs : `~astropy.wcs.wcs.WCS`
        WCS if the input image is `~numpy.ndarray`.
    origin : int
        Whether SkyCoord.from_pixel should use 0 or 1-based pixel coordinates.
    verbose : bool
        Print extra info. Default is `True`.

    Returns
    -------
    cutouts : list
        A list of NDData. If cutout failed for a target,
       `None` will be added as a place holder. Output WCS
       will in be in Tan projection.

    Notes
    -----
    The input Catalog must have the following columns, which MUST have
    units information where applicable:

        * ``'id'`` - ID string; no unit necessary.
        * ``'coords'`` - SkyCoord (Overrides ra, dec, x and y columns).
        * ``'ra'`` or ``'x'``- RA (angular units e.g., deg, H:M:S, arcsec etc..)
          or pixel x position (only in `~astropy.units.pix`).
        * ``'dec'`` or ``'y'`` - Dec (angular units e.g., deg, D:M:S, arcsec etc..)
          or pixel y position (only in `~astropy.units.pix`).
        * ``'cutout_width'`` - Cutout width (e.g., in arcsec, pix).
        * ``'cutout_height'`` - Cutout height (e.g., in arcsec, pix).

    Optional columns:
        * ``'cutout_pa'`` - Cutout angle (e.g., in deg, arcsec). This is only
          use if user chooses to rotate the cutouts. Positive value
          will result in a clockwise rotation.

    If saved to fits, cutouts are organized as follows:
        <output_dir>/
            <id>.fits

    Each cutout image is a simple single-extension FITS with updated WCS.
    Its header has the following special keywords:

        * ``OBJ_RA`` - RA of the cutout object in degrees.
        * ``OBJ_DEC`` - DEC of the cutout object in degrees.
        * ``OBJ_ROT`` - Rotation of cutout object in degrees.

    """
    # Do not rotate if column is missing.
    if 'cutout_pa' in catalog.colnames:
        if catalog['cutout_pa'].unit is None:
            raise u.UnitsError("Units not specified for cutout_pa.")
        apply_rotation = True
    else:
        apply_rotation = False

    # Optional dependencies...
    if apply_rotation:
        try:
            from reproject.interpolation.high_level import reproject_interp
        except ImportError as e:
            raise ImportError("Optional requirement not met: " + e.msg)

    # Search for wcs:
    if isinstance(data, NDData):
            if wcs is not None:
                raise Exception("Ambiguous: WCS defined in NDData and parameters.")
            wcs = data.wcs
    elif not isinstance(data, np.ndarray):
        raise TypeError("Input image should be a 2D `~numpy.ndarray` "
                        "or `~astropy.nddata.NDData")
    elif wcs is None:
        raise Exception("WCS information was not provided.")

    if wcs.wcs.ctype[0] != 'RA---TAN' or  wcs.wcs.ctype[1] != 'DEC--TAN':
        raise Exception("Expected  WCS to be in the TAN projection.")

    # Calculate the pixel scale of input image:
    pixel_scales = proj_plane_pixel_scales(wcs)
    pixel_scale_width = pixel_scales[0] * u.Unit(wcs.wcs.cunit[0]) / u.pix
    pixel_scale_height = pixel_scales[1] * u.Unit(wcs.wcs.cunit[1]) / u.pix

    # Check if `SkyCoord`s are available:
    if 'coords' in catalog.colnames:
        coords = catalog['coords']
        if not isinstance(coords, SkyCoord):
            raise TypeError('The coords column is not a SkyCoord')
    elif 'ra' in catalog.colnames and 'dec' in catalog.colnames:
        if 'x' in catalog.colnames and 'y' in catalog.colnames:
            raise Exception("Ambiguous catalog: Both (ra, dec) and pixel positions provided.")
        if catalog['ra'].unit is None or catalog['dec'].unit is None:
            raise u.UnitsError("Units not specified for ra and/or dec columns.")
        coords = SkyCoord(catalog['ra'], catalog['dec'], unit=(catalog['ra'].unit,
                                                               catalog['dec'].unit))
    elif 'x' in catalog.colnames and 'y' in catalog.colnames:
        coords = SkyCoord.from_pixel(catalog['x'].astype(float), catalog['y'].astype(float), wcs, origin=origin)
    else:
        try:
            coords = SkyCoord.guess_from_table(catalog)
        except Exception as e:
            raise e

    coords = coords.transform_to(wcs_to_celestial_frame(wcs))

    # Figure out cutout size:
    if 'cutout_width' in catalog.colnames:
        if catalog['cutout_width'].unit is None:
            raise u.UnitsError("Units not specified for cutout_width.")
        if catalog['cutout_width'].unit == u.pix:
            width = catalog['cutout_width'].astype(float)  # pix
        else:
            width = (catalog['cutout_width'] / pixel_scale_width).decompose().value  # pix
    else:
        raise Exception("cutout_width column not found in catalog.")

    if 'cutout_height' in catalog.colnames:
        if catalog['cutout_height'].unit is None:
            raise u.UnitsError("Units not specified for cutout_height.")
        if catalog['cutout_height'].unit == u.pix:
            height = catalog['cutout_height'].astype(float)  # pix
        else:
            height = (catalog['cutout_height'] / pixel_scale_height).decompose().value  # pix
    else:
        raise Exception("cutout_height column not found in catalog.")

    cutcls = partial(Cutout2D, data.data, wcs=wcs, mode='partial')
    cutouts = []
    for position, x_pix, y_pix, row in zip(coords, width, height, catalog):

        if apply_rotation:
            pix_rot = row['cutout_pa'].to(u.degree).value

            # Construct new rotated WCS:
            cutout_wcs = WCS(naxis=2)
            cutout_wcs.wcs.ctype = ['RA---TAN', 'DEC--TAN']
            cutout_wcs.wcs.crval = [position.ra.deg, position.dec.deg]
            cutout_wcs.wcs.crpix = [(x_pix + 1) * 0.5, (y_pix + 1) * 0.5]

            try:
                cutout_wcs.wcs.cd = wcs.wcs.cd
                cutout_wcs.rotateCD(-pix_rot)
            except AttributeError:
                cutout_wcs.wcs.cdelt = wcs.wcs.cdelt
                cutout_wcs.wcs.crota = [0, -pix_rot]

            cutout_hdr = cutout_wcs.to_header()

            # Rotate the image using reproject
            try:
                cutout_arr = reproject_interp(
                    (data, wcs), cutout_hdr, shape_out=(math.floor(y_pix + math.copysign(0.5, y_pix)),
                                                        math.floor(x_pix + math.copysign(0.5, x_pix))), order=1)
            except Exception:
                if verbose:
                    log.info('reproject failed: '
                             'Skipping {0}'.format(row['id']))
                cutouts.append(None)
                continue

            cutout_arr = cutout_arr[0]  # Ignore footprint
            cutout_hdr['OBJ_ROT'] = (pix_rot, 'Cutout rotation in degrees')
        else:
            # Make cutout or handle exceptions by adding None to output list
            try:
                cutout = cutcls(position, size=(y_pix, x_pix))
            except NoConvergence:
                if verbose:
                    log.info('WCS solution did not converge: '
                             'Skipping {0}'.format(row['id']))
                cutouts.append(None)
                continue
            except NoOverlapError:
                if verbose:
                    log.info('Cutout is not on image: '
                             'Skipping {0}'.format(row['id']))
                cutouts.append(None)
                continue
            else:
                cutout_hdr = cutout.wcs.to_header()
                cutout_arr = cutout.data

        # If cutout result is empty, skip that target
        if np.array_equiv(cutout_arr, 0):
            if verbose:
                log.info('No data in cutout: Skipping {0}'.format(row['id']))
            cutouts.append(None)
            continue

        # Finish constructing header.
        cutout_hdr['OBJ_RA'] = (position.ra.deg, 'Cutout object RA in deg')
        cutout_hdr['OBJ_DEC'] = (position.dec.deg, 'Cutout object DEC in deg')

        cutouts.append(NDData(data=cutout_arr, wcs=WCS(cutout_hdr), meta=cutout_hdr))

    return cutouts