fits-well
A blazing-fast Rust reader and writer for FITS (Flexible Image Transport System) files — the standard data format of astronomy — targeting the full FITS 4.0 standard.
Two goals shape every decision:
- Fast — zero-copy where the format allows, lazy seeking HDU access, reused scratch buffers, and tile-parallel (de)compression across a rayon pool.
- Whole-standard coverage — images, ASCII tables, binary tables with a heap and variable-length arrays, random groups (read), a typed WCS layer (23 projections), time coordinates, and tiled compression.
Install
[]
= "0.1"
The default build pulls in tiled compression (flate2) and tile parallelism
(rayon). For the dependency-light pure-Rust core, use
default-features = false (see the Feature flags section below).
Usage
Inspect a file
open scans every HDU boundary from the headers alone — no pixel data is read.
use File;
use FitsReader;
let reader = open?;
println!;
for in reader.hdus.iter.enumerate
# Ok::
Write and read an image
shape is fastest-axis-first (NAXIS1 first); samples is the flat buffer.
use File;
use ;
let image = Image ;
let mut writer = new;
writer.write_image?;
writer.into_inner.sync_all?;
let mut reader = open?;
// `image_indices` lists the image-bearing HDUs, so you pick one rather than
// hard-coding it. `read_image` borrows the data unit in place (zero-copy).
let raw = reader.read_image?;
// `bitpix` is the stored width; `sample_type()` is the *effective* type, resolving
// the unsigned / signed-byte BZERO conventions (cfitsio's "equivalent type").
println!;
// `decode()` byte-swaps into an owned host-endian buffer; `physical()` applies
// BSCALE/BZERO and maps any BLANK value to NaN.
if let I16 = raw.decode
# Ok::
A tile-compressed image (FITS §10) reads back through the same read_image
call — it detects ZIMAGE and decompresses transparently. To write one:
# use File;
# use ;
# let image = Image ;
let options = tiled; // 8×8 tiles
let mut writer = new;
writer.write_compressed_image?;
# writer.into_inner.sync_all?;
# Ok::
Binary tables
Address a column by index or by its TTYPEn name; the handle decodes on demand.
use File;
use ;
let columns = ;
let mut writer = new;
writer.write_table?; // 3 rows
writer.into_inner.sync_all?;
let mut reader = open?;
let table = reader.read_table?; // the table is HDU 1 (HDU 0 is the empty primary)
println!;
// `.raw()` is the stored, typed plane; `.physical()` applies TZEROn/TSCALn and
// maps TNULLn to NaN, widening to f64. `.unsigned()`, `.complex()`, `.bits()`,
// and `.vla()` cover the other column kinds the same way.
println!;
println!;
# Ok::
World Coordinate System
Header::wcs parses the CTYPEn/CRPIXn/CRVALn/… keywords into a transform
that converts between pixel and sky coordinates in the file's declared frame.
use File;
use FitsReader;
let reader = open?;
let wcs = reader.hdus.header.wcs?; // None = the primary WCS
let sky = wcs.pixel_to_world; // RA/Dec at the reference pixel
let pixel = wcs.world_to_pixel; // and back again
println!;
# Ok::
The typed time layer (Header::time, Datetime, TimeScale) handles
ISO-8601/JD/MJD, epochs, and UTC…TCB/GPS/UT1 scale conversions.
Examples
Runnable end-to-end programs live in examples/:
Feature flags
| Feature | Default | What it adds |
|---|---|---|
compression |
✅ | Tiled image + table (de)compression — GZIP_1/2, RICE_1, PLIO_1, HCOMPRESS_1 (pulls in flate2). |
parallel |
✅ | Tile-parallel (de)compression across a rayon pool (implies compression). |
mmap |
— | FitsReader::open_mmap — zero-copy reads straight off memory-mapped pages (memmap2). |
ndarray |
— | RawImage/Image → typed ImageArray or a physical ArrayD<f64> in FITS axis order. |
--no-default-features gives the pure-Rust core (block / header / HDU / reader /
writer / WCS / time); its only unconditional dependencies are bitvec (packed
X bit-array columns) and num-complex (C/M complex columns). WCS (§8) and
time (§9) are dependency-free pure math and are always compiled.
License
Licensed under either of
- Apache License, Version 2.0 (LICENSE-APACHE)
- MIT license (LICENSE-MIT)
at your option.