fitsio-pure
A pure Rust implementation of the FITS (Flexible Image Transport System) file format. No C dependencies, no unsafe code, and fully compatible with wasm32 targets.
Why pure Rust?
The existing fitsio crate wraps the C library cfitsio, which requires a system-level install and C toolchain. This creates friction:
- Cross-platform builds:
cfitsiomust be compiled or installed separately on macOS, Windows, and Linux. On macOS and Windows especially, getting the right library version and linking flags is a common pain point.fitsio-purebuilds withcargo buildon any platform with no external dependencies. - Faster builds: No C compilation step, no
pkg-config, no linking against system libraries. Cold builds are significantly faster. - WebAssembly: Compiles directly to
wasm32-unknown-unknownwith--no-default-features. No emscripten, no wasm-bindgen shims for a C library. - Cross-compilation:
cargo build --target aarch64-unknown-linux-gnujust works. No cross-compilation toolchain for C needed.
Features
| Feature | Default | Description |
|---|---|---|
std |
yes | Standard library support |
compat |
no | Drop-in replacement API matching the fitsio crate |
cli |
no | CLI binaries: fitsinfo, fitsconv |
array |
no | ndarray integration (ArrayD<T> support via ReadImage) |
The core library is no_std compatible (with alloc) and compiles to wasm32-unknown-unknown.
Using the compat API as a drop-in replacement for fitsio
Enable the compat feature to get an API that mirrors the fitsio crate. In most cases, switching only requires changing your dependency and use paths.
Cargo.toml
Replace your fitsio dependency:
# Before
[]
= "0.21"
# After
[]
= { = "https://github.com/OrbitalCommons/fitsio-pure", = ["compat"] }
Import changes
// Before (fitsio)
use FitsFile;
use ;
use HduInfo;
use ReadsKey;
use ;
// After (fitsio-pure with compat feature)
use FitsFile;
use ;
use HduInfo;
use ReadsKey;
use ;
Creating a FITS file and writing an image
use FitsFile;
use ;
let mut fitsfile = create
.overwrite
.open
.unwrap;
let description = ImageDescription ;
let hdu = fitsfile.create_image.unwrap;
let pixels: = vec!;
f32write_image.unwrap;
Reading image data
use FitsFile;
use ReadImage;
let fitsfile = open.unwrap;
let hdu = fitsfile.hdu.unwrap;
let pixels: = f32read_image.unwrap;
Reading and writing header keywords
use FitsFile;
use ;
let mut fitsfile = edit.unwrap;
let hdu = fitsfile.primary_hdu.unwrap;
// Write a keyword
hdu.write_key.unwrap;
// Read it back
let object: String = hdu.read_key.unwrap;
Reading table columns
use FitsFile;
use ReadsCol;
let fitsfile = open.unwrap;
let hdu = fitsfile.hdu.unwrap;
let ra: = f64read_col.unwrap;
let dec: = f64read_col.unwrap;
API comparison: fitsio vs fitsio-pure compat
The table below shows equivalent operations side by side.
| Operation | fitsio | fitsio-pure compat |
|---|---|---|
| Open file | FitsFile::open(path)? |
FitsFile::open(path)? |
| Edit file | FitsFile::edit(path)? |
FitsFile::edit(path)? |
| Create file | FitsFile::create(path).open()? |
FitsFile::create(path).open()? |
| Overwrite | FitsFile::create(path).overwrite().open()? |
FitsFile::create(path).overwrite().open()? |
| Get primary HDU | f.primary_hdu()? |
f.primary_hdu()? |
| Get HDU by name | f.hdu("SCI")? |
f.hdu("SCI")? |
| Get HDU by index | f.hdu(0)? |
f.hdu(0usize)? |
| Read header key | hdu.read_key::<T>(&mut f, name)? |
hdu.read_key::<T>(&f, name)? |
| Write header key | hdu.write_key(&mut f, name, val)? |
hdu.write_key(&mut f, name, val)? |
| Read image | hdu.read_image(&mut f)? |
T::read_image(&f, &hdu)? |
| Write image | hdu.write_image(&mut f, &data)? |
T::write_image(&mut f, &hdu, &data)? |
| Read column | hdu.read_col::<T>(&mut f, name)? |
T::read_col(&f, &hdu, name)? |
| HDU info | hdu.info |
hdu.info(&f)? |
| Number of HDUs | f.num_hdus()? |
f.num_hdus()? |
Key differences from fitsio:
- No mutable borrow required for read operations (reads take
&FitsFile, not&mut FitsFile). - Image and column read/write use associated functions (
T::read_image(...)) instead of methods on the HDU. - HDU info requires a reference to the file since there is no cached C-side state.
Supported types
Image pixel types: u8, i16, i32, i64, f32, f64
Header keyword types: i64, f64, bool, String
Table column types: i32, i64, f32, f64, String
CLI tools
Enable the cli feature to build the CLI binaries:
fitsinfo-- Print a summary of all HDUs in a FITS file.fitsconv-- Convert between FITS and other formats.
Benchmarks
Comparative I/O throughput between fitsio-pure and fitsio (cfitsio C wrapper).
Run with cargo run -p fits-benchmark --features pure,cfitsio --no-default-features --release.
| Test | fitsio-pure Write MP/s | cfitsio Write MP/s | fitsio-pure Read MP/s | cfitsio Read MP/s |
|---|---|---|---|---|
| f32 1024x1024 | 88 | 290 | 323 | 981 |
| f64 1024x1024 | 40 | 147 | 138 | 378 |
| i32 1024x1024 | 179 | 288 | 349 | 1025 |
| f32 4096x4096 | 78 | 278 | 103 | 315 |
| f64 4096x4096 | 49 | 147 | 63 | 161 |
| i32 4096x4096 | 99 | 283 | 128 | 311 |
cfitsio is faster because it maintains open file handles and writes data in-place, while fitsio-pure rebuilds the byte buffer on writes. Read performance is much closer thanks to HDU metadata caching and bulk endian conversion via bytemuck.
Full results and analysis in crates/fits-benchmark/README.md.
Reference Materials
- FITS Standard 3.0 Specification -- The official IAU FITS format definition
- cfitsio -- The canonical C FITS I/O library
- rust-fitsio -- Existing Rust FITS bindings (wraps cfitsio); the
compatmodule targets this API
Testing and Validation
This project uses a combination of synthetic round-trip tests and validation against real-world astronomical data.
Validation Approach
To ensure correctness, we validate fitsio-pure against official sample files and astropy.io.fits.
- Resources: See FITS_RESOURCES.md for a list of data sources (NASA, Astropy, LSST).
- Automated Fetching: Use
scripts/fetch_samples.shto download test files. - Cross-Validation: Use
scripts/validate_metadata.pyto generate ground-truth metadata fromastropyfor comparison. - Reporting: Discovered gaps are documented in VALIDATION_REPORT.md.
For more details on the current status, see the Validation Report.