Writing FITS files

Four builder functions convert numpy / Python data into HDU specs:

Hand the resulting list to fitsy.write().

Anywhere pixel data or a numeric column is expected, any array-like works – a numpy array, a nested list, a tuple, or an object implementing __array__. Non-arrays are converted once via numpy.asarray(), which is also what decides the resulting BITPIX / TFORMn; pass a numpy array with an explicit dtype when you care about the on-disk type. A numpy array already in the platform’s byte order is used as-is, with no copy and no conversion.

"""Write a new FITS file holding an image and a binary table.

Run from the repository root:

    python examples/python/writing_files.py
"""

import os
import tempfile

import fitsy
import numpy as np

img = np.random.default_rng(0).normal(size=(64, 64)).astype("f4")
tbl = {
    # Numeric columns take any array-like: an array pins the dtype
    # (and so TFORMn), a plain list lets numpy infer it.
    "RA": np.array([10.0, 11.0, 12.0]),
    "DEC": [-5.0, -5.5, -6.0],
    "NAME": ["a", "bb", "ccc"],
}

with tempfile.TemporaryDirectory() as td:
    path = os.path.join(td, "out.fits")
    fitsy.write(
        path,
        [
            fitsy.image(img, header={"OBJECT": "noise"}),
            fitsy.bintable(tbl, extname="CATALOG"),
        ],
    )

    # Round-trip: read it back and check.
    with fitsy.open(path) as f:
        print("HDU count:", len(f))
        print("primary axes:", f[0].axes)
        print("table columns:", f[1].column_names)

By default fitsy.write() refuses to clobber an existing file. Pass overwrite=True to replace it.

Headers

The header argument to fitsy.image(), fitsy.compressed_image(), and fitsy.append() accepts either a fitsy.Header or a plain dict. Dict values may be scalars or (value, comment) tuples.

Pixel scaling on write (BSCALE / BZERO)

fitsy.image() writes the supplied numpy array verbatim: the buffer’s dtype determines BITPIX and the pixel bytes are emitted without further transformation. fitsy does not invert BSCALE or BZERO from physical units back to a raw integer representation.

Two consequences:

  • If the input header carries BSCALE and BZERO, the values in the array are interpreted on read as physical = BZERO + BSCALE * raw. Writing them back without changing the keywords means the new file’s “raw” pixels are your current array, not the original raw integers.

  • To round-trip a scaled integer image (e.g. one that was opened with hdu.data returning floats), drop the BSCALE / BZERO cards from the new header and write the data as the intended dtype, or apply the inverse transform yourself before building the HDU.

Unsigned integer images (uint16, uint32, uint64) are the one exception: both fitsy.image() and the Rust ImageBuilder::from_u16 / from_u32 / from_u64 constructors offset-encode pixels into the matching signed BITPIX and emit BSCALE = 1 with the standard BZERO offset automatically (FITS Standard Sec.4.4.2.5). Round-tripping uint* arrays through fitsy is lossless.

ASCII tables

For text-formatted TABLE extensions, use fitsy.ascii_table(). formats overrides the per-column TFORM; tnulls supplies a string sentinel for None cells in integer columns.