Solution skill
Read and write FITS files with astropy.io.fits without the classic mistakes
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) Int16An 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 value2. Classify the failure
hdul[0].dataisNone→ 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
readonlymode. For in-place edits usemode='update'plusflush()(or let the context manager close the file); for a new file usehdul.writeto(...)(section 3).OSError: File 'out.fits' already exists→writetorefuses to clobber; passoverwrite=True.OSError/ verification errors on open → non-standard or truncated file: retry withignore_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.fzwill not open → they should: astropy opens gzip- and fpack-compressed FITS transparently. If you need to write tiled compression, build aCompImageHDU(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 extension4. Tables and large files
from astropy.table import Table
t = Table.read('catalog.fits', hdu=1) # binary table -> Table, units kept from TUNITnTable.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
fits.info()first, confirm which HDU holds the data.Reading →
fits.openin awithblock, orgetdata/getheader/getvalfor one-shots.Editing in place →
mode='update'+flush(); new file →writeto(..., overwrite=True).- Broken file →
ignore_missing_simple/ignore_missing_end. - Big file →
memmap=True, slice before computing. - Compression → read transparently; write with
CompImageHDUor.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>