13 releases

new 0.3.0 Feb 7, 2025
0.2.11 Jul 19, 2024
0.2.9 May 22, 2023
0.2.7 Apr 25, 2023
0.1.1 Jan 4, 2023

#118 in Algorithms

Download history 4/week @ 2024-10-22 46/week @ 2024-10-29 29/week @ 2024-11-05 24/week @ 2024-11-12 33/week @ 2024-11-19 11/week @ 2024-11-26 2/week @ 2024-12-03 41/week @ 2024-12-10 14/week @ 2024-12-17 1/week @ 2025-01-28 131/week @ 2025-02-04

132 downloads per month
Used in wcs

Apache-2.0 OR MIT

290KB
6K SLoC

FITS file reader written in pure Rust

API Documentation on docs.rs testing CI

This crate is under development, it was initiated for reading FITS images mapped onto HEALPix cell in the sky (See the HiPS IVOA standard) for using inside the Aladin Lite web sky atlas.

Currently, fitsrs supports reading multiple HDU and is mainly dedicated to image extension reading. For interpreting WCS keywords, see wcs-rs. A very new support of binary table extension has been added. This has been done mainly for supporting the tiled image convention for storing compressed images in binary tables. This stores tile images inside variable length arrays of a binary table. The ASCII table extension parsing has not been implemented but it is possible to get an iterator over the data bytes as well as its mandatory cards from the header.

Contributing

[!WARNING] Running the test involves test files you can download here. This tar is 2.2GB.

Once the tar file has been downloaded, put it into the root on your cloned repo and extract it:

tar -xvf fits-rs-test-files.tar

Once the files have been extracted you can run the tests locally:

cargo test --release

Features

  • Support single typed data block (i.e. image type data)
  • Single HDU parsing, header and data units
  • Support FITS files that may not fit in memory (iterator, possibility to seek directly to a specific pixel index/row)
  • Async reading (requires to read the whole data. Seeking is not possible)
  • Parsing of comments, history, continued, cards.
  • Keep all the cards in the original order
  • Basic support of Bintable
  • Tiled image convention for storing compressed images in FITS binary tables
    • Compression supported, GZIP, GZIP2 and RICE on u8, i16, i32 and f32.
    • H_compress and PLI0 are not supported
    • Dithering techniques for floating point images. Not well tested (test samples are welcome)
    • NULL_PIXEL_MASK column and ZMASKCMP keyword is not supported
  • FITS writer/serializer
  • ASCII table extension parsing
  • Tile-compressed in binary table files (https://fits.gsfc.nasa.gov/registry/tilecompression.html). Only RICE and GZIP supported
  • Support of multiple HDU. Image and binary tables extension support. Provide an idiomatic Rust iterator over the list of HDU.
  • WCS parsing, see wcs-rs

[!WARNING] Features not done are not planned to be done. A work for supporting them can be done only if people have use cases for those i.e. it is only at user's requests. The FITS standard and its conventions are massive and it is a huge work to support all the cases and sub cases.

License

fitsrs has the double the MIT/Apache-2.0 license.

It uses code adapted from the famous CFITSIO library.Especially the RICE compression/decompression source code has been ported from the original cfitsio code to Rust.

Example

use std::fs::File;
use std::io::Cursor;
use fitsrs::{Fits, ImageData, HDU, hdu::header::Xtension};

use std::io::{BufReader, Read};

let f = File::open("samples/fits.gsfc.nasa.gov/EUVE.fits").unwrap();
let reader = BufReader::new(f);

let mut hdu_list = Fits::from_reader(reader);

while let Some(Ok(hdu)) = hdu_list.next() {
    match hdu {
        // skip the primary HDU
        HDU::Primary(_) => (),
        HDU::XImage(hdu) => {
            let xtension = hdu.get_header().get_xtension();

            let naxis1 = *xtension.get_naxisn(1).unwrap();
            let naxis2 = *xtension.get_naxisn(2).unwrap();

            let num_pixels = (naxis2 * naxis1) as usize;

            match hdu_list.get_data(hdu) {
                ImageData::U8(it) => {
                    let data = it.collect::<Vec<_>>();
                    assert_eq!(num_pixels, data.len())
                },
                ImageData::I16(it) => {
                    let data = it.collect::<Vec<_>>();
                    assert_eq!(num_pixels, data.len())
                },
                ImageData::I32(it) => {
                    let data = it.collect::<Vec<_>>();
                    assert_eq!(num_pixels, data.len())
                },
                ImageData::I64(it) => {
                    let data = it.collect::<Vec<_>>();
                    assert_eq!(num_pixels, data.len())
                },
                ImageData::F32(it) => {
                    let data = it.collect::<Vec<_>>();
                    assert_eq!(num_pixels, data.len())
                },
                ImageData::F64(it) => {
                    let data = it.collect::<Vec<_>>();
                    assert_eq!(num_pixels, data.len())
                },
            }
        },
        HDU::XBinaryTable(hdu) => {
            let num_rows = hdu.get_header()
                .get_xtension()
                .get_num_rows();

            let rows = hdu_list.get_data(hdu)
                .row_iter()
                .collect::<Vec<_>>();

            assert_eq!(num_rows, rows.len());
        },
        HDU::XASCIITable(hdu) => {
            let num_bytes = hdu.get_header()
                .get_xtension()
                .get_num_bytes_data_block();

            let data = hdu_list.get_data(hdu)
                .collect::<Vec<_>>();

            assert_eq!(num_bytes as usize, data.len());
        },
    }
}

For async input readers:

#[tokio::test]
async fn parse_fits_async() {
    use std::fs::File;
    use std::io::Cursor;
    use fitsrs::hdu::AsyncHDU;
    use fitsrs::hdu::data::stream::Stream;
    use fitsrs::async_fits::AsyncFits;
    use fitsrs::hdu::header::extension::Xtension;

    use std::io::{BufReader, Read};

    // reader needs to implement futures::io::AsyncRead
    let f = File::open("samples/fits.gsfc.nasa.gov/EUVE.fits").unwrap();
    let reader = BufReader::new(f);

    let mut hdu_list = AsyncFits::from_reader(reader);

    while let Some(Ok(mut hdu)) = hdu_list.next().await {
        match hdu {
            AsyncHDU::Primary(_) => (),
            AsyncHDU::Image(hdu) => {
                let xtension = hdu.get_header().get_xtension();

                let naxis1 = *xtension.get_naxisn(1).unwrap() as usize;
                let naxis2 = *xtension.get_naxisn(2).unwrap() as usize;

                let num_pixels = naxis2 * naxis1;

                match hdu_list.get_data(hdu) {
                    Stream::U8(st) => {
                        let data = st.collect::<Vec<_>>().await;
                        assert_eq!(num_pixels, data.len())
                    },
                    Stream::I16(st) => {
                        let data = st.collect::<Vec<_>>().await;
                        assert_eq!(num_pixels, data.len())
                    },
                    Stream::I32(st) => {
                        let data = st.collect::<Vec<_>>().await;
                        assert_eq!(num_pixels, data.len())
                    },
                    Stream::I64(st) => {
                        let data = st.collect::<Vec<_>>().await;
                        assert_eq!(num_pixels, data.len())
                    },
                    Stream::F32(st) => {
                        let data = st.collect::<Vec<_>>().await;
                        assert_eq!(num_pixels, data.len())
                    },
                    Stream::F64(st) => {
                        let data = st.collect::<Vec<_>>().await;
                        assert_eq!(num_pixels, data.len())
                    },
                }
            },
            AsyncHDU::XBinaryTable(hdu) => {
                let num_bytes = hdu.get_header()
                    .get_xtension()
                    .get_num_bytes_data_block();

                let it_bytes = hdu_list.get_data(hdu);
                let data = it_bytes.collect::<Vec<_>>().await;
                assert_eq!(num_bytes as usize, data.len());
            },
            AsyncHDU::XASCIITable(hdu) => {
                let num_bytes = xhdu.get_header()
                    .get_xtension()
                    .get_num_bytes_data_block();

                let it_bytes = hdu_list.get_data(hdu);
                let data = it_bytes.collect::<Vec<_>>().await;
                assert_eq!(num_bytes as usize, data.len());
            },
        }
    }
}

Dependencies

~1.7–3MB
~58K SLoC