A Rust implementation of the Rice compression algorithm used by
CFITSIO for the tile-compression of FITS
images. It is a transpiled and refactored port of ricecomp.c, originally
written by Richard White at STScI and contributed to CFITSIO in July 1999.
Rice coding is a fast, lossless entropy coder for integer data. It works best on images whose adjacent pixels are strongly correlated (astronomical frames, detector readouts, and similar), where the pixel-to-pixel differences are small and cluster near zero. For a full walk-through of how the coder works, see ALGORITHM.md.
- Lossless round-trip compression and decompression of
i32,i16, andi8pixel arrays. - Streaming encoder generic over any [
std::io::Write] sink. - Decoder writes into a caller-supplied output buffer so allocations can be reused.
- Graceful,
Result-based error handling — a truncated or malformed stream returns an error instead of panicking. - No external runtime dependencies.
- Bit-compatible with CFITSIO's
fits_rcomp/fits_rdecomp.
[dependencies]
ricecomp = "0.5"The API is split into two modules:
write::RCEncoder— compresses a slice of pixels into a byte sink.read::RCDecoder— decompresses a byte slice back into pixels.
Both take a block size (nblock), the number of pixels that share a single
Rice parameter. CFITSIO uses 32 for images; larger blocks adapt more slowly to
local statistics, smaller blocks spend more overhead on per-block headers.
The number of pixels (nx) and the block size are not stored in the stream,
so the decoder must be given the same values that were used to encode.
use ricecomp::read::RCDecoder;
use ricecomp::write::RCEncoder;
let pixels: [i32; 8] = [100, 101, 103, 102, 104, 108, 110, 109];
let block_size = 32;
// Encode into any std::io::Write sink (here a Vec<u8>).
let mut compressed: Vec<u8> = Vec::new();
let mut encoder = RCEncoder::new(&mut compressed);
let written = encoder
.encode(&pixels, pixels.len(), block_size)
.expect("encoding failed");
println!("compressed {} pixels into {} bytes", pixels.len(), written);
// Decode into a caller-supplied buffer.
let decoder = RCDecoder::new();
let mut output = vec![0u32; pixels.len()];
decoder
.decode(&compressed, pixels.len(), block_size, &mut output)
.expect("decoding failed");
let recovered: Vec<i32> = output.iter().map(|&x| x as i32).collect();
assert_eq!(recovered, pixels);RCEncoder::new accepts anything implementing std::io::Write, so you can
compress straight into a Vec<u8>, a file, or any other writer. The decoder
writes into the output slice you pass in, letting you reuse allocations across
calls.
The same pattern applies to narrower integers via the dedicated variants:
| Element type | Encode | Decode | Decode output |
|---|---|---|---|
i32 |
encode |
decode |
&mut [u32] |
i16 |
encode_short |
decode_short |
&mut [u16] |
i8 |
encode_byte |
decode_byte |
&mut [u8] |
Each width uses Rice parameters tuned to its dynamic range, so the streams are not interchangeable — decode with the same variant you encoded with.
write::EncodeError—ZeroSizeInput(empty input or zero block size) andEndOfBuffer(the writer could not accept more bytes).read::DecodeError—ZeroSizeInput,NotProperlyAllocated(input shorter than the mandatory 4-byte header), andEndOfBuffer(the compressed stream ended before the requested number of pixels was produced).
- Algorithm description, section 6.1 of the FITS tile-compression convention: https://fits.gsfc.nasa.gov/registry/tilecompression/tilecompression2.3.pdf
- Comparison of compression methods for FITS: https://arxiv.org/pdf/0903.2140.pdf
- Original C source:
ricecomp.cin CFITSIO, by Richard White (STScI), 1999.
Licensed under the MIT license. The bundled CFITSIO license, which covers the
original C implementation this port derives from, is included under
licenses/.