Reading FITS files

fitsy.open(path, mode='readonly', lenient=False)

Open a FITS file by path.

Parameters:
  • path (str or os.PathLike) – Filesystem path to the FITS file.

  • mode ({'readonly', 'update'}, optional) –

    'readonly' (default) opens read-only; header mutations and FitsFile.writeto() raise ValueError. Matches astropy’s fits.open mode of the same name.

    'update' opens read/write. Header edits and image-pixel in-place edits (hdu.data[...] = x) are preserved on FitsFile.writeto(). Table column data is read-only in this release; reconstruct the table with fitsy.bintable() to change column values.

  • lenient (bool, optional) – When True, accept SIMPLE = F primary headers (non-standard FITS files written by some pipelines). Does not downgrade any other validation errors to warnings. Default False.

Returns:

FitsFile – A read-only or read/write handle depending on mode.

Raises:
  • ValueError – If mode is not one of the recognized values.

  • FitsError – On parse failures or I/O errors.

Examples

>>> import fitsy
>>> with fitsy.open("image.fits") as f:
...     img = f[0]
...     print(img.axes)
class fitsy.FitsFile

Bases: object

Owning, ordered, mutable list of HDUs.

astropy parity: FitsFile behaves like astropy’s astropy.io.fits.HDUList. Slots are typed Python objects (ImageHdu / BinTable / AsciiTable); they own their header and data and survive after the file handle is dropped.

In mode='readonly' (the default), in-place edits (f[0].data[...] = x, f[0].header["K"] = v, f.append(hdu), del f[i]) are kept in memory and preserved on the next writeto(); the on-disk source file is never modified.

In mode='update', those same edits are written back to the source file on flush(), close(), or clean __exit__. Pixel patches via f[i].section[...] = arr are written immediately via positional pwrite.

Use the module-level open() factory rather than constructing this class directly.

Examples

>>> with fitsy.open("image.fits", mode="update") as f:
...     f[0].data[0, 0] = 42.0
...     # changes flushed automatically on __exit__
>>> with fitsy.open("image.fits") as f:    # readonly
...     f[0].header["OBSERVER"] = "you"
...     f.writeto("edited.fits")           # original untouched
__enter__()

Context-manager entry. Returns self.

__exit__(exc_type=None, _exc_val=None, _exc_tb=None)
__getitem__(key, /)

Return self[key].

__len__()

Return len(self).

add_checksums()

Enable CHECKSUM / DATASUM stamping on the next writeto() or flush().

When called, every HDU emitted by the next write will gain freshly computed CHECKSUM and DATASUM cards (per the FITS Checksum Proposal). Existing placeholder cards in each header are overwritten in place; missing ones are inserted. The flag stays on for the lifetime of the FitsFile object, matching astropy’s semantics where hdu.add_checksum() permanently mutates the HDU.

Notes

This does not stamp anything immediately – the actual computation happens during the next write, when the final byte layout of each HDU is known. To verify checksums on the resulting file, call verify_checksums() after the write.

Astropy parity: equivalent to calling hdu.add_checksum() on every HDU in the list. There is no per-HDU variant in fitsy because checksums must be computed against the final on-disk byte layout.

append(value)

Append an HDU at the end. Accepts an HDU instance or a builder.

close()

Flush pending edits (if any) and release the source file handle.

After close() the slot list and any HDU wrappers Python already holds remain usable as in-memory data, but the underlying file handle is dropped so any Pending slot that has not yet been materialized will fail to load. Subsequent flush() / writeto() calls also raise.

Idempotent: calling close() more than once is safe. Astropy parity: matches HDUList.close().

flush()

Flush pending edits to disk.

f.append(...), del f[i], fancy / dtype-mismatched patches, edits on a tile-compressed image), rewrites the file via a sibling temp file + atomic rename. Slots the user never touched are streamed byte-for-byte from the original file (no decode/re-encode).

Mixing modes: if you issue an in-place section[...] patch and then a non-patch mutation in the same session, the patch reaches disk first (via pwrite) and the subsequent flush() then performs a full rewrite that includes the patched bytes by streaming the (already patched) source file. Patches are not lost.

Crash safety: in-place patches use pwrite with no undo journal; a process death mid-patch can leave the file with some rows updated and others not (this matches astropy’s mmap-backed update mode). The full-rewrite path is crash-safe because it writes to a sibling temp file and renames atomically once the bytes are durable. Note that the parent directory is not separately fsynced, so a power loss between rename and the next directory commit can theoretically leave the rename invisible after reboot on non-journaling filesystems. Stale .fitsy-tmp.* siblings from a crashed rewrite are harmless and may be deleted.

A no-op for read-only files.

hdu(i)

Return the i-th HDU. Equivalent to file[i] for non-negative integer i.

hdu_by_name(name, ver=None)

Return the first HDU with matching EXTNAME.

Parameters:
  • name (str) – Value of the EXTNAME keyword to match.

  • ver (int, optional) – If given, also require matching EXTVER (default 1 when the keyword is absent).

Raises:

IndexError – If no HDU matches.

insert(i, value)

Insert an HDU at position i. Accepts an HDU instance or a builder.

read_only

True when the file was opened read-only.

verify_checksums()

Verify per-HDU CHECKSUM and DATASUM cards.

Streams the data section of each HDU directly from disk in fixed-size chunks (no full materialisation) and compares against the values stored in the HDU header. HDUs that have neither card are reported with both fields None; HDUs that only have one of the two are reported with the missing field None and the present one as True / False.

Returns:

list[dict] – One dict per HDU, in file order. Keys:

  • hdu – 0-based HDU index (int).

  • checksum_okTrue/False/None.

  • datasum_okTrue/False/None.

Notes

Works on any file (read-only or update), and on in-memory FitsFile objects whose data is already resident. Astropy parity: equivalent to iterating [hdu.verify_checksum() for hdu in hdul] but without reading the data into memory.

wcs(i=0, alt=' ')

Resolve the WCS for the given HDU index.

Parameters:
  • i (int, optional) – HDU index. Default 0 (primary HDU).

  • alt (str, optional) – Single ASCII character. ' ' (default) selects the primary WCS description.

Notes

Only the target HDU’s header is consulted; -TAB axis tables stored in sibling HDUs are not currently resolved.

writeto(path, overwrite=False)

Write the file (with all in-memory edits) to path.

Each HDU is re-emitted from its current Python state:

  • ImageHdu – pixel data is encoded from the live numpy array (so hdu.data[...] = x round-trips); BITPIX and NAXIS* are recomputed from the array.

  • BinTable, AsciiTable – data bytes are re-emitted as captured at load time (column edits do not round-trip in this release).

If the first HDU is not an image, an empty primary image HDU (NAXIS = 0) is automatically prepended so the output is a valid FITS file.

The on-disk source file (if any) is never modified, except when path resolves to the same file the handle was opened from – a self-write requires update mode and triggers an in-place rewrite (alias for flush()).

Parameters:
  • path (str or os.PathLike) – Destination path.

  • overwrite (bool, optional) – If False (default), raise FileExistsError when path already exists. Set to True to replace it.

Raises:
  • ValueError – If the file contains zero HDUs, or if path resolves to the source file and the handle is read-only.

  • FileExistsError – If path exists and overwrite is False.

  • FitsError – On I/O failure.

class fitsy.ImageHdu(data, header=None, name=None)

Bases: object

Image HDU with lazy numpy data.

Returned by FitsFile.hdu() (or file[i]) when the HDU kind is an image. The pixel data is not read at materialisation time; the first access to hdu.data (or any operation that needs the full array, e.g. FitsFile.writeto()) reads it from disk via positional pread. Subsequent accesses return the same array, and in-place mutation (hdu.data[0, 0] = 42) is preserved on the next FitsFile.writeto().

For workloads that operate on rectangular sub-regions of an image larger than RAM, use sectionhdu.section[a:b] reads only the requested bytes, and hdu.section[a:b] = arr writes only the touched bytes (via pwrite) without materialising the full array in memory.

Examples

>>> with fitsy.open("image.fits") as f:
...     img = f[0]
...     print(img.bitpix, img.axes, img.data.shape)
axes

[NAXIS1, NAXIS2, ...].

When the pixel data has been materialised, the axes are reported from the live numpy array shape (reversed, since numpy is row-major while FITS lists fastest-varying first). Otherwise the axes recorded at HDU-open time are returned – this is the lazy path that does not trigger a data read.

Type:

Image axes in NAXIS order

bitpix

FITS BITPIX value (e.g. -32 for f32).

data

Pixel data as a numpy array.

Materialises the array on first access by reading the data section from disk, byteswapping into native order, and applying BSCALE/BZERO/BLANK scaling. Subsequent accesses return the same array, and in-place mutation (hdu.data[...] = x) is preserved by the next FitsFile.writeto().

For images that do not fit in RAM, prefer sectionhdu.section[a:b] reads only the requested bytes without materialising the full array.

Returns:

numpy.ndarray or NoneNone when the HDU has no data section (NAXIS == 0).

header

The HDU header (see Header).

section

Slicing accessor that mirrors numpy.ndarray indexing. hdu.section[a:b, c:d] reads only the requested region from disk – no full-image materialisation.

In mode='update', hdu.section[a:b] = arr writes only the touched bytes back via positional pwrite, again without materialising the full image. This is the supported way to read or patch sub-regions of an image bigger than available RAM.

In-place writes require contiguous slicing (start:stop with step 1) on an HDU with identity scaling (BSCALE=1, BZERO=0, no BLANK). Anything else (fancy indexing, negative steps, scaled HDUs) raises a ValueError – assign through hdu.data[...] to trigger a full-file rewrite instead.

If hdu.data has already been accessed (and is therefore resident in memory), reads and writes go through the in-memory array for consistency with subsequent hdu.data accesses.

Returns:

_ImageSection – Slicing proxy. Use section[i, j, k] exactly like data[i, j, k].

wcs(alt=' ')

Resolve the WCS for this HDU.

Parameters:

alt (str, optional) – Single ASCII character. ' ' (default) selects the primary description; 'A' through 'Z' select alternate descriptions.

Returns:

Wcs or NoneNone if the header carries no WCS for alt.

Notes

Only this HDU’s header is consulted; -TAB axis tables stored in sibling HDUs are not resolved.

class fitsy.BinTable

Bases: object

Binary table HDU (BINTABLE).

Returned by FitsFile.hdu() (or file[i]) when the HDU kind is BINTABLE. Columns are decoded eagerly:

  • Scalar numeric columns return 1-D numpy.ndarray.

  • Fixed-repeat columns return (n_rows, repeat) 2-D arrays.

  • Variable-length and string columns return Python lists.

Examples

>>> with fitsy.open("catalog.fits") as f:
...     tbl = f[1]
...     ra = tbl["RA"]   # numpy array
...     name = tbl["NAME"]  # list[str]
__getitem__(key, /)

Return self[key].

column(name)

Column accessor; equivalent to table[name].

column_names

List of column names in declaration order.

data

Pre-decoded columns assembled into a numpy structured array (one record per row, dtype follows column types).

Variable-length and string columns are exposed via object dtype; numeric scalars retain native dtype. Returned array is read-only.

header

The HDU header.

n_rows

Number of rows in the table.

row(r)

Build a row dict for row index r.

to_dict()

Materialise every column as a plain dict[str, ndarray | list].

class fitsy.AsciiTable

Bases: object

ASCII TABLE HDU.

Returned by FitsFile.hdu() when the HDU kind is TABLE. Columns where every cell parses as numeric are returned as numpy.ndarray (with NaN for null cells); mixed columns fall back to list[str].

__getitem__(key, /)

Return self[key].

column(name)

Column accessor; equivalent to table[name].

column_names

List of column names in declaration order.

data

All columns assembled into a numpy structured array.

header

The HDU header.

n_rows

Number of rows in the table.

row(r)

Build a row dict for row index r.

to_dict()

Materialise every column as a plain dict[str, ndarray | list].

class fitsy.RandomGroups

Bases: object

Random-groups primary HDU (legacy format; see Standard Sec.6).

Read-only Python view: groups are decoded on demand through group(). The HDU does not participate in FitsFile.writeto(); round-tripping random-groups files through Python is intentionally not supported (use the Rust API).

bitpix

BITPIX value.

data_per_group

Number of data values per group (prod(NAXIS2..NAXISn)).

group(i)

Decode group i (0-based) as (parameters, data) numpy arrays. Both arrays use the HDU’s BITPIX dtype; BSCALE, BZERO, PSCALn, PZEROn are not applied.

header

HDU header.

n_groups

Number of groups (GCOUNT).

n_params

Number of parameters per group (PCOUNT).

Convenience functions

fitsy.getdata(path, ext=None, *, header=False)

Read one HDU’s data (and optionally its header) from path.

fitsy.getheader(path, ext=None)

Read one HDU’s header from path.

fitsy.getval(path, key, ext=None)

Read one header keyword from path.

Raises KeyError if the keyword is absent.

fitsy.info(path)

Return a brief HDU summary table for path.

Returns a list of (index, name, ver, kind, dims_or_n_rows) tuples. kind is the wrapper class name (“ImageHdu”, “BinTable”, …).

Comparing files

fitsy.diff(a, b, *, rtol=0.0, max_diffs=10, ignore_keywords=None)

Compare two FITS files and return a diff report.

Parameters:
  • a (str | os.PathLike) – Paths to the two files to compare.

  • b (str | os.PathLike) – Paths to the two files to compare.

  • rtol (float, optional) – Relative tolerance for floating-point comparisons. Default 0.0 (exact equality).

  • max_diffs (int, optional) – Maximum number of differences recorded per category before truncation. Default 10.

  • ignore_keywords (list[str], optional) – Header keywords to ignore (case-insensitive). Defaults to ["CHECKSUM", "DATASUM", "DATE"].

Returns:

FitsDiff – Diff object. Use str(diff) for the report; diff.identical is True when the files match.

class fitsy.FitsDiff

Bases: object

Result of comparing two FITS files.

__bool__()

True if self else False

__str__()

Return str(self).

diff_hdu_count

Number of HDUs that have at least one difference.

diff_hdu_indices()

List of HDU indices that contain differences.

hdu_counts

(n_a, n_b) HDU counts.

identical

True when both files have the same number of HDUs and every HDU is byte-equivalent under the configured options.

report()

Multi-line text report (mirrors astropy FITSDiff.report).