A fast pure Rust fits library 🔭 🦀
The goal of FeFits is to provide a fast, conformant and friendly API for working with The Flexible Image Transport System format common in Astronomy.
The FITS standard is broad and this library does not implement all of it. Rather, FeFits attempts to cover the most general use cases first with a strong architectural foundation on which additional parts of the spec and extensions can be added.
Currently FeFits supports:
- Header read/write support
- Read only SIMPLE image data
- Read only bintable tiled image data
- Bit-for-bit parity with funpack for rice-compressed images, including quantized images.
FeFits does not attempt to match the api of cfitsio or astropy.fits exactly, though attempts have been made to keep the api as familiar as possible.
Here is how one might dump the contents of a file's headers:
let mut fits = Fits::open("test.fits").unwrap();
for i in 0..fits.len() { // HDUs
let header = fits.read_header(i).unwrap_or_default();
for card in header.cards() {
println!("{card}");
}
}Or to access image data:
let mut fits = Fits::open("test.fits")?;
let hdu = fits.find_image()?.ok_or("no 2D image HDU found")?;
let image = fits.read_image(hdu)?;
let pixels = image.into_f64();
println!(
"First 10 pixel values: {:?}",
&pixels[..10.min(pixels.len())]
);See the examples directory for more usage examples. The integration tests also contain many examples of working with the API at a high level.
As it turns out, decompressing tile-compressed FITS files is "embarrassingly parallel".
With the rayon feature (enabled by default) FeFits will utilize multiple cores to decompress
images. This results in a 5x speedup over cfitsio and a 6x speedup over astropy.fits
on a machine with multiple cores.
Benchmarks are included in this repo (they don't cover Python) that test reading image data from coj0m421-sq37-20260512-0189-e91.fits.fz, a 150mb rice-compressed floating point image and tfn0m419-sq32-20250218-0237-e00.fits.fz a 5mb rice-compressed integer image:
| image | fefits | cfitsio | astropy.fits |
|---|---|---|---|
| 150mb fp | 52ms | 281ms | 327ms |
| 5mb int | 6ms | 26ms | 50ms |
Even without parallelism FeFits performs comparably to cfitsio and faster than astropy.io.fits:
| image | fefits | cfitsio | astropy.fits |
|---|---|---|---|
| 150mb fp | 285ms | 281ms | 327ms |
| 5mb int | 32ms | 28ms | 50ms |
The very unscientific benchmarks for astropy.io.fits. were done with this script:
import time
from astropy.io import fits
start = time.time()
hdul = fits.open("../fefits/tests/fixtures/coj0m421-sq37-20260512-0189-e91.fits.fz")
data = hdul['SCI'].data
elapsed = time.time() - start
print(elapsed)Since astropy also uses cfitsio under the hood, unsurprisingly astropy was slightly slower probably due to marshalling Python objects.
Benchmarks can be run with cargo bench.
A suite of integration tests are present that rely on a ~300mb fixtures archive. The test-integration
Just recipe will automatically download this archive, extract it and
run the tests. Or, you can download the fixtures manually to run the tests.
There are quite a few use cases for pure Rust based fits tooling on the horizon including fast thumbnail generation services, silly command line applications and in-browser image manipulation tools: imagine a webassembly based JS9! All of these without the need for complicated C compiler toolchains, FORTRAN or the performance penalties of Python.