Pure-Rust, zero-dependency reader and writer for FITS v4.0 — the Flexible Image Transport System format used throughout astronomy.
fitskit reads and writes the full FITS v4.0 standard — primary and extension HDUs, images, ASCII tables, and binary tables (including variable-length arrays) — with no external dependencies in the default build and no C toolchain required. It decodes every tile-compressed image type in the standard (RICE, GZIP, PLIO, HCOMPRESS) and — uniquely among pure-Rust FITS crates — writes RICE- and GZIP-compressed images that cfitsio's funpack reads back byte-for-byte.
- All standard HDU types — Primary, IMAGE, ASCII
TABLE,BINTABLE - Full read and write — round-trip files, bytes, or any
Read/Write - All BITPIX types — 8, 16, 32, 64, -32, -64
- BSCALE/BZERO — raw and scaled access, with the unsigned-integer convention
- Variable-length arrays —
PandQheap descriptors in binary tables - CHECKSUM/DATASUM — ones-complement integrity computation and verification
- Tile-compressed images (read) — RICE_1, GZIP_1, GZIP_2, PLIO_1, and HCOMPRESS_1, including float quantization with subtractive dithering; decoded bit-exactly against cfitsio's
funpack - Tile-compressed images (write) — encode RICE_1 and GZIP_1/GZIP_2 (integer and float); output verified byte-exact through
funpack - Zero dependencies by default — optional
imageandgzipfeatures pull in pure-Rust crates only
[dependencies]
fitskit = "0.2"use fitskit::{FitsFile, HduData, PixelData};
let fits = FitsFile::from_file("image.fits")?;
let primary = fits.primary();
println!("BITPIX = {}", primary.header.get_int("BITPIX").unwrap());
if let HduData::Image(img) = &primary.data {
println!("{}x{}", img.width().unwrap(), img.height().unwrap());
if let PixelData::F32(pixels) = &img.pixels {
println!("first pixel = {}", pixels[0]);
}
}
for hdu in fits.extensions() {
match &hdu.data {
HduData::Image(img) => println!("IMAGE: {:?}", img.axes),
HduData::BinTable(t) => println!("BINTABLE: {} rows, {} cols", t.nrows, t.columns.len()),
HduData::AsciiTable(t) => println!("TABLE: {} rows", t.nrows),
HduData::Empty => {}
}
}
# Ok::<(), fitskit::Error>(())use fitskit::{FitsFile, ImageData, PixelData, HeaderValue};
let pixels: Vec<i16> = (0..10000).map(|i| (i % 1000) as i16).collect();
let img = ImageData::new(vec![100, 100], PixelData::I16(pixels));
let mut fits = FitsFile::with_primary_image(img);
fits.primary_mut().header.set("OBJECT", HeaderValue::String("M31".into()), None);
fits.to_file("output.fits")?;
# Ok::<(), fitskit::Error>(())use fitskit::{FitsFile, Hdu, BinTableBuilder, BinColumnType};
let table = BinTableBuilder::new()
.add_column("RA", BinColumnType::D64(1))
.add_column("DEC", BinColumnType::D64(1))
.add_column("MAG", BinColumnType::E32(1))
.push_row(|row| {
row.write_f64(180.0);
row.write_f64(45.0);
row.write_f32(12.5);
})
.push_row(|row| {
row.write_f64(90.0);
row.write_f64(-30.0);
row.write_f32(8.2);
})
.build();
let mut fits = FitsFile::with_empty_primary();
fits.push_extension(Hdu::bintable_extension(table));
# let _ = fits.to_bytes();Tile-compressed images are stored on disk as a BINTABLE extension (the FITS
Tiled Image Compression convention). Hdu::as_compressed_image cheaply detects
them; decompress reassembles the full image lazily.
use fitskit::FitsFile;
let fits = FitsFile::from_file("compressed.fits")?;
for hdu in fits.extensions() {
if let Some(cimg) = hdu.as_compressed_image() {
println!("compression = {:?}", cimg.compression());
let image = cimg.decompress()?;
println!("decompressed to {:?}", image.axes);
}
}
# Ok::<(), fitskit::Error>(())ImageData::compress produces a compressed-image BINTABLE HDU ready to push
into a file. The output is read back byte-for-byte by cfitsio's funpack.
use fitskit::{FitsFile, ImageData, PixelData, CompressOptions};
let pixels: Vec<i16> = (0..10000).map(|i| (i % 1000) as i16).collect();
let img = ImageData::new(vec![100, 100], PixelData::I16(pixels));
// Default options: RICE_1, one tile per row, lossless for integers.
let hdu = img.compress(&CompressOptions::default())?;
let mut fits = FitsFile::with_empty_primary();
fits.push_extension(hdu);
fits.to_file("compressed.fits")?;
# Ok::<(), fitskit::Error>(())
gzipfeature: theGZIP_1/GZIP_2algorithms (e.g.CompressOptions { algorithm: CompressionType::Gzip1, .. }when writing, or decoding a GZIP-compressed tile on read) require thegzipfeature;RICE_1works without it. Build with--features gziporfitskit = { version = "0.2", features = ["gzip"] }.
With the image feature, ImageData converts to and from the image crate's DynamicImage — e.g. to save a FITS image as a PNG, or to ingest a raster as FITS. Build with fitskit = { version = "0.2", features = ["image"] }.
use fitskit::{FitsFile, HduData, ImageData};
let fits = FitsFile::from_file("image.fits")?;
if let HduData::Image(img) = &fits.primary().data {
// FITS → image crate (BSCALE/BZERO applied; 1.0/0.0 = identity)
let dynamic = img.to_dynamic_image(1.0, 0.0)?;
dynamic.save("image.png").unwrap();
// image crate → FITS, returning (ImageData, bscale, bzero)
let (restored, _bscale, _bzero) = ImageData::from_dynamic_image(&dynamic)?;
assert_eq!(restored.axes, img.axes);
}
# Ok::<(), fitskit::Error>(())use fitskit::{FitsFile, ImageData, PixelData};
let img = ImageData::new(vec![4], PixelData::U8(vec![1, 2, 3, 4]));
let fits = FitsFile::with_primary_image(img);
// Write with CHECKSUM/DATASUM keywords
let bytes = fits.to_bytes_with_checksum()?;
// Verify on read
let fits2 = FitsFile::from_bytes(&bytes)?;
fits2.primary().verify_datasum()?;
# Ok::<(), fitskit::Error>(())With the wcs feature, parse a two-axis celestial WCS straight from a header and
map between 1-based FITS pixel coordinates and world coordinates in
degrees. The spherical-projection math is backed by the zero-dependency
mapproj crate.
use fitskit::FitsFile;
let fits = FitsFile::from_file("image.fits")?;
let wcs = fits.primary().header.wcs()?;
// Pixel (1-based) -> world (lon, lat) in degrees
let (ra, dec) = wcs.pixel_to_world(256.5, 256.5).unwrap();
// ...and back to pixel
let (x, y) = wcs.world_to_pixel(ra, dec).unwrap();
# Ok::<(), fitskit::Error>(())Supported: the common 2-axis celestial case — CTYPEn = xxx--CCC for any
projection mapproj implements (TAN, SIN, ARC, ZEA, STG, AIT, MER,
CAR, CEA, SFL, MOL, conics, …), with the linear transform from CDi_j or
PCi_j + CDELTi (CD takes precedence). SIP distortions and 3+-axis / spectral
WCS are out of scope.
A FITS file is an ordered sequence of Header-Data Units (HDUs). fitskit mirrors that structure directly:
| Type | Role |
|---|---|
FitsFile |
Top-level container — an ordered Vec<Hdu>. Reads/writes files, byte slices, or any Read/Write; builder methods (with_primary_image, with_empty_primary, push_extension, to_file, to_bytes, to_bytes_with_checksum). |
Hdu |
One Header-Data Unit: a Header plus an HduData payload. as_compressed_image() exposes a tile-compressed image stored in a BINTABLE. |
HduData |
The payload enum: Empty, Image(ImageData), AsciiTable(AsciiTable), BinTable(BinTable). |
Header |
Ordered list of Keywords with typed accessors (get_int, get_float, get_string, get_bool) and set. |
Keyword / HeaderValue |
An 80-byte header card and its typed value (with CONTINUE long-string handling). |
ImageData / PixelData |
Image axes plus a typed pixel buffer (U8/I16/I32/I64/F32/F64); raw access and BSCALE/BZERO-scaled access (scaled_values). |
BinTable / BinColumn / BinColumnType / BinCellValue |
Binary-table model — columns, rows, and heap (variable-length arrays); built with BinTableBuilder. |
AsciiTable |
ASCII TABLE model with TFORMn column parsing and TSCALn/TZEROn scaling. |
CompressedImage / CompressionType / TileGeometry / Quantize |
Read-side view over a tile-compressed image: detect the algorithm and geometry, then decompress() to an ImageData. |
CompressOptions |
Write-side encode options (algorithm, tiling, quantization, dithering) for ImageData::compress / compress_image. |
Bitpix |
The BITPIX data type (8/16/32/64/-32/-64). |
Error / Result |
Crate error type and result alias. |
Decoding is done eagerly at read time (big-endian → native), except tile-compressed
images, which are decoded lazily on decompress() so the original compressed tiles
are preserved for a lossless round-trip write.
| Flag | Default | Description |
|---|---|---|
| (none) | ✓ | Core read/write, all HDU types, RICE_1 / PLIO_1 / HCOMPRESS_1 decompression, and RICE_1 compression — zero dependencies |
image |
Conversion between ImageData and the image crate's DynamicImage |
|
gzip |
GZIP_1/GZIP_2 tile compression and decompression via the pure-Rust miniz_oxide crate |
|
wcs |
Two-axis celestial World Coordinate System pixel ⇄ world transforms (Wcs, Header::wcs) via the zero-dependency mapproj crate |
The default build stays dependency-free; RICE_1, PLIO_1, and HCOMPRESS_1 decompression and RICE_1 compression all work without any feature (only GZIP_1/GZIP_2 need the gzip feature).
Among Rust FITS crates, fitskit fills a specific niche — pure Rust, zero default dependencies, full read + write including tables, complete compressed-image reading, and compressed-image writing, with no C toolchain to install. No other pure-Rust crate writes compressed FITS.
| Crate | Pure Rust | Write | Tables | Compressed read | Compressed write | Notes |
|---|---|---|---|---|---|---|
fitsio |
✗ | ✓ | ✓ | ✓ | ✓ | Wraps the cfitsio C library; needs a C toolchain |
fitsrs |
✓ | ✗ | partial | — | ✗ | Read-only |
fitrs |
✓ | ✓ | ✗ | ✗ | ✗ | Dormant; no table support |
| fitskit | ✓ | ✓ | ✓ | ✓ (all types) | ✓ (RICE/GZIP) | Zero default deps; no C dependency |
Supported
- Primary + extension HDUs; images, ASCII tables, binary tables (read + write)
- All BITPIX types; BSCALE/BZERO scaling and the unsigned-integer convention
- Variable-length arrays (
P/Qdescriptors) in binary tables - CHECKSUM/DATASUM computation and verification
- Tile-compressed image reading: RICE_1, GZIP_1, GZIP_2, PLIO_1, and
HCOMPRESS_1 — for integer and floating-point images, including quantization
with subtractive dithering (
NO_DITHER/SUBTRACTIVE_DITHER_1/SUBTRACTIVE_DITHER_2) - Tile-compressed image writing: RICE_1 and GZIP_1/GZIP_2 — integer
(lossless) and float (lossless via GZIP, or lossy quantized + dithered);
output verified byte-exact through cfitsio's
funpack - Two-axis celestial WCS pixel ⇄ world transforms (
wcsfeature) for the commonCTYPEn = xxx--CCCcase, linear transform fromCDi_jorPCi_j+CDELTi
Not supported
- Encoding
PLIO_1orHCOMPRESS_1(these decode only) - HCOMPRESS image smoothing (
SMOOTH≠ 0) on decode - Random groups (deprecated in FITS v4.0)
- WCS: SIP distortions, 3+-axis / spectral WCS,
PVi_mprojection parameters, and non-degreeCUNIT(thewcsfeature handles the 2-axis celestial linear- projection case only)