E: Corrigiendo la cabecera de un fichero FITS

Nos hemos encontrado un fichero imagenes/fitsHeaderIncorrecto.fit, generado por MaxIm DL que produce un error al intentar abrirlo con AstroPy. El error concretamente es que la cabecera del fichero BZERO tiene un valor igual a 0.00000000000000000 0, cuando debería ser 0.00000000000000000.

[1]:
import os
import os.path
import shutil
from astropy.io import fits
import matplotlib.pyplot as plt
import numpy as np

ficheroConflictivo = "imagenes/fitsHeaderIncorrecto.fit"

im = fits.open(ficheroConflictivo)    # Fallará, porque el fichero tiene una cabecera incorrecta:
                                      # BZERO   =  0.00000000000000000 0
WARNING: VerifyWarning: Error validating header for HDU #0 (note: Astropy uses zero-based indexing).
    Unparsable card (BZERO), fix it first with .verify('fix').
There may be extra bytes after the last HDU or the file is corrupted. [astropy.io.fits.hdu.hdulist]
---------------------------------------------------------------------------
OSError                                   Traceback (most recent call last)
Cell In[1], line 10
      6 import numpy as np
      8 ficheroConflictivo = "imagenes/fitsHeaderIncorrecto.fit"
---> 10 im = fits.open(ficheroConflictivo)    # Fallará, porque el fichero tiene una cabecera incorrecta:
     11                                       # BZERO   =  0.00000000000000000 0

File ~/anaconda3/envs/cursoAstronomia2/lib/python3.8/site-packages/astropy/io/fits/hdu/hdulist.py:214, in fitsopen(name, mode, memmap, save_backup, cache, lazy_load_hdus, ignore_missing_simple, use_fsspec, fsspec_kwargs, **kwargs)
    211 if not name:
    212     raise ValueError(f"Empty filename: {name!r}")
--> 214 return HDUList.fromfile(
    215     name,
    216     mode,
    217     memmap,
    218     save_backup,
    219     cache,
    220     lazy_load_hdus,
    221     ignore_missing_simple,
    222     use_fsspec=use_fsspec,
    223     fsspec_kwargs=fsspec_kwargs,
    224     **kwargs,
    225 )

File ~/anaconda3/envs/cursoAstronomia2/lib/python3.8/site-packages/astropy/io/fits/hdu/hdulist.py:482, in HDUList.fromfile(cls, fileobj, mode, memmap, save_backup, cache, lazy_load_hdus, ignore_missing_simple, **kwargs)
    462 @classmethod
    463 def fromfile(
    464     cls,
   (...)
    472     **kwargs,
    473 ):
    474     """
    475     Creates an `HDUList` instance from a file-like object.
    476
   (...)
    479     documentation for details of the parameters accepted by this method).
    480     """
--> 482     return cls._readfrom(
    483         fileobj=fileobj,
    484         mode=mode,
    485         memmap=memmap,
    486         save_backup=save_backup,
    487         cache=cache,
    488         ignore_missing_simple=ignore_missing_simple,
    489         lazy_load_hdus=lazy_load_hdus,
    490         **kwargs,
    491     )

File ~/anaconda3/envs/cursoAstronomia2/lib/python3.8/site-packages/astropy/io/fits/hdu/hdulist.py:1248, in HDUList._readfrom(cls, fileobj, data, mode, memmap, cache, lazy_load_hdus, ignore_missing_simple, use_fsspec, fsspec_kwargs, **kwargs)
   1245     if hdulist._file.close_on_error:
   1246         hdulist._file.close()
-> 1248     raise OSError("Empty or corrupt FITS file")
   1250 if not lazy_load_hdus or kwargs.get("checksum") is True:
   1251     # Go ahead and load all HDUs
   1252     while hdulist._read_next_hdu():

OSError: Empty or corrupt FITS file

Vamos a corregir reemplazando dicha cabecera de manera artesanal (no podemos usar los mecanismos habituales de AstroPy porque la misma biblioteca no es capaz de cargar dicho fichero.

Primero copiamos el fichero para evitar el riesgo de estropear el fichero:

[2]:
ficheroCorregido = "salidas/fitsHeaderIncorrecto_corregido.fit"

shutil.copyfile(ficheroConflictivo, ficheroCorregido)
[2]:
'salidas/fitsHeaderIncorrecto_corregido.fit'
[3]:
def corrigeHeaderFits(nombreArchivo):
    f = open(nombreArchivo, "r+b")          # Abrimos el fichero como lectura y escritura binaria

    readed = b""
    while not readed.startswith(b"END"):    # La última línea de la cabecera debe empezar por END
        readed = f.read(80)                 # Las cabeceras FITS son de 80 caracteres ASCII (80 bytes)

        print(readed)

        if readed.startswith(b"BZERO"):             # Si la línea de la cabecera comienza por BZERO
            if b"0.00000000000000000 0" in readed:  # Y encontramos la cadena errónea dentro
                print("Hemos encontrado un BZERO incorrecto. Sustituyendo")
                f.seek(-80, os.SEEK_CUR)                 # Retrocedemos 80 caracteres el puntero de lectura del fichero
                                                         # Y escribimos la cabecera bien
                f.write(b'BZERO   =  0.00000000000000000                                                  ')  # Ojo! no quitar ni un espacio en blanco de la cadena

    f.close()

corrigeHeaderFits(ficheroCorregido)
b'SIMPLE  =                    T / conforms to FITS standard                      '
b'BITPIX  =                  -32 / array data type                                '
b'NAXIS   =                    2 / number of array dimensions                     '
b'NAXIS1  =                  200                                                  '
b'NAXIS2  =                  200                                                  '
b'BZERO   =  0.00000000000000000 0                                                '
Hemos encontrado un BZERO incorrecto. Sustituyendo
b'DATAMIN =             0.000000                                                  '
b'DATAMAX =           65535.000000                                                '
b"INSTRUME=           'ATIK-460ex: fw rev 3.34'                                   "
b"TELESCOP=           'Mewlon210'                                                 "
b"OBSERVER=           'Pedro Benedicto'                                           "
b"FILTER  = 'Position 0'osition 0'                                                "
b'EXPTIME =   10.000000000000000                                                  '
b"DATE-OBS= '2022-04-02T22:50:02'T22:50:02'                                       "
b'XPIXSZ  =           4.540                                                       '
b'YPIXSZ  =           4.540                                                       '
b'XBINNING=           1                                                           '
b'YBINNING=           1                                                           '
b'XORGSUBF=           0                                                           '
b'YORGSUBF=           0                                                           '
b'XPOSSUBF=           0                                                           '
b'YPOSSUBF=           0                                                           '
b'CBLACK  =                  104                                                  '
b'CWHITE  =                  634                                                  '
b'CCD-TEMP=           -10.1                                                       '
b"SWCREATE=           'Artemis Capture'                                           "
b"INPUTFMT= 'FITS    ' /          Format of file from which image was read        "
b"SWMODIFY= 'MaxIm DL Version 6.11 141223 1VH7F' /Name of software                "
b"SWSERIAL= '1VH7F-K9Y6J-9C35K-TUXJ7-6J8QF-V2' /Software serial number            "
b'HISTORY  Bias Subtraction (Bias 1x1_-10, 2749 x 2199, Bin1 x 1, Temp -10C,      '
b'HISTORY  Exp Time 0ms)                                                          '
b"CALSTAT = 'BDF     '                                                            "
b'HISTORY  Dark Subtraction (Dark 1x1_-10_60s, 2749 x 2199, Bin1 x 1,             '
b'HISTORY  Temp -10C, Exp Time 60s)                                               '
b'HISTORY  Flat Field (Flat 4, 2749 x 2199, Bin1 x 1, Temp -15C,                  '
b'PEDESTAL=                 -100 /Correction to add for zero-based ADU            '
b"CSTRETCH= 'Medium  ' /          Initial display stretch mode                    "
b"SWOWNER = 'Tan Yong Liang' /    Licensed owner of software                      "
b'SNAPSHOT=                   19 /Number of images combined                       '
b'EXPOSURE=   10.000000000000000 /Exposure time in seconds                        '
b"MIDPOINT= '2022-04-02T22:52:45' /UT of midpoint of exposure                     "
b'END                                                                             '

Ahora si podemos cargar y visualizar el fichero corregido:

[4]:
im = fits.open(ficheroCorregido)
data = im[0].data

plt.imshow(data, vmin=np.min(data), vmax=np.max(data)/128)

plt.show()
WARNING: Unexpected extra padding at the end of the file.  This padding may not be preserved when saving changes. [astropy.io.fits.header]
_images/E-10-corrigeHeaderFits_6_1.png