表格数据

在本章中,我们将讨论表HDU中的数据组件。表总是在扩展HDU中,而不是在主HDU中。

FITS标准中有两种表:二进制表和ASCII表。二进制表在存储方面更经济,在数据访问和操作方面更快。ASCII表以“人类可读”的形式存储数据,因此需要将ASCII文本解析为数值,因此会占用更多的存储空间和更多的处理时间。

注解

如果您想以FITS格式读写一个表,那么最方便的方法通常是通过高级 统一文件读写接口 . 具体请参见 Unified I/O FITS 部分。

将数据作为记录数组

什么是记录数组?

记录数组是包含异构数据类型的记录(即行)的数组。记录数组可以通过NumPy库中的records模块获得。

下面是一个示例记录数组:

>>> import numpy as np
>>> bright = np.rec.array([(1,'Sirius', -1.45, 'A1V'),
...                        (2,'Canopus', -0.73, 'F0Ib'),
...                        (3,'Rigil Kent', -0.1, 'G2V')],
...                       formats='int16,a20,float32,a10',
...                       names='order,name,mag,Sp')

在本例中,有三条记录(行)和四个字段(列)。第一个字段是短整数,第二个字段是字符串(长度为20),第三个字段是浮点数,第四个字段是字符串(长度为10)。每个记录具有相同(异构)的数据结构。

用于FITS表的底层数据结构是一个名为 FITS_rec 它是 numpy.recarray . 一 FITS_rec 可以使用与上面示例中为普通重排提供的相同初始化格式直接实例化。您还可以实例化一个新的 FITS_rec 从列表中 astropy.io.fits.Column 对象使用 FITS_rec.from_columns() 类方法。它的语义与 BinTableHDU.from_columns()TableHDU.from_columns() ,但它只返回一个实际的FITSu rec数组,而不是整个HDU对象。

表的元数据

FITS表HDU中的数据基本上是一个带有附加属性的记录数组。元数据(即关于表数据的信息)存储在头中。例如,关键字TFORM1包含第一个字段的格式,TTYPE2表示第二个字段的名称,等等。NAXIS2表示记录数(行),TFIELDS表示字段数(列)。对于FITS表格,最大字段数为999。TFORM中指定的数据类型由二进制表的字母代码和ASCII表的类似Fortran的格式字符串表示。请注意,这与构造记录数组时的格式规范不同。

读取FITS表格

就像图像一样 .data 表HDU的属性包含该表的数据。

例子

阅读FITS表格:

>>> from astropy.io import fits
>>> fits_table_filename = fits.util.get_testdata_filepath('btable.fits')

>>> hdul = fits.open(fits_table_filename)  # open a FITS file
>>> data = hdul[1].data  # assume the first extension is a table
>>> # show the first two rows
>>> first_two_rows = data[:2]
>>> first_two_rows  
[(1, 'Sirius', -1.45000005, 'A1V') (2, 'Canopus', -0.73000002, 'F0Ib')]
>>> # show the values in field "mag"
>>> magnitudes = data['mag']
>>> magnitudes  
array([-1.45000005, -0.73000002, -0.1       ], dtype=float32)
>>> # columns can be referenced by index too
>>> names = data.field(1)
>>> names.tolist() 
['Sirius', 'Canopus', 'Rigil Kent']
>>> hdul.close()

请注意 astropy ,当使用 field() 方法时,它是0索引的,而头关键字(如TFORM)中的后缀是1索引的。所以, data.field(0) 列中的数据,其名称在TTYPE1中指定,格式为TFORM1。

警告

FITS格式允许表格列采用零宽度数据格式,例如 '0D' . 对于该列不包含数据的文件,这可能是为了节省空间。在这种文件中,访问表数据时会省略零宽度列,因此在使用 field() 方法。因此,如果希望遇到包含零宽度列的文件,建议按名称而不是按索引访问字段。

表操作

选择表中的记录

与图像数据一样,我们可以使用相同的“mask array”思想从表中挑选出所需的记录,并从中创建一个新的表。

实例

假设表的第二个字段的名称为“magnity”,则生成一个输出表,其中包含输入表中幅值>-0.5的所有记录:

>>> with fits.open(fits_table_filename) as hdul:
...     data = hdul[1].data
...     mask = data['mag'] > -0.5
...     newdata = data[mask]
...     hdu = fits.BinTableHDU(data=newdata)
...     hdu.writeto('newtable.fits')

还可以就地更新来自HDU对象的数据:

>>> with fits.open(fits_table_filename) as hdul:
...     hdu = hdul[1]
...     mask = hdu.data['mag'] > -0.5
...     hdu.data = hdu.data[mask]
...     hdu.writeto('newtable2.fits')

合并表

合并不同的表在 astropy .

实例

合并输入表的列定义:

>>> fits_other_table_filename = fits.util.get_testdata_filepath('table.fits')

>>> with fits.open(fits_table_filename) as hdul1:
...     with fits.open(fits_other_table_filename) as hdul2:
...         new_columns = hdul1[1].columns + hdul2[1].columns
...         new_hdu = fits.BinTableHDU.from_columns(new_columns)
>>> new_columns
ColDefs(
        name = 'order'; format = 'I'
        name = 'name'; format = '20A'
        name = 'mag'; format = 'E'
        name = 'Sp'; format = '10A'
        name = 'target'; format = '20A'
        name = 'V_mag'; format = 'E'
    )

将表中输入字段的个数相加。用户必须确保输入表不共享任何公共字段名。输出表中的记录数将是所有输入表中最大的记录数。最初较短表格的扩展插槽将为零(或空白)填充。

此示例的另一个版本可用于将新列附加到表中。用新列更新现有表通常比实际操作更困难,但您可以通过使用现有表中的列加上新列创建一个新表来将列“附加”到表中:

>>> with fits.open(fits_table_filename) as hdul:
...     orig_table = hdul[1].data
...     orig_cols = orig_table.columns
>>> new_cols = fits.ColDefs([
...     fits.Column(name='NEWCOL1', format='D',
...                 array=np.zeros(len(orig_table))),
...     fits.Column(name='NEWCOL2', format='D',
...                 array=np.zeros(len(orig_table)))])
>>> hdu = fits.BinTableHDU.from_columns(orig_cols + new_cols)

现在 newtable.fits 包含一个带有原始表的新表,加上两个用零填充的新列。

附加表格

将一个表追加到另一个表稍显困难,因为这两个表可能具有不同的字段属性。

实例

这里,第一个示例是按字段索引追加,第二个示例是按字段名称追加。在这两种情况下,输出表将继承第一个表的列属性(名称、格式等):

>>> with fits.open(fits_table_filename) as hdul1:
...     with fits.open(fits_table_filename) as hdul2:
...         nrows1 = hdul1[1].data.shape[0]
...         nrows2 = hdul2[1].data.shape[0]
...         nrows = nrows1 + nrows2
...         hdu = fits.BinTableHDU.from_columns(hdul1[1].columns, nrows=nrows)
...         for colname in hdul1[1].columns.names:
...             hdu.data[colname][nrows1:] = hdul2[1].data[colname]

表格中的缩放数据

表字段的数据(如图像)也可以缩放。在表中缩放比在图像中具有更广泛的含义。在图像中,物理数据是存储数据的简单线性转换。表字段也有这样一个结构,其中BSCALE和BZERO存储在头中,作为TSCALn和TZEROn。此外,布尔列和ASCII表的数字字段也是通用的“缩放”字段,但没有TSCAL和TZERO。

所有缩放字段,如图像大小写,将占用额外的内存空间以及处理。因此,如果需要高性能,请尽量减少缩放字段的使用。

所有的缩放都是为用户完成的,因此用户只能看到物理数据。因此,不必担心在物理列和存储列值之间来回伸缩。

创建配合表

列创建

要从头开始创建表,必须先创建单独的列。A Column 构造函数需要最少的列名和格式信息。以下是二进制表允许的所有格式的摘要:

FITS format code         Description                     8-bit bytes

L                        logical (Boolean)               1
X                        bit                             *
B                        Unsigned byte                   1
I                        16-bit integer                  2
J                        32-bit integer                  4
K                        64-bit integer                  8
A                        character                       1
E                        single precision floating point 4
D                        double precision floating point 8
C                        single precision complex        8
M                        double precision complex        16
P                        array descriptor                8
Q                        array descriptor                16

我们将在本章集中讨论二进制表。ASCII表将在后面的章节中讨论。不常用的X格式(位数组)和P格式(用于可变长度表)也将在后面的章节中讨论。

除了在构造 Column ,有许多可选参数可用于创建列。以下是这些参数及其对应的标题关键字和说明的列表:

Argument        Corresponding         Description
in Column()     header keyword

name            TTYPE                 column name
format          TFORM                 column format
unit            TUNIT                 unit
null            TNULL                 null value (only for B, I, and J)
bscale          TSCAL                 scaling factor for data
bzero           TZERO                 zero point for data scaling
disp            TDISP                 display format
dim             TDIM                  multi-dimensional array spec
start           TBCOL                 starting position for ASCII table
coord_type      TCTYP                 coordinate/axis type
coord_unit      TCUNI                 coordinate/axis unit
coord_ref_point TCRPX                 pixel coordinate of the reference point
coord_ref_value TCRVL                 coordinate value at reference point
coord_inc       TCDLT                 coordinate increment at reference point
time_ref_pos    TRPOS                 reference position for a time coordinate column
ascii                                 specifies a column for an ASCII table
array                                 the data of the column

实例

下面是一些列使用可选参数的各种组合:

>>> counts = np.array([312, 334, 308, 317])
>>> names = np.array(['NGC1', 'NGC2', 'NGC3', 'NGC4'])
>>> values = np.arange(2*2*4).reshape(4, 2, 2)
>>> col1 = fits.Column(name='target', format='10A', array=names)
>>> col2 = fits.Column(name='counts', format='J', unit='DN', array=counts)
>>> col3 = fits.Column(name='notes', format='A10')
>>> col4 = fits.Column(name='spectrum', format='10E')
>>> col5 = fits.Column(name='flag', format='L', array=[True, False, True, True])
>>> col6 = fits.Column(name='intarray', format='4I', dim='(2, 2)', array=values)

在本例中,格式是用FITS字母代码指定的。当(数字类型)字母代码前面有一个数字(>1),这意味着该字段中的每个单元格都是一维数组。在列“col4”的情况下,每个单元是一个由10个元素组成的数组(NumPy数组)。在列“col6”的情况下,使用“dim”参数,每个单元格都是2x2个元素的多维数组。

对于字符串字段,数字应该是 left 在创建二进制表时,应该是 正确的 创建ASCII表时。然而,由于这是一个常见的混淆,在创建二进制表时,这两种格式都是可以理解的(但是,请注意,在写入文件时,正确的格式将写入头中)。所以,对于列“col1”和“col3”,它们的每个单元格中都有10个字符。对于数字类型,数字不能在维度代码之前。

柱体构造完成后 BinTableHDU.from_columns() 类方法可以用来构造一个表HDU。我们可以通过列定义对象:

>>> coldefs = fits.ColDefs([col1, col2, col3, col4, col5, col6])
>>> hdu = fits.BinTableHDU.from_columns(coldefs)
>>> coldefs
ColDefs(
    name = 'target'; format = '10A'
    name = 'counts'; format = 'J'; unit = 'DN'
    name = 'notes'; format = '10A'
    name = 'spectrum'; format = '10E'
    name = 'flag'; format = 'L'
    name = 'intarray'; format = '4I'; dim = '(2, 2)'
)

或者直接使用 BinTableHDU.from_columns() 方法:

>>> hdu = fits.BinTableHDU.from_columns([col1, col2, col3, col4, col5, col6])
>>> hdu.columns
ColDefs(
    name = 'target'; format = '10A'
    name = 'counts'; format = 'J'; unit = 'DN'
    name = 'notes'; format = '10A'
    name = 'spectrum'; format = '10E'
    name = 'flag'; format = 'L'
    name = 'intarray'; format = '4I'; dim = '(2, 2)'
)

注解

熟悉旧版本的用户 astropy 会想知道发生了什么事 astropy.io.fits.new_table . BinTableHDU.from_columns() 以及它的ASCII表的同伴 TableHDU.from_columns() 它们接受的参数和它们的行为是相同的,但要更明确地说明它们创建的表HDU的类型。

看看新创建的HDU的头文件,就会发现相关的关键字被正确地填充了:

>>> hdu.header
XTENSION= 'BINTABLE'           / binary table extension
BITPIX  =                    8 / array data type
NAXIS   =                    2 / number of array dimensions
NAXIS1  =                   73 / length of dimension 1
NAXIS2  =                    4 / length of dimension 2
PCOUNT  =                    0 / number of group parameters
GCOUNT  =                    1 / number of groups
TFIELDS =                    6 / number of table fields
TTYPE1  = 'target  '
TFORM1  = '10A     '
TTYPE2  = 'counts  '
TFORM2  = 'J       '
TUNIT2  = 'DN      '
TTYPE3  = 'notes   '
TFORM3  = '10A     '
TTYPE4  = 'spectrum'
TFORM4  = '10E     '
TTYPE5  = 'flag    '
TFORM5  = 'L       '
TTYPE6  = 'intarray'
TFORM6  = '4I      '
TDIM6   = '(2, 2)  '

警告

需要注意的是,当使用 BinTableHDU.from_columns() ,则会创建所有输入列数组的内存副本。这是因为不能保证列在内存中按行的主要顺序连续排列(事实上,它们很可能不是),因此必须将它们组合到一个新数组中。

但是,如果数组数据 is 在内存中已经是连续的,比如在现有的记录数组中,可以使用kludge来创建一个新的表HDU,而无需进行任何复制。首先,像前面一样创建列,但是不使用 array= 论点:

>>> col1 = fits.Column(name='target', format='10A')

那就打电话来 BinTableHDU.from_columns() ::

>>> hdu = fits.BinTableHDU.from_columns([col1, col2, col3, col4, col5])

这将像以前一样创建一个新的表HDU,具有正确的列定义,但数据节为空。现在,您可以将阵列直接指定给HDU的“数据”属性:

>>> hdu.data = mydata

在未来的版本中 astropy ,表的创建将被简化,此过程将不需要。

适合具有时间列的表格

这个 FITS Time standard paper 定义用于在FITS文件中表示计时信息的格式和关键字。这个 astropy FITS包提供对本机读写的支持 Time 使用此格式的列和对象。这是在 FITS 统一的I/O接口和使用示例可以在 TDISPn关键字 第节。支持并不完整,只实现了完整标准的一个子集。

例子

以下是一个带有时间摘要的表(列)的示例:

COMMENT      ---------- Globally valid key words ----------------
TIMESYS = ’TT      ’          / Time system
MJDREF  = 50814.000000000000  / MJD zero point for (native) TT (= 1998-01-01)
MJD-OBS = 53516.257939301     / MJD for observation in (native) TT

COMMENT      ---------- Time Column -----------------------
TTYPE1  = ’Time    ’          / S/C TT corresponding to mid-exposure
TFORM1  = ’2D      ’          / format of field
TUNIT1  = ’s       ’
TCTYP1  = ’TT      ’
TCNAM1  = ’Terrestrial Time’  / This is TT
TCUNI1  = ’s       ’

但是,FITS标准和 astropy 时间对象没有完全映射,必须做出一些妥协。帮助用户理解 astropy 代码处理这些情况时,下面的文本描述了 astropy 有一些细节。

为了创建符合FITS时间标准的FITS列,我们考虑了 FITS Time paper .

用于存储的策略 Time FITS表中的列将创建 Header 使用适当的时间坐标全局引用关键字和特定于列的重写关键字。模块 astropy.io.fits.fitstime 处理时间专栏的读写。

以下关键字设置时间坐标帧:

  • 时间尺度

    所有元数据中最重要的是时间尺度,它是度量时间的规范。

    TIMESYS (string-valued)
    Time scale; default UTC
    
    TCTYPn (string-valued)
    Column-specific override keyword

    全局时间刻度可以由表equivalent关键字中记录的时间刻度覆盖 TCTYPn 对于FITS表列中的时间坐标。 TCTYna 用于替代坐标。

  • 时间基准

    HDU中所有时间都相对的时间参考点。由于在同一个表中有多个时间列时,没有特定于上下文的引用时间,因此我们需要使用其他一些关键字来调整这些列的引用时间。

    参考时间点应通过以下三个关键字中的一个来指定,这些关键字按优先顺序递减:

    MJDREF (floating-valued)
    Reference time in MJD
    
    JDREF (floating-valued)
    Reference time in JD
    
    DATEREF (datetime-valued)
    Reference time in ISO-8601

    时间引用关键字(MJDREF、JDREF、DATEREF)使用中指定的时间刻度进行解释 TIMESYS .

    注解

    如果这三个关键字都不存在,只要HDU中的所有时间都用ISO-8601表示就没有问题 Datetime Strings 格式: CCYY-MM-DD[Thh:mm:ss[.s...]] (例如, "2015-04-05T12:22:33.8" );否则,必须假定MJDREF=0.0。

    参考时间的值对所有时间值都具有全局有效性,但它没有与之关联的特定时间刻度。因此我们需要使用 TCRVLn (时间坐标参考值)关键字以补偿时间刻度差异。

  • 时间基准位置

    引用位置,由关键字指定 TREFPOS ,指定时间有效的空间位置,可以是进行观测的位置,也可以是应用了光照时间校正的空间点。这可能是一个标准位置(例如 GEOCENTERTOPOCENTER )或空间中由特定坐标定义的点。

    TREFPOS (string-valued)
    Time reference position; default TOPOCENTER
    
    TRPOSn (string-valued)
    Column-specific override keyword

    注解

    对于TOPOCENTER,我们需要在 OBSGEO-* 关键词。

  • 时间参考方向

    路径i.,如果没有应用时间戳,则 TOPOCENTER 对于观测数据),应提供用于计算路径长度延迟的参考方向,以保持数据的正确分析轨迹。然而,只有在观测地点(观测站位置)也有可用信息时,这才有用。

    参考方向是通过对特定关键字的引用来指示的。这些关键字可以显式地保持方向或指示包含坐标的列。

    TREFDIR (string-valued)
    Pointer to time reference direction
    
    TRDIRn (string-valued)
    Column-specific override keyword
  • 时间单位

    FITS标准建议时间单位为规范中允许的单位之一。

    TIMEUNIT (string-valued)
    Time unit; default s
    
    TCUNIn (string-valued)
    Column-specific override
  • 时间偏移量

    有时,通过将一个数字放在一个关键字中来批量应用统一的时钟校正是很方便的。时间偏移的第二个用途是将零偏移设置为相对时间序列,从而允许时间戳中的相对时间为零或更高精度。其默认值为零。

    TIMEOFFS (floating-valued)
    This has global validity
  • 绝对误差、相对误差和时间分辨率、时间分块可在需要时使用。

以下关键字定义全局时间信息关键字:

  • 日期和日期-*关键字

    这些定义了在ISO-8601中创建和观察HDU的日期。 DATE 如果文件是在地球表面构建的,而其他文件的时间尺度是 TIMESYS .

  • MJD-*关键词

    这些定义与上述定义相同,但是 MJD (修改朱利安日期)。

该实现编写上述FITS关键字的子集,这些关键字映射到时间元数据。时间本质上是一个坐标,因此与 World Coordinate System 空间坐标规范。因此,在读取带有时间列的FITS表时,使用FITS-WCS标准规则和建议来验证坐标列确实是时间。