Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bintable data better support, tile compressed image (gzip and rice), additional things #20

Merged
merged 36 commits into from
Feb 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
9976c0f
first commit
bmatthieu3 Jan 13, 2025
8428743
WIP add FITSFile struct
bmatthieu3 Jan 16, 2025
d99df2b
add a FITSFile utilitary struct with an open method. It can detect if…
bmatthieu3 Jan 16, 2025
2f9ba58
refac FITSFile struct and add a GzReader that is decorator reader che…
bmatthieu3 Jan 17, 2025
10c3c13
iterator over the bytes of a row in a bintable
bmatthieu3 Jan 17, 2025
340a046
TCI (Tile Compressed Image feature): wip parse cards
bmatthieu3 Jan 17, 2025
ca37d6b
remove samples from github repo
bmatthieu3 Jan 17, 2025
3f1188b
wip return iterator for variable array + TCI: GZIP
bmatthieu3 Jan 21, 2025
6b88953
fix asciitable mandatory cards parsing
bmatthieu3 Jan 22, 2025
fdca494
make Header object clonable #15
bmatthieu3 Jan 22, 2025
d21a22f
Add iterators for retrieving data for image extension. Add an iterato…
bmatthieu3 Jan 22, 2025
453c718
refac parsing of variable length arrays in bintable. Impl the TFORM Q…
bmatthieu3 Jan 22, 2025
18d7570
seek to the end of the data block instead of reading all the bytes...
bmatthieu3 Jan 22, 2025
7aa67d5
Continued: seek to the next HDU
bmatthieu3 Jan 23, 2025
66abfc8
rename TFormBinaryTable -> TForm
bmatthieu3 Jan 23, 2025
1b40544
wip does not compile
bmatthieu3 Jan 24, 2025
1f5b8f3
bintable refac, some api endpoints
bmatthieu3 Jan 25, 2025
bd3b28d
Make Fits clonable if its inner reader is cloneable #15
bmatthieu3 Jan 25, 2025
5746ec4
fix some bintable tests
bmatthieu3 Jan 27, 2025
d20ec42
remove tci feature, this is a FITS convention, we support it by defau…
bmatthieu3 Jan 29, 2025
72b3aa4
WIP rice porting to a rust RICEDecoder reader
bmatthieu3 Jan 29, 2025
b1277a5
provide generic impl over u8, i16 and i32 of the RICE decoding algori…
bmatthieu3 Jan 30, 2025
4dc02dc
rebase on master, making the tests pass
bmatthieu3 Jan 30, 2025
915c0cc
many fixes on bintable parsing, fix the gzip tile compressed image de…
bmatthieu3 Jan 31, 2025
8f5f65c
WIP fixing rice for integers
bmatthieu3 Jan 31, 2025
d477f6c
fix RICE for integer (u8, i16 and i32). Tested on i16 a.t.m
bmatthieu3 Feb 3, 2025
c9f9cc2
compute the tile size in function of the index of the row
bmatthieu3 Feb 3, 2025
38d9c74
add a seek to row method available for seekable readers
bmatthieu3 Feb 3, 2025
7275a89
provide a seek to pixel for images
bmatthieu3 Feb 3, 2025
0a956eb
change the readme
bmatthieu3 Feb 4, 2025
e06af63
Rice: support different blocksize for decompression
bmatthieu3 Feb 4, 2025
07db478
add GZIP2 on integers (not tested)
bmatthieu3 Feb 4, 2025
b96275f
Does not compile, refac to introduce the dithering: W.I.P
bmatthieu3 Feb 5, 2025
eaeffd7
WIP debugging rice on f32. Take bscale and bzero if present for integ…
bmatthieu3 Feb 6, 2025
0cfa4d4
final commit
bmatthieu3 Feb 7, 2025
9762167
cargo fmt
bmatthieu3 Feb 7, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ fitswasm/test
fitswasm/Cargo.lock
fitswasm/target
fitswasm/pkg
samples/*
fits-rs-test-files.tar*
.DS_Store
samples
samples

publish-test-files.sh
19 changes: 16 additions & 3 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,12 +1,25 @@
# CHANGELOG.md

## 0.3.0 (unreleased)
## 0.3.0

Features:

- BREAKING API change. Provide an iterator over the HDU list
- Provide an iterator over the HDU list
- #16: Parsing of comments, history, continued, card as enum.
- BREAKING API change. `get` method on the Header object now takes a `&str` instead of a `[u8; 8]` slice.
- #20 Basic support of Bintable
- #20 Faster parsing using the benefit of the Seek trait to skip data block not wanted
- Seek to a specific pixel in the ImageData struct
- Possibility to access the raw bytes of a binary table for cursor typed readers
- Fits is cloneable as long as the provided reader is cloneable
- #20 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 texture not well tested (test samples are welcome)
* `NULL_PIXEL_MASK` column and `ZMASKCMP` keyword is not supported

Breaking changes:

- The iterator over HDUs is more rust idiomatic but introduces a different usage of the API
- `Header.get` method now takes a `&str` instead of a `[u8; 8]` slice.

Bugfixes:

Expand Down
14 changes: 13 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,31 @@ exclude = [
]

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
byteorder = "1.4.2"
serde = { version = "1.0.152", features = ["derive"] }
serde_json = "1.0"
futures = "0.3.31"
async-trait = "0.1.66"
quick-error = "2.0.1"
flate2 = "1.0.35"
log = "0.4"

[dev-dependencies]
test-case = "3.0.0"
tokio = { version = "1.26.0", features = ["rt", "macros"]}
image = { version = "0.25.5" }

[profile.release]
lto = true
opt-level = 3

[profile.test]
incremental = true
opt-level = 0
debug = 1
lto = true
debug-assertions = true
overflow-checks = true
rpath = false
codegen-units = 512
148 changes: 52 additions & 96 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
Fits reader written in pure Rust
--------------------------------
FITS file reader written in pure Rust
-------------------------------------

[![](https://img.shields.io/crates/v/fitsrs.svg)](https://crates.io/crates/fitsrs)
[![](https://img.shields.io/crates/d/fitsrs.svg)](https://crates.io/crates/fitsrs)
[![API Documentation on docs.rs](https://docs.rs/fitsrs/badge.svg)](https://docs.rs/fitsrs/)
![testing CI](https://github.com/cds-astro/fitsrs/actions/workflows/rust.yml/badge.svg)

This crate is under development, it was initiated for reading fits HiPS tile, i.e. generated from hipsgen.
This crate is under development, it was initiated for reading FITS images mapped onto HEALPix cell in the sky (See the [HiPS IVOA](https://www.ivoa.net/documents/HiPS/) standard) for using inside the [Aladin Lite](https://github.com/cds-astro/aladin-lite) web sky atlas.

This fits parser only supports image data (not tables), and does not know anything about WCS parsing.
For WCS parsing, see [wcsrs](https://github.com/cds-astro/wcs-rs).
This parser is able to parse extension HDUs. Ascii tables and binary tables are still not properly parsed, only the list bytes of their data block can be retrieved but no interpretation/parsing is done on it.
Currently, fitsrs supports reading multiple HDU and is mainly dedicated to image extension reading.
For interpreting WCS keywords, see [wcs-rs](https://github.com/cds-astro/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
------------
Expand All @@ -34,89 +35,41 @@ To Do list
----------

* [X] Support single typed data block (i.e. image type data)
* [X] Single HDU parsing, header and data units
* [X] Support big fits file parsing that may not fit in memory (iterator usage)
* [X] Async reading (experimental and not tested)
* [X] Keep CARD comment
* [ ] Support compressed fits files (https://fits.gsfc.nasa.gov/registry/tilecompression.html)
* [ ] Support data table (each column can have a specific types)
* [X] Support of multiple HDU, fits extensions (in progress, only the header is parsed)
* [ ] WCS parsing, see [wcsrs](https://github.com/cds-astro/wcs-rs)
* [X] Single HDU parsing, header and data units
* [X] Support FITS files that may not fit in memory (iterator, possibility to seek directly to a specific pixel index/row)
* [X] Async reading (requires to read the whole data. Seeking is not possible)
* [X] Parsing of comments, history, continued, cards.
* [X] Keep all the cards in the original order
* [X] Basic support of Bintable
* [X] Tiled image convention for storing compressed images in FITS binary tables
- [X] Compression supported, GZIP, GZIP2 and RICE on u8, i16, i32 and f32.
- [ ] H_compress and PLI0 are not supported
- [X] 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
* [X] Support of multiple HDU. Image and binary tables extension support. Provide an idiomatic Rust iterator over the list of HDU.
* [X] WCS parsing, see [wcs-rs](https://github.com/cds-astro/wcs-rs)

Example
----------

For files that can fit in memory

```rust
use std::fs::File;
use std::io::Cursor;
use std::io::Read;
use fitsrs::{Fits, HDU};
use fitsrs::{hdu::header::Xtension, hdu::data::Data};

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

let mut buf = Vec::new();
f.read_to_end(&mut buf).unwrap();
let reader = Cursor::new(&buf[..]);
> [!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.

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

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

let naxis1 = *xtension.get_naxisn(1).unwrap() as usize;
let naxis2 = *xtension.get_naxisn(2).unwrap() as usize;
fitsrs has the double the MIT/Apache-2.0 license.

let num_pixels = naxis2 * naxis1;
It uses code adapted from the famous [CFITSIO](https://github.com/HEASARC/cfitsio/blob/main/licenses/License.txt) library.Especially the RICE compression/decompression source code has been ported from the original cfitsio [code](https://github.com/HEASARC/cfitsio/blob/main/ricecomp.c) to Rust.

match hdu.get_data(&mut hdu_list) {
Data::U8(mem) => assert_eq!(num_pixels, mem.len()),
Data::I16(mem) => assert_eq!(num_pixels, mem.len()),
Data::I32(mem) => assert_eq!(num_pixels, mem.len()),
Data::I64(mem) => assert_eq!(num_pixels, mem.len()),
Data::F32(mem) => assert_eq!(num_pixels, mem.len()),
Data::F64(mem) => assert_eq!(num_pixels, mem.len()),
}
},
HDU::XBinaryTable(hdu) => {
let num_bytes = hdu.get_header()
.get_xtension()
.get_num_bytes_data_block();

match hdu.get_data(&mut hdu_list) {
Data::U8(mem) => assert_eq!(num_bytes as usize, mem.len()),
_ => unreachable!()
}
},
HDU::XASCIITable(hdu) => {
let num_bytes = hdu.get_header()
.get_xtension()
.get_num_bytes_data_block();

match hdu.get_data(&mut hdu_list) {
Data::U8(mem) => assert_eq!(num_bytes as usize, mem.len()),
_ => unreachable!()
}
},
}
}
```

For BufReader
Example
----------

```rust
use std::fs::File;
use std::io::Cursor;
use fitsrs::hdu::HDU;
use fitsrs::hdu::data::DataIter;
use fitsrs::fits::Fits;
use fitsrs::hdu::header::Xtension;
use fitsrs::{Fits, ImageData, HDU, hdu::header::Xtension};

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

Expand All @@ -137,49 +90,52 @@ while let Some(Ok(hdu)) = hdu_list.next() {

let num_pixels = (naxis2 * naxis1) as usize;

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

let it_bytes = hdu.get_data(&mut hdu_list);
let data = it_bytes.collect::<Vec<_>>();
assert_eq!(num_bytes as usize, data.len());
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 it_bytes = hdu.get_data(&mut hdu_list);
let data = it_bytes.collect::<Vec<_>>();
let data = hdu_list.get_data(hdu)
.collect::<Vec<_>>();

assert_eq!(num_bytes as usize, data.len());
},
}
Expand Down Expand Up @@ -217,7 +173,7 @@ async fn parse_fits_async() {

let num_pixels = naxis2 * naxis1;

match hdu.get_data(&mut hdu_list) {
match hdu_list.get_data(hdu) {
Stream::U8(st) => {
let data = st.collect::<Vec<_>>().await;
assert_eq!(num_pixels, data.len())
Expand Down Expand Up @@ -249,7 +205,7 @@ async fn parse_fits_async() {
.get_xtension()
.get_num_bytes_data_block();

let it_bytes = hdu.get_data(&mut hdu_list);
let it_bytes = hdu_list.get_data(hdu);
let data = it_bytes.collect::<Vec<_>>().await;
assert_eq!(num_bytes as usize, data.len());
},
Expand All @@ -258,7 +214,7 @@ async fn parse_fits_async() {
.get_xtension()
.get_num_bytes_data_block();

let it_bytes = xhdu.get_data(&mut hdu_list);
let it_bytes = hdu_list.get_data(hdu);
let data = it_bytes.collect::<Vec<_>>().await;
assert_eq!(num_bytes as usize, data.len());
},
Expand Down
14 changes: 7 additions & 7 deletions fitswasm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ extern crate wasm_bindgen;
// see https://rustwasm.github.io/wasm-bindgen/
use wasm_bindgen::prelude::*;

use fitsrs::{BitpixValue, DataType, FITSHeaderKeyword, FITSKeywordValue, Fits};
use fitsrs::{Bitpix, DataType, FITSHeaderKeyword, FITSKeywordValue, Fits};
use js_sys::{Float32Array, Float64Array, Int16Array, Int32Array, Uint8Array};

#[wasm_bindgen(js_name = read)]
Expand Down Expand Up @@ -46,12 +46,12 @@ pub fn read(bytes: Vec<u8>) -> Result<js_sys::Object, JsValue> {
FITSHeaderKeyword::Simple => (String::from("SIMPLE"), JsValue::undefined()),
FITSHeaderKeyword::Bitpix(b) => {
let value = match b {
BitpixValue::F32 => -32,
BitpixValue::F64 => -64,
BitpixValue::U8 => 8,
BitpixValue::I16 => 16,
BitpixValue::I32 => 32,
BitpixValue::I64 => 64,
Bitpix::F32 => -32,
Bitpix::F64 => -64,
Bitpix::U8 => 8,
Bitpix::I16 => 16,
Bitpix::I32 => 32,
Bitpix::I64 => 64,
};

(String::from("BITPIX"), JsValue::from_f64(value as f64))
Expand Down
Loading