Quickstart¶
A five-minute tour of rustfits: open a file, read and slice an image, read and subset a table, write something back. Every code block below is self-contained — copy it into a script, swap the filename, and it runs.
Opening a file¶
rustfits.FITS is the file handle. Use it as a context
manager so the file is closed and flushed when the block exits.
import rustfits
with rustfits.FITS("data.fits") as fits:
print(fits) # gives a pretty-printed list of HDUs
for hdu in fits:
# gives a pretty-printed view of the HDU
print(hdu)
Each HDU is accessed by integer position (fits[0] is the
primary HDU) or by EXTNAME (fits["sci"]; lookup is case-
insensitive). HDUs come back typed: ImageHDU,
TableHDU, CompressedImageHDU,
etc.
In a repl, the FITS object gives a nice representation
>>> fits
file: /home/esheldon/data/tmp/tmp9fl85sgs/mix.fits
mode: r
extnum hdutype extname
0 IMAGE_HDU
1 BINARY_TBL MYTABLE
As do the HDUs
>>> fits['image_hdu']
file: img.fits
extension: 0
type: IMAGE_HDU
image info:
data type: f8
dims: [300, 400]
>>> fits['table_hdu']
file: table.fits
extension: 1
type: BINARY_TBL
extname: WIDE
rows: 3
column info:
index i8
flags ?
name U array[var]
source U12
ra f8
dec f8
shape f4 array[2]
samples f4 array[16]
File modes¶
rustfits.FITS accepts three modes:
Mode |
Access |
If the file exists |
If the file is missing |
|---|---|---|---|
|
read-only |
opens it |
raises |
|
read + write |
opens it (contents preserved) |
raises |
|
read + write |
truncates to zero length |
creates it |
Use "r+" to modify or append HDUs to an existing file without
losing anything. Use "w+" when you want to start fresh — it
is equivalent to fitsio’s "rw" plus clobber=True. "w+"
is also the default for the top-level rustfits.write()
convenience; pass mode="r+" to append to an existing file
instead of truncating it.
Reading an image¶
Whole-array read:
arr = rustfits.read("image.fits")
with rustfits.FITS("image.fits") as fits:
arr = fits[1].read()
print(arr.shape, arr.dtype)
Slicing with __getitem__ (same axis convention as numpy —
slowest axis first):
with rustfits.FITS("image.fits") as fits:
stamp = fits[1][100:200, 50:150] # 100x100 cutout
row = fits[1][42, :] # one row
pixel = fits[1][10, 20] # scalar
Only the tiles or strips that overlap the slice are read from disk — fine for grabbing cutouts out of a multi-GB image.
Writing pixels back¶
__setitem__ is symmetric with __getitem__: anything you
can read this way you can write the same way. Open the file in
"r+" (read-write):
import numpy as np
with rustfits.FITS("image.fits", "r+") as fits:
fits[1][100:200, 50:150] = 0 # zero out a region
fits[1][42, :] = np.arange(fits[1].shape[1])
A scalar RHS broadcasts across the selection; an ndarray must match the slice’s shape.
Reading a table¶
A TableHDU returns a numpy structured array:
with rustfits.FITS("catalog.fits") as fits:
tab = fits[1].read()
print(tab.dtype.names)
print(tab["ra"][:5])
Read just some columns or some rows:
with rustfits.FITS("catalog.fits") as fits:
hdu = fits[1]
sub = hdu.read(columns=["ra", "dec"]) # column subset
head = hdu.read(rows=slice(0, 100)) # first 100 rows
picks = hdu.read(rows=[0, 5, 10, 17]) # fancy rows
Column subsets¶
Indexing a table with a column name returns a subset object — a lazy view that turns into an array (or smaller table) when sliced or read.
with rustfits.FITS("catalog.fits") as fits:
ra_col = fits[1]["ra"] # SingleColumnSubset
ra_all = ra_col[:] # plain ndarray
ra_first_100 = ra_col[:100]
ra_picks = ra_col[[0, 5, 10]]
sub = fits[1][["ra", "dec"]] # ColumnSubset
cat = sub[:] # structured ndarray
first_10 = sub[:10] # smaller structured ndarray
Both subset types also expose .read() and .write() methods
if you prefer named calls over slicing.
Writing into a table¶
The same indexing surface works for writes. hdu[i] = record
replaces one row; hdu["col"] = arr replaces a whole column;
subset objects support __setitem__ for patching individual
cells or row ranges of a column:
import numpy as np
with rustfits.FITS("catalog.fits", "r+") as fits:
hdu = fits[1]
# Single-row write — value is a record (numpy.void) or a
# length-1 structured array with the table's field names.
hdu[0] = hdu[0] # no-op
# Whole-column write.
hdu["flag"] = np.zeros(len(hdu), dtype="i4")
# Single-cell write — symmetric with the read form
# `hdu["ra"][5]`.
hdu["ra"][5] = 123.4
# Multiple rows of one column.
hdu["ra"][[0, 1, 2]] = [10.0, 11.0, 12.0]
Append rows with append():
new_rows = np.zeros(3, dtype=hdu.dtype)
hdu.append(new_rows)
Creating a new file¶
The shortest path is rustfits.write(), which auto-detects
image vs table from the value you pass it:
import numpy as np
import rustfits
img = np.arange(10000, dtype="f4").reshape(100, 100)
rustfits.write("out.fits", img) # → image HDU
cat = np.zeros(100, dtype=[("ra", "f8"), ("dec", "f8"), ("flag", "i4")])
rustfits.write("cat.fits", cat) # → table HDU
rustfits.write(
"named.fits",
{"x": np.arange(3), "y": np.arange(3) * 2.0},
extname="sci",
)
rustfits.write() accepts only the universal kwargs
(mode, extname, header). For type-specific knobs
(compress=, quantize=, blank=, var_dtypes=,
units=, bit_columns=, …), or for multiple HDUs in
one file, open FITS directly and call
write_image() /
write_table():
with rustfits.FITS("out.fits", "w+") as fits:
fits.write_image(img, extname="sci")
fits.write_table(cat, extname="cat")
The "w+" mode truncates or creates; "r+" opens an
existing file for read-write without truncating.
For code that processes HDUs without knowing their type ahead
of time — copying HDUs from one file to another, for instance
— write() is the method-form counterpart
to rustfits.write() and auto-detects image vs table from
its argument:
with rustfits.FITS("in.fits") as src:
with rustfits.FITS("out.fits", "w+") as dst:
for hdu in src:
if hdu.has_data:
dst.write(hdu.read())
Headers¶
Every HDU has a header attribute exposing the FITS cards as
a dict-like view:
with rustfits.FITS("data.fits") as fits:
hdr = fits[1].header
exptime = hdr["exptime"]
comment = hdr.comment_of("exptime")
print(hdr.keys())
For “just the header, no data” workflows there’s a top-level
rustfits.read_header() shortcut (default ext=0 picks
the primary HDU):
hdr = rustfits.read_header("data.fits") # primary
sci_hdr = rustfits.read_header("data.fits", ext="sci")
The returned FITSHeader outlives the file
close — read-only access works after the function returns.
Mutation is straightforward in "r+" mode:
with rustfits.FITS("data.fits", "r+") as fits:
fits[1].header["object"] = "M31"
fits[1].header.add_comment("Processed by pipeline v2")
Walking the HDUs¶
Iterate to see what’s in a file:
with rustfits.FITS("data.fits") as fits:
for i, hdu in enumerate(fits):
print(i, hdu.extname, hdu.has_data)
Two HDU properties are useful for filtering without reading any data:
hdu.has_data— True iff the HDU has actual data (NAXIS > 0and everyNAXISn > 0). The primary HDU is often empty (header-only) andhas_datais False there.isinstance(hdu, ImageHDU)/TableHDU— pick by HDU type.CompressedImageHDUis a subclass ofImageHDUandCompressedTableHDUis a subclass ofTableHDU, so anisinstancecheck on the base class matches both compressed and uncompressed.
A realistic “find the first image worth reading” pattern:
with rustfits.FITS("data.fits") as fits:
for hdu in fits:
if hdu.has_data and isinstance(hdu, rustfits.ImageHDU):
img = hdu.read()
break
The matching pattern for tables uses rustfits.TableHDU.
Where to next¶
Images — dtypes, scaling, BLANK masking,
extend().Tables — schema construction, VLA columns, schema edits.
Compression — tile-compressed images and tables, the
Quantizeconfig, the tile cache.Headers — protected keys, CONTINUE chains, HIERARCH,
FITSHeaderEditfor batched updates, checksums.