验证

astropy 已经建立了一个灵活的方案来验证符合FITS标准的FITS数据。验证的基本原理 astropy 对输入要宽容,对输出要严格。

什么时候? astropy 读取不符合FITS标准的FITS文件,它不会引发错误并退出。它会尽力做出最好的解释,只有当有问题的数据被访问,并且无法得到明确的解释时,它才会放弃。

另一方面,当写入输出FITS文件时,默认情况下要写入的内容必须严格符合FITS标准。这个默认行为可以被其他几个选项覆盖,这样用户就不会因为轻微的标准冲突而被耽搁。

符合标准

由于FITS标准是一个“宽松”的标准,因此有许多地方可能发生违规行为,而要执行它们几乎是不可能的。大型观测站生成的数据产品不符合100%拟合要求,这并不少见。一些天文台也开发了自己的非标准方言,其中一些非常普遍,已经成为事实上的标准。示例包括长字符串值和CONTINUE卡的使用。

违反标准的行为可能发生在数据结构的不同级别。 astropy 的验证方案是在这些层次上开发的。这是三个 astropy 验证级别:

  1. HDU列表

  2. 每个HDU

  3. HDU标头中的每个卡

这三个级别对应于三类对象: HDUList ,任何HDU(例如。, PrimaryHDUImageHDU 等等),以及 Card . 它们是唯一拥有 verify() 方法。大多数其他班级 astropy.io.fits 没有一个 verify() 方法。

如果 verify() 在HDU列表级别调用,它验证所有三个级别的标准符合性,但调用 verify() 在卡级别只检查该卡的合规性。因为 astropy 在读取FITS文件时是容忍的,不是吗 verify() 在输入时调用。在输出端, verify() 以限制性最强的选项作为默认值调用。

验证选项

中的所有verify(option)调用都接受多个选项 astropy . 此外,还可用于 output_verify 以下方法的参数: close()writeto()flush() . 在这些情况下,它们被传递给 verify() 在这些方法中调用。可用选项包括:

exception

如果违反任何FITS标准,此选项将引发异常。这是输出的默认选项(即,当 writeto()close()flush() 调用)。如果用户希望在输出时覆盖此默认值,则可以使用下面列出的其他选项。

warn

此选项与“忽略”选项相同,但会发送警告消息。它不会尝试修复任何FITS标准冲突,无论是否可修复。

ignore

此选项将忽略任何符合标准的冲突。在输出时,它将把HDU列表内容写入输出FITS文件,不管它是否符合FITS标准。

“忽略”选项在以下情况下很有用:

  1. 读取带有非标准格式的输入FITS文件,用户希望将其复制或写入输出文件。非标准格式将保留在输出文件中。

  2. 用户希望有目的地创建一个非标准的FITS文件,可能是为了测试或一致性。

不会打印警告消息。这就像一个无声的警告选项(见下文)。

fix

此选项将尝试修复任何拟合标准冲突。不可能总是能纠正这种违规行为。一般来说,有两种FITS标准冲突:fixable和non-fixable。例如,如果一个关键字有一个浮点数,其小写字母为“e”(例如,1.23e11),而不是FITS标准要求的大写“e”,则这是一个可修复的违规。另一方面,像“P.I.”这样的关键字名称是不可修复的,因为它不知道用什么来替换不允许的句点。如果冲突是可修复的,则此选项将打印一条消息,指出它已修复。如果它不可修复,它将抛出一个异常。

修复的原则是不造成伤害。例如,可以通过删除一张关键字名为“P.I.”的卡片来“修复”,但是 astropy 不会采取这种行动来损害数据的完整性。

并非所有的修复都是“正确的”修复,但至少 astropy 将尝试以这样一种方式进行修复,它不会丢弃其他FITS阅读器。

silentfix

与fix相同,但不会打印出信息性消息。在用户不需要过多无害消息的大型脚本中,这可能很有用。如果冲突不可修复,它仍将抛出异常。

此外,截至 astropy 版本0.4.0提供以下组合选项:

  • 修复+忽略

  • 修复+警告

  • 修复+异常

  • silentfix+忽略

  • silentfix+警告

  • silentfix+异常

这些选项结合了基本选项的语义。例如, silentfix+exception 实际上相当于 silentfix 在这种情况下,可修复的错误将被静默地修复,但任何不可修复的错误都将引发异常。另一方面, silentfix+warn 将对不可修复的错误发出警告,但对任何已修复的错误将保持沉默。

不同数据对象级别的验证

我们将检查什么 astropy 的验证有三个不同的层次:

在HDUList验证

在HDU列表级别,验证仅针对两种简单情况:

  1. 验证HDU列表中的第一个HDU是主HDU。这是一个可以治愈的病例。解决方法是在HDU列表中插入一个最小的主HDU。

  2. 验证HDU列表中的第二个或更高版本的HDU不是主HDU。违规将无法纠正。

在每个HDU上验证

对于每个HDU,将验证强制关键字、它们在报头中的位置以及它们的值。每一个FITS HDU都有一组固定的关键字,按固定的顺序排列。例如,主HDU的标头必须至少具有以下关键字:

SIMPLE =                     T /
BITPIX =                     8 /
NAXIS  =                     0

如果任一必填关键字丢失或顺序错误,则FIX选项将修复它们::

>>> from astropy.io import fits
>>> filename = fits.util.get_testdata_filepath('verify.fits')
>>> hdul = fits.open(filename)
>>> hdul[0].header
SIMPLE  =                    T / conforms to FITS standard
NAXIS   =                    0 / NUMBER OF AXES
BITPIX  =                    8 / BITS PER PIXEL
>>> hdul[0].verify('fix') 
VerifyWarning: Verification reported errors:
VerifyWarning: 'BITPIX' card at the wrong place (card 2).
  Fixed by moving it to the right place (card 1).
VerifyWarning: Note: astropy.io.fits uses zero-based indexing.
>>> hdul[0].header           # voila!
SIMPLE  =                    T / conforms to FITS standard
BITPIX  =                    8 / BITS PER PIXEL
NAXIS   =                    0 / NUMBER OF AXES
>>> hdul.close()

每张卡的验证

最底层的卡片,也有最复杂的验证可能性。

实例

以下是可修复和不可修复卡的列表:

固定卡:

  1. 带小写字母“e”或“d”的浮点数:

    >>> from astropy.io import fits
    >>> c = fits.Card.fromstring('FIX1    = 2.1e23')
    >>> c.verify('silentfix')
    >>> print(c)
    FIX1    =               2.1E23
    
  2. 等号在卡片图像的第九列之前:

    >>> c = fits.Card.fromstring('FIX2= 2')
    >>> c.verify('silentfix')
    >>> print(c)
    FIX2    =                    2
    
  3. 不带引号的字符串值::

    >>> c = fits.Card.fromstring('FIX3    = string value without quotes')
    >>> c.verify('silentfix')
    >>> print(c)
    FIX3    = 'string value without quotes'
    
  4. 卡片图像中第九列前面缺少等号。

  5. 浮点数与E或D之间的空格:

    >>> c = fits.Card.fromstring('FIX5    = 2.4 e 03')
    >>> c.verify('silentfix')
    >>> print(c)
    FIX5    =               2.4E03
    
  6. 无法分析的值将“固定”为字符串:

    >>> c = fits.Card.fromstring('FIX6    = 2 10 ')
    >>> c.verify('fix+warn') 
    VerifyWarning: Verification reported errors:
    VerifyWarning: Card 'FIX6' is not FITS standard
     (invalid value string: '2 10').
       Fixed 'FIX6' card to meet the FITS standard.
    VerifyWarning: Note: astropy.io.fits uses zero-based indexing.
    >>> print(c)
    FIX6    = '2 10    '
    

不可换卡:

  1. 关键字名称中有非法字符。

我们将用一个“生命周期”示例来总结验证过程:

>>> h = fits.PrimaryHDU()  # create a PrimaryHDU
>>> # Try to add an non-standard FITS keyword 'P.I.' (FITS does no allow
>>> # '.' in the keyword), if using the update() method - doesn't work!
>>> h.header['P.I.'] = 'Hubble' 
VerifyWarning: Keyword name 'P.I.' is greater than 8 characters or
 contains characters not allowed by the FITS standard;
  a HIERARCH card will be created.
>>> # Have to do it the hard way (so a user will not do this by accident)
>>> # First, create a card image and give verbatim card content (including
>>> # the proper spacing, but no need to add the trailing blanks)
>>> c = fits.Card.fromstring("P.I. = 'Hubble'")
>>> h.header.append(c)  # then append it to the header
>>> # Now if we try to write to a FITS file, the default output
>>> # verification will not take it.
>>> h.writeto('pi.fits')  
Traceback (most recent call last):
 ...
VerifyError: HDU 0:
    Card 5:
        Card 'P.I. ' is not FITS standard (equal sign not at column 8).
        Illegal keyword name 'P.I. '
>>> # Must set the output_verify argument to 'ignore', to force writing a
>>> # non-standard FITS file
>>> h.writeto('pi.fits', output_verify='ignore')
>>> # Now reading a non-standard FITS file
>>> # astropy.io.fits is magnanimous in reading non-standard FITS files
>>> hdul = fits.open('pi.fits')
>>> hdul[0].header 
SIMPLE  =            T / conforms to FITS standard
BITPIX  =            8 / array data type
NAXIS   =            0 / number of array dimensions
EXTEND  =            T
HIERARCH P.I. = 'Hubble  '
P.I.    = 'Hubble  '
VerifyWarning: Verification reported errors:
VerifyWarning: Card 'P.I. ' is not FITS standard (equal sign
 not at column 8).  Fixed 'P.I. ' card to meet the FITS standard.
VerifyWarning: Unfixable error: Illegal keyword name 'P.I. '
VerifyWarning: Note: astropy.io.fits uses zero-based indexing.
>>> # even when you try to access the offending keyword, it does NOT
>>> # complain
>>> hdul[0].header['p.i.']
'Hubble'
>>> # But if you want to make sure if there is anything wrong/non-standard,
>>> # use the verify() method
>>> hdul.verify() 
VerifyWarning: Verification reported errors:
VerifyWarning: HDU 0:
VerifyWarning:     Card 5:
VerifyWarning:         Illegal keyword name 'P.I. '
VerifyWarning: Note: astropy.io.fits uses zero-based indexing.
>>> hdul.close()

使用FITS校验和关键字约定进行验证

北美FITS委员会已经审查了FITS校验和关键字约定,以作为FITS标准。本公约对FITS hdu中包含的信息进行完整性检查。约定由两个标题关键字卡组成:校验和和和数据。CHECKSUM关键字被定义为一个ASCII字符串,其值强制在所有2880字节上累积的32位1的补码校验和适合HDU中的逻辑记录等于负零。在数据的无符号字符串中定义为32位的无符号字符串。验证累积校验和是否仍等于负零提供了一种相当可靠的方法来确定HDU在物理介质上复制或存储文件时没有被后续数据处理操作修改或损坏。

为了避免对性能造成任何影响,默认情况下 astropy 不会在打开文件时验证HDU校验和,也不会在写入文件时生成校验和值。事实上,当文件被打开时,校验和和和数据存储卡会自动从HDU头文件中移除,而当HDU写入文件时,任何校验和或数据存储卡都会从头文件中剥离出来。为了在打开文件时验证hdu的校验和值,用户必须在对open便利函数的调用中提供checksum关键字参数值为True。完成此操作后,任何校验和验证失败都将导致发出警告(通过警告模块)。如果在打开时请求校验和验证,并且HDU头中不存在校验和或数据存储卡,则文件将打开而不带注释。类似地,为了在写入文件时输出HDU头中的校验和和和数据存储卡,用户必须在对 writeto() 功能。通过提供值为“DATASUM”的校验和关键字参数,可以只将DATASUM卡写入头。

实例

要在打开文件时验证HDU的校验和值,请执行以下操作:

>>> # Open the file checksum.fits verifying the checksum values for all HDUs
>>> filename = fits.util.get_testdata_filepath('checksum.fits')
>>> hdul = fits.open(filename, checksum=True)
>>> hdul.close()
>>> # Open the file in.fits where checksum verification fails
>>> filename = fits.util.get_testdata_filepath('checksum_false.fits')
>>> hdul = fits.open(filename, checksum=True) 
AstropyUserWarning: Checksum verification failed for HDU ('PRIMARY', 1).
AstropyUserWarning: Datasum verification failed for HDU ('PRIMARY', 1).
AstropyUserWarning: Checksum verification failed for HDU ('RATE', 1).
AstropyUserWarning: Datasum verification failed for HDU ('RATE', 1).
>>> # Create file out.fits containing an HDU constructed from data
>>> # containing both CHECKSUM and DATASUM cards.
>>> data = hdul[0].data
>>> fits.writeto('out.fits', data=data, checksum=True)
>>> hdun = fits.open('out.fits', checksum=True)
>>> hdun.close()

>>> # Create file out.fits containing all the HDUs in the HDULIST
>>> # hdul with each HDU header containing only the DATASUM card
>>> hdul.writeto('out2.fits', checksum='datasum')

>>> # Create file out.fits containing the HDU hdu with both CHECKSUM
>>> # and DATASUM cards in the header
>>> hdu = hdul[1]
>>> hdu.writeto('out3.fits', checksum=True)

>>> # Append a new HDU constructed from array data to the end of
>>> # the file existingfile.fits with only the appended HDU
>>> # containing both CHECKSUM and DATASUM cards.
>>> fits.append('out3.fits', data, checksum=True)
>>> hdul.close()