Skill Tracer
Skills › Scientific Agent Skills › astropy › fits.md

fits.md

425 lines · skills/astropy/references/fits.md at 5f2bc03

Open on GitHub
1# FITS File Handling (astropy.io.fits)
2
3The `astropy.io.fits` module reads and writes FITS images and tables. File paths
4and instrument-specific arrays in these fragments are illustrative; select the
5actual science HDU after inspecting `.info()`. Synthetic checks cover the same
6local I/O operations, sections, headers and checksums on Astropy 8.0.1.
7

FITS File Handling (astropy.io.fits)

The astropy.io.fits module reads and writes FITS images and tables. File paths and instrument-specific arrays in these fragments are illustrative; select the actual science HDU after inspecting .info(). Synthetic checks cover the same local I/O operations, sections, headers and checksums on Astropy 8.0.1.

Raw lines 1 to 7
8## Opening FITS Files
9

Opening FITS Files

Raw lines 8 to 9
10### Basic File Opening
12```python
13from astropy.io import fits
15# Open file (returns HDUList - list of HDUs)
16hdul = fits.open('filename.fits')
18# Always close when done
19hdul.close()
21# Better: use context manager (automatically closes)
22with fits.open('filename.fits') as hdul:
23 hdul.info() # Display file structure
24 data = hdul[0].data
25```

Basic File Opening

from astropy.io import fits

# Open file (returns HDUList - list of HDUs)
hdul = fits.open('filename.fits')

# Always close when done
hdul.close()

# Better: use context manager (automatically closes)
with fits.open('filename.fits') as hdul:
    hdul.info()  # Display file structure
    data = hdul[0].data
Raw lines 10 to 26
27### File Opening Modes
29```python
30fits.open('file.fits', mode='readonly') # Read-only (default)
31fits.open('file.fits', mode='update') # Read and write
32fits.open('file.fits', mode='append') # Add HDUs to file
33```

File Opening Modes

fits.open('file.fits', mode='readonly')   # Read-only (default)
fits.open('file.fits', mode='update')     # Read and write
fits.open('file.fits', mode='append')     # Add HDUs to file
Raw lines 27 to 34
35### Memory Mapping
37Memory mapping defaults to enabled for compatible local files. Scaled image
38data (`BSCALE`/`BZERO`/`BLANK`) can require `memmap=False`; compression and
39column conversions can allocate memory. A retained `.data` reference keeps the
40mmap/file handle alive after `hdul.close()`. Copy only the needed cutout inside
41the context when independent data are required:
43```python
44with fits.open('large_file.fits', memmap=True) as hdul:
45 cutout = hdul['SCI'].data[100:200, 100:200].copy()
46```

Memory Mapping

Memory mapping defaults to enabled for compatible local files. Scaled image data (BSCALE/BZERO/BLANK) can require memmap=False; compression and column conversions can allocate memory. A retained .data reference keeps the mmap/file handle alive after hdul.close(). Copy only the needed cutout inside the context when independent data are required:

with fits.open('large_file.fits', memmap=True) as hdul:
    cutout = hdul['SCI'].data[100:200, 100:200].copy()
Raw lines 35 to 47
48### Remote Files
50Access cloud-hosted FITS files (illustrative; requires `fsspec` and the relevant
51backend, e.g. `s3fs` for S3). HTTP servers must support range requests for efficient
52partial reads; backend buffering/compression can increase transfer sizes:
54**Network note:** Remote FITS reads disclose the target URI to the storage provider and may use credentials from the configured filesystem backend. Use anonymous reads only for public data, and prefer local files for proprietary observations.
56```python
57uri = "s3://bucket-name/image.fits"
58with fits.open(uri, use_fsspec=True, fsspec_kwargs={"anon": True}) as hdul:
59 # .section requests a cutout; do not access .data or guarantee exact network bytes
60 cutout = hdul[1].section[100:200, 100:200]
61```

Remote Files

Access cloud-hosted FITS files (illustrative; requires fsspec and the relevant backend, e.g. s3fs for S3). HTTP servers must support range requests for efficient partial reads; backend buffering/compression can increase transfer sizes:

Network note: Remote FITS reads disclose the target URI to the storage provider and may use credentials from the configured filesystem backend. Use anonymous reads only for public data, and prefer local files for proprietary observations.

uri = "s3://bucket-name/image.fits"
with fits.open(uri, use_fsspec=True, fsspec_kwargs={"anon": True}) as hdul:
    # .section requests a cutout; do not access .data or guarantee exact network bytes
    cutout = hdul[1].section[100:200, 100:200]
Raw lines 48 to 62
63## HDU Structure
65FITS files contain Header Data Units (HDUs):
66- **Primary HDU** (`hdul[0]`): First HDU, always present
67- **Extension HDUs** (`hdul[1:]`): Image or table extensions
69```python
70hdul.info() # Display all HDUs
71# Output:
72# No. Name Ver Type Cards Dimensions Format
73# 0 PRIMARY 1 PrimaryHDU 220 ()
74# 1 SCI 1 ImageHDU 140 (1014, 1014) float32
75# 2 ERR 1 ImageHDU 51 (1014, 1014) float32
76```

HDU Structure

FITS files contain Header Data Units (HDUs):

  • Primary HDU (hdul[0]): First HDU, always present
  • Extension HDUs (hdul[1:]): Image or table extensions
hdul.info()  # Display all HDUs
# Output:
# No.    Name      Ver    Type      Cards   Dimensions   Format
#  0  PRIMARY       1 PrimaryHDU     220   ()
#  1  SCI           1 ImageHDU       140   (1014, 1014)   float32
#  2  ERR           1 ImageHDU        51   (1014, 1014)   float32
Raw lines 63 to 77
78## Accessing HDUs
80```python
81# By index
82primary = hdul[0]
83extension1 = hdul[1]
85# By name
86sci = hdul['SCI']
88# By name and version number
89sci2 = hdul['SCI', 2] # Second SCI extension
90```

Accessing HDUs

# By index
primary = hdul[0]
extension1 = hdul[1]

# By name
sci = hdul['SCI']

# By name and version number
sci2 = hdul['SCI', 2]  # Second SCI extension
Raw lines 78 to 91
92## Working with Headers

Working with Headers

Raw lines 92 to 93
94### Reading Header Values
96```python
97hdu = hdul[0]
98header = hdu.header
100# Get keyword value (case-insensitive)
101observer = header['OBSERVER']
102exptime = header['EXPTIME']
104# Get with default if missing
105filter_name = header.get('FILTER', 'Unknown')
107# Access by index
108value = header[7] # 8th card's value
109```

Reading Header Values

hdu = hdul[0]
header = hdu.header

# Get keyword value (case-insensitive)
observer = header['OBSERVER']
exptime = header['EXPTIME']

# Get with default if missing
filter_name = header.get('FILTER', 'Unknown')

# Access by index
value = header[7]  # 8th card's value
Raw lines 94 to 110
111### Modifying Headers
113```python
114# Update existing keyword
115header['OBSERVER'] = 'Edwin Hubble'
117# Add/update with comment
118header['OBSERVER'] = ('Edwin Hubble', 'Name of observer')
120# Add keyword at specific position
121header.insert(5, ('NEWKEY', 'value', 'comment'))
123# Add HISTORY and COMMENT
124header['HISTORY'] = 'File processed on 2025-01-15'
125header['COMMENT'] = 'Note about the data'
127# Delete keyword
128del header['OLDKEY']
129```

Modifying Headers

# Update existing keyword
header['OBSERVER'] = 'Edwin Hubble'

# Add/update with comment
header['OBSERVER'] = ('Edwin Hubble', 'Name of observer')

# Add keyword at specific position
header.insert(5, ('NEWKEY', 'value', 'comment'))

# Add HISTORY and COMMENT
header['HISTORY'] = 'File processed on 2025-01-15'
header['COMMENT'] = 'Note about the data'

# Delete keyword
del header['OLDKEY']
Raw lines 111 to 130
131### Header Cards
133Each keyword is stored as a "card" (80-character record):
135```python
136# Access full card
137card = header.cards[0]
138print(f"{card.keyword} = {card.value} / {card.comment}")
140# Iterate over all cards
141for card in header.cards:
142 print(f"{card.keyword}: {card.value}")
143```

Header Cards

Each keyword is stored as a "card" (80-character record):

# Access full card
card = header.cards[0]
print(f"{card.keyword} = {card.value} / {card.comment}")

# Iterate over all cards
for card in header.cards:
    print(f"{card.keyword}: {card.value}")
Raw lines 131 to 144
145## Working with Image Data

Working with Image Data

Raw lines 145 to 146
147### Reading Image Data
149```python
150# Get data from HDU
151data = hdul[1].data # Returns NumPy array
153# Data properties
154print(data.shape) # e.g., (1024, 1024)
155print(data.dtype) # e.g., float32
156print(data.min(), data.max())
158# Access specific pixels
159pixel_value = data[100, 200]
160region = data[100:200, 300:400]
161```

Reading Image Data

# Get data from HDU
data = hdul[1].data  # Returns NumPy array

# Data properties
print(data.shape)      # e.g., (1024, 1024)
print(data.dtype)      # e.g., float32
print(data.min(), data.max())

# Access specific pixels
pixel_value = data[100, 200]
region = data[100:200, 300:400]
Raw lines 147 to 162
163### Data Operations
165Data is a NumPy array, so use standard NumPy operations:
167```python
168import numpy as np
170# Statistics
171mean = np.mean(data)
172median = np.median(data)
173std = np.std(data)
175# Modify data
176data[data < 0] = 0 # Clip negative values
177data = data * gain + bias # Calibration
179# Mathematical operations
180log_data = np.log10(data)
181from scipy.ndimage import gaussian_filter
182smoothed = gaussian_filter(data, sigma=2)
183```

Data Operations

Data is a NumPy array, so use standard NumPy operations:

import numpy as np

# Statistics
mean = np.mean(data)
median = np.median(data)
std = np.std(data)

# Modify data
data[data < 0] = 0  # Clip negative values
data = data * gain + bias  # Calibration

# Mathematical operations
log_data = np.log10(data)
from scipy.ndimage import gaussian_filter
smoothed = gaussian_filter(data, sigma=2)
Raw lines 163 to 184
185### Cutouts and Sections
187Extract regions without loading entire array:
189```python
190# Section notation [y_start:y_end, x_start:x_end]
191cutout = hdul[1].section[500:600, 700:800]
192```

Cutouts and Sections

Extract regions without loading entire array:

# Section notation [y_start:y_end, x_start:x_end]
cutout = hdul[1].section[500:600, 700:800]
Raw lines 185 to 193
194## Creating New FITS Files

Creating New FITS Files

Raw lines 194 to 195
196### Simple Image File
198```python
199# Create data
200data = np.random.random((100, 100))
202# Create HDU
203hdu = fits.PrimaryHDU(data=data)
205# Add header keywords
206hdu.header['OBJECT'] = 'Test Image'
207hdu.header['EXPTIME'] = 300.0
209# Write to file
210hdu.writeto('new_image.fits')
212# Overwrite if exists
213hdu.writeto('new_image.fits', overwrite=True)
214```

Simple Image File

# Create data
data = np.random.random((100, 100))

# Create HDU
hdu = fits.PrimaryHDU(data=data)

# Add header keywords
hdu.header['OBJECT'] = 'Test Image'
hdu.header['EXPTIME'] = 300.0

# Write to file
hdu.writeto('new_image.fits')

# Overwrite if exists
hdu.writeto('new_image.fits', overwrite=True)
Raw lines 196 to 215
216### Multi-Extension File
218```python
219# Create primary HDU (can have no data)
220primary = fits.PrimaryHDU()
221primary.header['TELESCOP'] = 'HST'
223# Create image extensions
224sci_data = np.ones((100, 100))
225sci = fits.ImageHDU(data=sci_data, name='SCI')
227err_data = np.ones((100, 100)) * 0.1
228err = fits.ImageHDU(data=err_data, name='ERR')
230# Combine into HDUList
231hdul = fits.HDUList([primary, sci, err])
233# Write to file
234hdul.writeto('multi_extension.fits')
235```

Multi-Extension File

# Create primary HDU (can have no data)
primary = fits.PrimaryHDU()
primary.header['TELESCOP'] = 'HST'

# Create image extensions
sci_data = np.ones((100, 100))
sci = fits.ImageHDU(data=sci_data, name='SCI')

err_data = np.ones((100, 100)) * 0.1
err = fits.ImageHDU(data=err_data, name='ERR')

# Combine into HDUList
hdul = fits.HDUList([primary, sci, err])

# Write to file
hdul.writeto('multi_extension.fits')
Raw lines 216 to 236
237## Working with Table Data

Working with Table Data

Raw lines 237 to 238
239### Reading Tables
241```python
242# Open table
243with fits.open('table.fits') as hdul:
244 table = hdul[1].data # BinTableHDU or TableHDU
246 # Access columns
247 ra = table['RA']
248 dec = table['DEC']
249 mag = table['MAG']
251 # Access rows
252 first_row = table[0]
253 subset = table[10:20]
255 # Column info
256 cols = hdul[1].columns
257 print(cols.names)
258 cols.info()
259```

Reading Tables

# Open table
with fits.open('table.fits') as hdul:
    table = hdul[1].data  # BinTableHDU or TableHDU

    # Access columns
    ra = table['RA']
    dec = table['DEC']
    mag = table['MAG']

    # Access rows
    first_row = table[0]
    subset = table[10:20]

    # Column info
    cols = hdul[1].columns
    print(cols.names)
    cols.info()
Raw lines 239 to 260
261### Creating Tables
263```python
264# Define columns
265col1 = fits.Column(name='ID', format='K', array=[1, 2, 3, 4])
266col2 = fits.Column(name='RA', format='D', array=[10.5, 11.2, 12.3, 13.1])
267col3 = fits.Column(name='DEC', format='D', array=[41.2, 42.1, 43.5, 44.2])
268col4 = fits.Column(name='Name', format='20A',
269 array=['Star1', 'Star2', 'Star3', 'Star4'])
271# Create table HDU
272table_hdu = fits.BinTableHDU.from_columns([col1, col2, col3, col4])
273table_hdu.name = 'CATALOG'
275# Write to file
276table_hdu.writeto('catalog.fits', overwrite=True)
277```

Creating Tables

# Define columns
col1 = fits.Column(name='ID', format='K', array=[1, 2, 3, 4])
col2 = fits.Column(name='RA', format='D', array=[10.5, 11.2, 12.3, 13.1])
col3 = fits.Column(name='DEC', format='D', array=[41.2, 42.1, 43.5, 44.2])
col4 = fits.Column(name='Name', format='20A',
                   array=['Star1', 'Star2', 'Star3', 'Star4'])

# Create table HDU
table_hdu = fits.BinTableHDU.from_columns([col1, col2, col3, col4])
table_hdu.name = 'CATALOG'

# Write to file
table_hdu.writeto('catalog.fits', overwrite=True)
Raw lines 261 to 278
279### Column Formats
281Common FITS table column formats:
282- `'A'`: Character string (e.g., '20A' for 20 characters)
283- `'L'`: Logical (boolean)
284- `'B'`: Unsigned byte
285- `'I'`: 16-bit integer
286- `'J'`: 32-bit integer
287- `'K'`: 64-bit integer
288- `'E'`: 32-bit floating point
289- `'D'`: 64-bit floating point

Column Formats

Common FITS table column formats:

  • 'A': Character string (e.g., '20A' for 20 characters)
  • 'L': Logical (boolean)
  • 'B': Unsigned byte
  • 'I': 16-bit integer
  • 'J': 32-bit integer
  • 'K': 64-bit integer
  • 'E': 32-bit floating point
  • 'D': 64-bit floating point
Raw lines 279 to 290
291## Modifying Existing Files

Modifying Existing Files

Raw lines 291 to 292
293### Update Mode
295```python
296with fits.open('file.fits', mode='update', save_backup=True) as hdul:
297 # Modify header
298 hdul[0].header['NEWKEY'] = 'value'
300 # Modify data
301 hdul[1].data[100, 100] = 999
303 # Changes automatically saved when context exits
304```

Update Mode

with fits.open('file.fits', mode='update', save_backup=True) as hdul:
    # Modify header
    hdul[0].header['NEWKEY'] = 'value'

    # Modify data
    hdul[1].data[100, 100] = 999

    # Changes automatically saved when context exits
Raw lines 293 to 305
306### Append Mode
308```python
309# Add new extension to existing file
310new_data = np.random.random((50, 50))
311new_hdu = fits.ImageHDU(data=new_data, name='NEW_EXT')
313with fits.open('file.fits', mode='append') as hdul:
314 hdul.append(new_hdu)
315```

Append Mode

# Add new extension to existing file
new_data = np.random.random((50, 50))
new_hdu = fits.ImageHDU(data=new_data, name='NEW_EXT')

with fits.open('file.fits', mode='append') as hdul:
    hdul.append(new_hdu)
Raw lines 306 to 316
317## Convenience Functions
319For quick operations without managing HDU lists:
321```python
322# Get data only
323data = fits.getdata('file.fits', ext=1)
325# Get header only
326header = fits.getheader('file.fits', ext=0)
328# Get both
329data, header = fits.getdata('file.fits', ext=1, header=True)
331# Get single keyword value
332exptime = fits.getval('file.fits', 'EXPTIME', ext=0)
334# Set keyword value
335fits.setval('file.fits', 'NEWKEY', value='newvalue', ext=0)
337# Write simple file
338fits.writeto('output.fits', data, header, overwrite=True)
340# Append to file
341fits.append('file.fits', data, header)
343# Display file info
344fits.info('file.fits')
345```

Convenience Functions

For quick operations without managing HDU lists:

# Get data only
data = fits.getdata('file.fits', ext=1)

# Get header only
header = fits.getheader('file.fits', ext=0)

# Get both
data, header = fits.getdata('file.fits', ext=1, header=True)

# Get single keyword value
exptime = fits.getval('file.fits', 'EXPTIME', ext=0)

# Set keyword value
fits.setval('file.fits', 'NEWKEY', value='newvalue', ext=0)

# Write simple file
fits.writeto('output.fits', data, header, overwrite=True)

# Append to file
fits.append('file.fits', data, header)

# Display file info
fits.info('file.fits')
Raw lines 317 to 346
347## Comparing FITS Files
349```python
350# Print differences between two files
351fits.printdiff('file1.fits', 'file2.fits')
353# Compare programmatically
354diff = fits.FITSDiff('file1.fits', 'file2.fits')
355print(diff.report())
356```

Comparing FITS Files

# Print differences between two files
fits.printdiff('file1.fits', 'file2.fits')

# Compare programmatically
diff = fits.FITSDiff('file1.fits', 'file2.fits')
print(diff.report())
Raw lines 347 to 357
358## Converting Between Formats

Converting Between Formats

Raw lines 358 to 359
360### FITS to/from Astropy Table
362```python
363from astropy.table import Table
365# FITS to Table
366table = Table.read('catalog.fits')
368# Table to FITS
369table.write('output.fits', format='fits', overwrite=True)
370```

FITS to/from Astropy Table

from astropy.table import Table

# FITS to Table
table = Table.read('catalog.fits')

# Table to FITS
table.write('output.fits', format='fits', overwrite=True)
Raw lines 360 to 371
372## Best Practices
3741. **Always use context managers** (`with` statements) for safe file handling
3752. **Avoid modifying structural keywords** (SIMPLE, BITPIX, NAXIS, etc.)
3763. **Use memory mapping** for large files to conserve RAM
3774. **Use .section** for remote cutouts, then check backend transfer behavior
3785. **Check HDU structure** with `.info()` before accessing data
3796. **Verify data types** before operations to avoid unexpected behavior
3807. **Use convenience functions** for simple one-off operations

Best Practices

  1. Always use context managers (with statements) for safe file handling
  2. Avoid modifying structural keywords (SIMPLE, BITPIX, NAXIS, etc.)
  3. Use memory mapping for large files to conserve RAM
  4. Use .section for remote cutouts, then check backend transfer behavior
  5. Check HDU structure with .info() before accessing data
  6. Verify data types before operations to avoid unexpected behavior
  7. Use convenience functions for simple one-off operations
Raw lines 372 to 381
382## Common Issues
384### Handling Non-Standard FITS
386Some files violate FITS standards:
388```python
389# Diagnose the original; do not silently accept or rewrite broken observations.
390with fits.open('bad_file.fits') as hdul:
391 hdul.verify('exception')
393# Illustrative recovery only: ignore_missing_end handles a missing END card,
394# not arbitrary verification errors. Preserve original and review every change.
395with fits.open('bad_file.fits', ignore_missing_end=True) as hdul:
396 hdul.verify('fix')
397 hdul.writeto('fixed_file.fits', checksum=True)
398```

Handling Non-Standard FITS

Some files violate FITS standards:

# Diagnose the original; do not silently accept or rewrite broken observations.
with fits.open('bad_file.fits') as hdul:
    hdul.verify('exception')

# Illustrative recovery only: ignore_missing_end handles a missing END card,
# not arbitrary verification errors. Preserve original and review every change.
with fits.open('bad_file.fits', ignore_missing_end=True) as hdul:
    hdul.verify('fix')
    hdul.writeto('fixed_file.fits', checksum=True)
Raw lines 384 to 399
400### Checksums, units and nulls
402`hdul.verify('exception')` checks FITS structure, not scientific validity.
403Write `checksum=True`, read with `checksum=True`, and inspect warnings or the
404HDU `verify_checksum()`/`verify_datasum()` results when integrity matters.
405Record input hash, selected HDU, `BUNIT`, calibration and header changes; FITS
406image arrays do not automatically carry a `Quantity` unit.
408Astropy 8 adds `logical_as_bytes=True` to preserve FITS logical NULL (`b'\x00'`)
409separately from false (`b'F'`). Default boolean conversion can lose that distinction.
410`PL`/`QL` variable-length logical arrays now write standard FITS T/F bytes.
411`Table.read(..., strip_spaces=False)` preserves trailing string spaces when
412needed; the current default strips them. Test a write/read round trip if these
413representations affect the analysis.

Checksums, units and nulls

hdul.verify('exception') checks FITS structure, not scientific validity. Write checksum=True, read with checksum=True, and inspect warnings or the HDU verify_checksum()/verify_datasum() results when integrity matters. Record input hash, selected HDU, BUNIT, calibration and header changes; FITS image arrays do not automatically carry a Quantity unit.

Astropy 8 adds logical_as_bytes=True to preserve FITS logical NULL (b'\x00') separately from false (b'F'). Default boolean conversion can lose that distinction. PL/QL variable-length logical arrays now write standard FITS T/F bytes. Table.read(..., strip_spaces=False) preserves trailing string spaces when needed; the current default strips them. Test a write/read round trip if these representations affect the analysis.

Raw lines 400 to 414
415### Large File Performance
417```python
418# Use memory mapping (default)
419hdul = fits.open('huge_file.fits', memmap=True)
421# For write operations with large arrays, use Dask
422import dask.array as da
423large_array = da.ones((64, 64), chunks=(16, 16)) # Small executable example; scale deliberately
424fits.writeto('output.fits', large_array)
425```

Large File Performance

# Use memory mapping (default)
hdul = fits.open('huge_file.fits', memmap=True)

# For write operations with large arrays, use Dask
import dask.array as da
large_array = da.ones((64, 64), chunks=(16, 16))  # Small executable example; scale deliberately
fits.writeto('output.fits', large_array)
Raw lines 415 to 425