Header objects

class rustfits.FITSHeader

Bases: object

Live view of an HDU’s FITS header.

Returned by hdu.header (see HDU.header). Behaves like a mapping for lookup (header["KEY"] / "KEY" in header / header.get("KEY", default)) and for mutation (header["KEY"] = value / del header["KEY"]). Mutations write through to disk immediately — no flush required, no two-step commit.

Reading: header[key] returns the value directly (astropy-style), not a {value, comment} dict. Per-key comments come from comment_of(). header.to_dict() returns the legacy shape for serialization or tests.

Writing: every mutation follows disk-write-before-commit: the file is updated first, and only on success is the in-memory card list replaced. Pre-I/O errors (e.g. a header that would overflow its reserved blocks AND the grow path is disabled — currently rare; in-place file grow is supported) leave the header unchanged. Mid-write I/O failures taint the file (close + reopen to recover).

Protected keys. Some keywords represent state rustfits manages on the user’s behalf — file structure (SIMPLE, BITPIX, NAXIS*, XTENSION, PCOUNT, GCOUNT, TFIELDS, TFORMn, …), integrity (CHECKSUM, DATASUM), or compression layout (ZIMAGE, ZCMPTYPE, ZBITPIX, ZNAXIS*, …). __setitem__ and __delitem__ raise ValueError on these keys. Use the dedicated HDU APIs for structural changes (e.g. ImageHDU.extend(), TableHDU.insert_column()).

Batched edits. For multiple mutations, prefer edit() (returns a FITSHeaderEdit context manager) so the file is rewritten once at the end rather than per mutation.

Examples

Lookup and iteration:

value = header["EXPTIME"]
for k in header:
    print(k, header[k])
if "FILTER" in header:
    ...

Single mutation:

header["EXPTIME"] = 30.0
header["COMMENT"] = "calibration frame"  # via add_comment
del header["JUNK"]

Batched mutation:

with header.edit() as h:
    h["A"] = 1
    h["B"] = 2
    h["C"] = 3
# exactly one disk rewrite at the end

Bulk copy from another header or a dict via update():

# Copy metadata from one HDU's header to another.
# Protected (structural / integrity / compression) keys
# in the source are skipped silently; commentary cards
# are skipped unless copy_commentary=True.
fits[2].header.update(fits[1].header)

# From a dict (every key must be non-protected; whole
# update is rejected if any key is protected).
fits[1].header.update({"OBSERVER": "Hubble", "EXPTIME": 30.0})
__contains__(key, /)

Return bool(key in self).

__delitem__(key, /)

Delete self[key].

__getitem__(key, /)

Return self[key].

__iter__()

Implement iter(self).

__len__()

Return len(self).

__setitem__(key, value, /)

Set self[key] to value.

add_blank(text)

Append a blank-keyword commentary card to the header.

“Blank” cards have an all-spaces keyword; FITS uses them for visual section separators and unstructured notes. Same append-only semantics as add_comment().

add_comment(text)

Append a COMMENT card to the header.

Long text auto-splits across multiple commentary cards (FITS commentary cards hold ~72 chars each). Always appends; the COMMENT keyword is not a single-valued card. Use del header["COMMENT"] to remove every COMMENT card at once.

add_history(text)

Append a HISTORY card to the header.

HISTORY is FITS’s audit-trail keyword — each pipeline-processing step typically appends one card. Same append-only semantics as add_comment().

cards

The raw 80-character header card strings.

Useful for low-level inspection or for tools that need to write the bytes themselves. Returns a snapshot — the list is a copy, mutations to it don’t write back.

Returns:

Each entry is one 80-character FITS card (with the standard padding included).

Return type:

list of str

comment_of(key)

Return the comment text attached to a header card.

FITS cards have an optional / comment field after the value. This accessor returns that text (empty string when no comment is present). Use this rather than parsing card text yourself.

Parameters:

key (str) – Keyword name. Case-insensitive. Not valid for commentary keys (COMMENT / HISTORY / blank); for those, header[key] returns the list of commentary texts directly.

Raises:
edit()

Open a batched-edit context.

Returns a FITSHeaderEdit context manager. Mutations inside the with block accumulate in memory; at __exit__ (with no exception) they’re committed to disk in a single header rewrite. This is the right shape for multiple mutations: header.edit() does one I/O instead of one per mutation.

Example

with header.edit() as h:
    h["EXPTIME"] = 30.0
    h["FILTER"] = "g"
    h["GAIN"] = 1.5
get(key, default=None)

Get the value of key, or default if absent.

Mapping-style accessor — same shape as Python’s dict.get. Useful when you don’t want a KeyError on missing keys.

Parameters:
  • key (str) – Keyword name. Case-insensitive.

  • default (Any, optional) – Value to return when key is absent. Defaults to None.

items()

List of (key, value) tuples, in keys() order.

keys()

List of unique keyword names in on-disk order.

Commentary keywords (COMMENT, HISTORY, blank) appear at most once each, even if there are multiple commentary cards.

to_dict(skip_protected=False)

Snapshot the header as a {key: {value, comment}} dict.

Legacy shape, useful for serialization and tests. Everyday code should prefer header[key] (returns the value directly) and comment_of() (returns the per-key comment string).

Parameters:

skip_protected (bool, optional) – If True, omit protected keywords (structural, integrity, compression) from the output — produces a dict suitable for passing back into update() on another header. Default False (everything included).

Returns:

{key: {"value": ..., "comment": ...}} for regular keys, and {key: [text, text, ...]} for the commentary keys COMMENT / HISTORY / blank.

Return type:

dict

update(other, *, copy_commentary=False)

Bulk-copy entries from another header or a dict.

Parameters:
  • other (FITSHeader or dict) – Source of the new entries. When a FITSHeader, protected keywords are silently skipped (the destination already has its own correct structural / integrity / compression keys, and they must not be clobbered). When a dict, protected keywords raise — an explicit hand-written {"BITPIX": 32} in user code is almost certainly a mistake.

  • copy_commentary (bool, optional, keyword-only) – Only meaningful for FITSHeader sources. If True, commentary cards (COMMENT / HISTORY / blank) are appended verbatim; one append per source card, no deduplication. Default False (commentary skipped — the common case is “copy structured metadata from this other header”). For dict sources, the flag is ignored and commentary keywords always raise.

Raises:

ValueError – dict source contains a protected key, or (regardless of copy_commentary) a dict source contains a commentary key. The whole update is rejected — no partial commit.

Notes

All entries are written to disk in a single header rewrite (one I/O), not per-key. Validate-then-mutate: validation errors leave the file untouched.

values()

List of values, in keys() order.

Each value is what header[key] would return — scalar for regular keys, list of strings for commentary keys.

class rustfits.FITSHeaderEdit

Bases: object

A transactional batch of header mutations.

Returned by FITSHeader.edit() — meant to be used as a context manager. Mutations inside the with block accumulate in memory; on clean exit, the accumulated card list is written to disk in a single header rewrite. If the block exits with an exception, no commit happens — the header on disk is unchanged.

This is the right shape for multiple mutations in a row. Each mutation through FITSHeader directly does its own disk write; with FITSHeaderEdit, you pay one I/O for the whole batch.

The mutation surface (__setitem__ / __delitem__ / add_comment() / add_history() / add_blank() / update()) mirrors FITSHeader’s exactly. Read accessors (__getitem__ / __contains__ / __repr__) reflect the in-progress staged state, not the on-disk header.

Examples

with header.edit() as h:
    h["A"] = 1
    h["B"] = 2
    del h["JUNK"]
    h.add_history("Reprocessed with pipeline v2.3")
# exactly one disk rewrite happens here, at __exit__

Notes

Calling mutation methods outside a with block raises. Re-using a FITSHeaderEdit after commit raises; open a new one with another FITSHeader.edit() call.

__contains__(key, /)

Return bool(key in self).

__delitem__(key, /)

Delete self[key].

__getitem__(key, /)

Return self[key].

__setitem__(key, value, /)

Set self[key] to value.

add_blank(text)

Append a blank-keyword commentary card to the staged edits. See FITSHeader.add_blank().

add_comment(text)

Append a COMMENT card to the staged edits.

Same semantics as FITSHeader.add_comment(); the commentary is held in memory until the surrounding with block exits.

add_history(text)

Append a HISTORY card to the staged edits. See FITSHeader.add_history().

update(other, *, copy_commentary=False)

Bulk-copy entries from another header or a dict into the staged edits.

Same shape, source rules, and copy_commentary semantics as FITSHeader.update(); the difference is that the entries go into the staged-but-uncommitted card list and write to disk at __exit__.

rustfits.is_protected_key(key)

Check if the input keyword is protected

Is this (post-normalization) key one that rustfits manages on the user’s behalf — i.e., one whose value is determined by the file’s structure, integrity contract, or compression layout, and which the user must NOT mutate directly?

Categories: image-HDU structural, binary/ASCII table structural, random groups, tiled image compression, integrity (CHECKSUM/DATASUM). Not protected: user metadata like OBJECT, EXPTIME, EXTNAME, BUNIT, BSCALE, BZERO, CTYPEn, CRVALn, etc.

Parameters:

key (str) – A keyword name

Return type:

True if it is protected, False otherwise