世界坐标系的共享Python接口

背景

这个 WCS 类实现了在FITS文件中表示世界坐标系的最常见的“标准”,但它不能表示任意复杂的转换,并且对于如何使用标准beyond FITS文件没有达成一致意见。因此,存在其他世界坐标系转换方法,例如 gwcs 正在为詹姆斯·韦伯太空望远镜开发的软件包(也适用于其他数据)。

由于Astropy项目的目标之一是改善包之间的互操作性,我们已经为Python中使用的世界坐标系对象协作定义了一个标准化的应用程序编程接口(API)。该API在Astropy Enhancements(APE)14: A shared Python interface for World Coordinate Systems .

核心astropy包提供了基类,这些基类定义了在 astropy.wcs.wcsapi 模块,这些都列在 确认和许可 下面部分。

概述

虽然API的全部细节和动机在APE14中有详细说明,但本文档总结了直接在astropy核心包中实现的元素。高级界面可能是普通用户最感兴趣的。尤其是,最重要的方法是 pixel_to_world()world_to_pixel() 方法。它们提供了WCS的基本元素:映射到世界坐标系和从世界坐标系映射。其余部分通常提供有关 kind 关于WCS结构的世界坐标或类似信息。

更详细地说,这里实现的关键类是提供主用户界面的高级类 (BaseHighLevelWCS 和子类),以及较低级别的接口 (BaseLowLevelWCS 和子类)。这些可以是不同的对象 or 同一个。对于FITS-WCS WCS 用于FITS-WCS的对象遵循这两个接口,允许对已经包含FITS-WCS的文件立即使用此API。更具体的例子概述如下。

基本用法

让我们先看一下WCS的共享Python接口,使用一个带有两个天轴(赤经和赤纬)的简单图像:

>>> from astropy.wcs import WCS
>>> from astropy.utils.data import get_pkg_data_filename
>>> from astropy.io import fits
>>> filename = get_pkg_data_filename('galactic_center/gc_2mass_k.fits')  
>>> hdu = fits.open(filename)[0]  
>>> wcs = WCS(hdu.header)  
>>> wcs  
WCS Keywords
Number of WCS axes: 2
CTYPE : 'RA---TAN'  'DEC--TAN'
CRVAL : 266.4  -28.93333
CRPIX : 361.0  360.5
NAXIS : 721  720

我们可以检查变换中有多少像素和世界坐标轴,以及WCS应用于的数据的形状:

>>> wcs.pixel_n_dim  
2
>>> wcs.world_n_dim  
2
>>> wcs.array_shape  
(720, 721)

请注意,数组形状应与数据的形状匹配:

>>> hdu.data.shape  
(720, 721)

如上所述 像素约定和定义 ,通常认为图像的y轴是第一维度,而图像的x轴是第二维度。因此 array_shape 返回中的形状 相反的 排序到FITS标题中的NAXIS关键字(在FITS-WCS的情况下)。如果您对相反顺序的数据形状感兴趣(在FITS-WCS的情况下,这将与NAXIS顺序相匹配),那么您可以使用 pixel_shape ::

>>> wcs.pixel_shape  
(721, 720)

现在让我们检查每个轴的物理类型:

>>> wcs.world_axis_physical_types  
['pos.eq.ra', 'pos.eq.dec']

这确实是一个有两个天轴的图像。

新接口的主要部分定义了转换坐标的标准方法。最方便的方法是使用高级方法 pixel_to_world()world_to_pixel() ,可以直接转换为天体:

>>> coord = wcs.pixel_to_world([1, 2], [4, 3])  
>>> coord  
<SkyCoord (FK5: equinox=2000.0): (ra, dec) in deg
    [(266.97242993, -29.42584415), (266.97084321, -29.42723968)]>

同样,我们可以把天体物体转换回来——我们可以通过创建星系坐标来测试这一点,这些坐标将自动转换:

>>> from astropy.coordinates import SkyCoord
>>> coord = SkyCoord('00h00m00s +00d00m00s', frame='galactic')
>>> pixels = wcs.world_to_pixel(coord)  
>>> pixels  
(array(356.85179997), array(357.45340331))

如果您希望使用这些像素坐标索引原始数据,请确保使用 world_to_array_index() 它以正确的顺序返回坐标以索引Numpy数组,并舍入到最接近的整数值:

>>> index = wcs.world_to_array_index(coord)  
>>> index  
(357, 357)
>>> hdu.data[index]  
563.7532

高级用法

现在让我们来看看光谱立方体的WCS(两个天轴和一个光谱轴):

>>> filename = get_pkg_data_filename('l1448/l1448_13co.fits')  
>>> hdu = fits.open(filename)[0]  
>>> wcs = WCS(hdu.header)  
>>> wcs  
WCS Keywords
Number of WCS axes: 3
CTYPE : 'RA---SFL'  'DEC--SFL'  'VOPT'
CRVAL : 57.6599999999  0.0  -9959.44378305
CRPIX : -799.0  -4741.913  -187.0
PC1_1 PC1_2 PC1_3  : 1.0  0.0  0.0
PC2_1 PC2_2 PC2_3  : 0.0  1.0  0.0
PC3_1 PC3_2 PC3_3  : 0.0  0.0  1.0
CDELT : -0.006388889  0.006388889  66.42361
NAXIS : 105  105  53

如前所述,我们可以检查变换中有多少像素轴和世界轴,以及WCS应用于的数据的形状,以及每个轴的物理类型:

>>> wcs.pixel_n_dim  
3
>>> wcs.world_n_dim  
3
>>> wcs.array_shape  
(53, 105, 105)
>>> wcs.world_axis_physical_types  
['pos.eq.ra', 'pos.eq.dec', 'spect.dopplerVeloc.opt']

这确实是一个光谱立方体,有RA/Dec和一个速度轴。

如前所述,我们可以在像素和高层天体之间进行转换:

>>> celestial, spectral = wcs.pixel_to_world([1, 2], [4, 3], [2, 3])  
>>> celestial  
<SkyCoord (ICRS): (ra, dec) in deg
    [(51.73115731, 30.32750025), (51.72414268, 30.32111136)]>
>>> spectral  
<SpectralCoord
   (target: <ICRS Coordinate: (ra, dec, distance) in (deg, deg, kpc)
                (57.66, 0., 1000.)
             (pm_ra_cosdec, pm_dec, radial_velocity) in (mas / yr, mas / yr, km / s)
                (0., 0., 0.)>)
  [2661.04211695, 2727.46572695] m / s>

后面是:

>>> from astropy import units as u
>>> coord = SkyCoord('03h26m36.4901s +30d45m22.2012s')
>>> pixels = wcs.world_to_pixel(coord, 3000 * u.m / u.s)  
>>> pixels  
(array(8.11341207), array(71.0956641), array(7.10297292))

和之前一样,我们可以使用以下方法索引数组值:

>>> index = wcs.world_to_array_index(coord, 3000 * u.m / u.s)  
>>> index  
(7, 71, 8)
>>> hdu.data[index]  
0.22262384

如果您有兴趣在不使用高级astropy对象的情况下将世界值转换为简单的Python标量或Numpy数组,可以使用以下方法 pixel_to_world_values() 做这个-看 确认和许可 有关详细信息,请参阅。

扩展FITS-WCS中的物理类型

如上图所示, world_axis_physical_types 属性返回每个轴的物理类型列表。对于FITS-WCS,这是由标题中的CTYPE值确定的。在物理类型未知的情况下, None 返回。但是,可以使用 custom_ctype_to_ucd_mapping 上下文管理器。考虑具有以下CTYPE的WCS::

>>> from astropy.wcs import WCS
>>> wcs = WCS(naxis=1)
>>> wcs.wcs.ctype = ['SPAM']
>>> wcs.world_axis_physical_types
[None]

我们可以指定对于这个CTYPE,物理类型应该是 'food.spam' ::

>>> from astropy.wcs.wcsapi.fitswcs import custom_ctype_to_ucd_mapping
>>> with custom_ctype_to_ucd_mapping({'SPAM': 'food.spam'}):
...     wcs.world_axis_physical_types
['food.spam']

WCS对象的切片

在处理带有WCS信息的数据时,一个常见的操作是对WCS进行切片-这可以是为数据的一个子区域提取WCS,保留总的维度数(例如,从图像中剪切出来的),也可以降低数据和相关WCS的维数(例如从光谱中提取切片)立方体)。

这个 SlicedLowLevelWCS 类可用于切片任何符合 BaseLowLevelWCS 应用程序编程接口。为了证明这一点,让我们从一个光谱立方体文件开始:

>>> filename = get_pkg_data_filename('l1448/l1448_13co.fits')  
>>> wcs = WCS(fits.getheader(filename, ext=0))  

这个 wcs 对象是的实例 WCS 符合 BaseLowLevelWCS 应用程序编程接口。然后我们可以使用 SlicedLowLevelWCS 类来分割多维数据集:

>>> from astropy.wcs.wcsapi import SlicedLowLevelWCS
>>> slices = [10, slice(30, 100), slice(30, 100)]  
>>> subwcs = SlicedLowLevelWCS(wcs, slices=slices)  

这个 slices 参数接受切片、整数值和省略号的任意组合,这些组合通常会对Numpy数组进行切片。在上面的例子中,我们提取了一个光谱切片,在这个切片中我们提取了天空中的一个子区域。

如果您正在实现自己的WCS类,那么可以选择实现 __getitem__ 内部使用 SlicedLowLevelWCS . 事实上 WCS 类执行此操作-上面的示例可以更简洁地编写为:

>>> wcs[10, 30:100, 30:100]  
<...>
SlicedFITSWCS Transformation

This transformation has 2 pixel and 2 world dimensions

Array shape (Numpy order): (70, 70)

Pixel Dim  Axis Name  Data size  Bounds
        0  None              70  None
        1  None              70  None

World Dim  Axis Name  Physical Type  Units
        0  None       pos.eq.ra      deg
        1  None       pos.eq.dec     deg

Correlation between pixel and world axes:

           Pixel Dim
World Dim    0    1
        0  yes  yes
        1  yes  yes

这个切片基础设施能够处理具有相关轴的WCS对象的切片-在这种情况下,您可能最终得到具有不同数量像素和世界坐标的WCS。例如,如果我们对光谱立方体进行切片以提取与光谱切片图像平面中的一行相对应的1D数据集,则最终的WCS将具有一个像素维度和两个世界维度(因为RA/Dec随提取的1D切片而变化):

>>> wcs[10, 40, :]  
<...>
SlicedFITSWCS Transformation

This transformation has 1 pixel and 2 world dimensions

Array shape (Numpy order): (105,)

Pixel Dim  Axis Name  Data size  Bounds
        0  None             105  None

World Dim  Axis Name  Physical Type  Units
        0  None       pos.eq.ra      deg
        1  None       pos.eq.dec     deg

Correlation between pixel and world axes:

           Pixel Dim
World Dim    0
        0  yes
        1  yes