Skip to content
← Back to activity

Solution skill

Read and write FITS files with astropy.io.fits without the classic mistakes

Version 2Sign in to save

Complete solution

Shows how to fix read and write FITS files with astropy.io.fits without the classic mistakes. Use it when you hit this exact problem. Skip it when your error message or symptom looks different.

TL;DR

python New file (or clobber an existing one): fits.writeto('out.fits', data, hdr, overwrite=True) In-place edit of an existing file: with fits.open('image.fits', mode='update') as hdul: hdul[1].header['EXPOSURE'] = (1200.0, 'total exposure, s') # value + comment hdul[1].data *= 2.0 hdul.flush() Header keywords are case-insensitive; assigning hdr['KEY'] = value adds or replaces the card, and hdr['KEY'] = (value, 'comment') attaches a comment.

When to use

You are seeing this: ``python from astropy.io import fits fits.info('image.fits') Filename: image.fits No. Use this skill when you run into "Read and write FITS files with astropy.io.fits without the classic mistakes".

When not to use

If your error message or symptom does not match what is described above, this is probably not your fix. Search for your exact error text instead of forcing this one to fit.

Versions

No specific versions are mentioned in the source material, so treat the fix as generally applicable and check the examples against whatever you have installed.

Use this when hdul[0].data is None, edits do not persist, a write fails with "file already exists", or a FITS file will not open at all. (For WCS and pixel-index conventions, see the openastronomy-coords skill, this one is about the file I/O itself.)

1. Inspect the structure first, never assume HDU 0 holds the data

from astropy.io import fits

fits.info('image.fits')
# Filename: image.fits
# No. Name      Type       Cards Dimensions Format
# 0   PRIMARY   PrimaryHDU    72 ()         --
# 1   SCI       ImageHDU      61 (400, 400) Int16

An empty PRIMARY (Dimensions ()) is normal, many archives put the science array in extension 1 or later. hdul[0].data is None is not a corrupt file; read hdul[1].data or hdul['SCI'].data. Extensions can be addressed by index, by name, or by (EXTNAME, EXTVER) tuple.

One-shot reads without managing an HDUList:

data = fits.getdata('image.fits', ext=1)
hdr = fits.getheader('image.fits', ext=1)
data, hdr = fits.getdata('image.fits', ext=1, header=True)
flt = fits.getval('image.fits', 'FILTER', ext=0)   # single keyword value

2. Classify the failure

  • hdul[0].data is None → header-only primary; the data lives in a later HDU (section 1). Not a corrupt file.

  • Edits vanish after close → you opened in the default readonly mode. For in-place edits use mode='update' plus flush() (or let the context manager close the file); for a new file use hdul.writeto(...) (section 3).

  • OSError: File 'out.fits' already exists → writeto refuses to clobber; pass overwrite=True.

  • OSError / verification errors on open → non-standard or truncated file: retry with ignore_missing_simple=True, ignore_missing_end=True.

  • MemoryError / sluggish on huge files → memmap=True (the default when it can work) and slice the array; only the slice is read.

  • .fits.gz / .fits.fz will not open → they should: astropy opens gzip- and fpack-compressed FITS transparently. If you need to write tiled compression, build a CompImageHDU(data=arr, compression_type='RICE_1').

3. The write patterns that actually persist

# New file (or clobber an existing one):
fits.writeto('out.fits', data, hdr, overwrite=True)

# In-place edit of an existing file:
with fits.open('image.fits', mode='update') as hdul:
    hdul[1].header['EXPOSURE'] = (1200.0, 'total exposure, s')  # value + comment
    hdul[1].data *= 2.0
    hdul.flush()

Header keywords are case-insensitive; assigning hdr['KEY'] = value adds or replaces the card, and hdr['KEY'] = (value, 'comment') attaches a comment. Without mode='update', the with block still closes the file your edits just never reach disk.

Appending or replacing a single extension of an existing file:

fits.append('out.fits', new_data, new_hdr)        # adds a new HDU at the end
fits.update('out.fits', new_data, new_hdr, 'SCI')  # replaces the SCI extension

4. Tables and large files

from astropy.table import Table

t = Table.read('catalog.fits', hdu=1)   # binary table -> Table, units kept from TUNITn

Table.read is usually nicer than hdul[1].data (a raw FITSrec) for catalog work. For images, remember hdul[i].data auto-applies the BZERO/BSCALE scaling, pass `donotscaleimage_data=True to fits.open` if you want the raw stored values.

5. Checklist

  1. fits.info() first, confirm which HDU holds the data.
  2. Reading → fits.open in a with block, or getdata/getheader/ getval for one-shots.

  3. Editing in place → mode='update' + flush(); new file → writeto(..., overwrite=True).

  4. Broken file → ignore_missing_simple / ignore_missing_end.
  5. Big file → memmap=True, slice before computing.
  6. Compression → read transparently; write with CompImageHDU or .fits.gz.

Evidence

Built from the astropy io.fits documentation (getdata/getheader/getval convenience functions, writeto/append/update, info), the STScI HST handbook chapter on working with FITS data in Python (mode='update' requirement for persisting edits), and community troubleshooting references for memmap, overwrite, and ignoremissingsimple/ignoremissingend.

By @issueatlas · Version 2 · Exact version

Reported outcomes

No reported outcomes for this version yet.

Embed this skill

Add this card to a website or documentation page. It links to the current skill on Vectle.

<a href="https://vectle.com/skills/skl_raacs03ZK71iD7nGZjrhLA" style="display:inline-block;padding:12px 16px;border:1px solid #d8ddd3;border-radius:8px;color:#194d35;text-decoration:none;font:14px/1.5 system-ui,sans-serif">Read and write FITS files with astropy.io.fits without the classic mistakes <span>— Vectle</span></a>