format: ZFP decoder (filter 32013), pure Rust, read-only

A port of the zfp 1.0.1 decoder and of H5Z-ZFP 1.1.1's decompression
path behind a new `zfp` feature (in `plugin-filters`): every mode
(fixed rate, precision, accuracy, reversible, expert), int32, int64,
float and double, 1-4 dimensional fields with partial blocks, and
headers written big-endian (values byte-swapped as H5Z-ZFP does).

H5Z-ZFP keeps the zfp header in cd_values (version word, then the
magic/metadata/mode bit stream); each chunk is the bare stream. The
decoder reproduces libzfp bit for bit: integer arithmetic wraps as
libzfp's, block exponents scale by exact powers of two, and the mode
goes through zfp_stream_mode as H5Z-ZFP hands it to zfp. A stream that
ends early is an error (libzfp reads past its buffer), as is a field
whose size is not the chunk's; the output is allocated only once the
stream holds a bit per block.

Tests: header/mode unit tests, and a counting-allocator fuzz of random
headers (expert parameters at their edges) and streams.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
osobh
2026-09-26 12:56:07 -05:00
co-authored by Claude Opus 5.5
parent f2ff2c424f
commit 23a4784e72
7 changed files with 1377 additions and 5 deletions
@@ -27,7 +27,8 @@ pub const FILTER_LZF: u16 = 32000;
pub const FILTER_BLOSC: u16 = 32001;
/// Bitshuffle, optionally with LZ4 or Zstandard (hdf5plugin's `Bitshuffle`).
pub const FILTER_BITSHUFFLE: u16 = 32008;
/// ZFP lossy floating-point compression (hdf5plugin's `Zfp`). Not supported.
/// ZFP lossy (and lossless) compression of numeric arrays (H5Z-ZFP;
/// hdf5plugin's `Zfp`). Read-only, with the `zfp` feature.
pub const FILTER_ZFP: u16 = 32013;
/// Blosc 2 (hdf5plugin's `Blosc2`).
pub const FILTER_BLOSC2: u16 = 32026;
@@ -6,7 +6,7 @@
//! build: the HDF5 standard filters (deflate, shuffle, Fletcher32, szip,
//! N-Bit, scale-offset) and the plugin filters whose cargo features are
//! enabled (LZ4, Zstandard, pcodec, LZF, bitshuffle, bzip2, blosc,
//! blosc2).
//! blosc2, zfp).
//! [`builtin_filters`] lists them.
//! * **Registered filters** (`std` only) — codecs the application supplies
//! for any other ID with [`register_filter`] (a [`FilterCodec`], or just a
@@ -162,7 +162,7 @@ pub fn known_filter(id: u16) -> Option<(&'static str, Option<&'static str>)> {
32001 => ("Blosc", Some("blosc")),
32004 => ("LZ4", Some("lz4")),
32008 => ("bitshuffle", Some("bitshuffle")),
32013 => ("ZFP", None),
32013 => ("ZFP", Some("zfp")),
32015 => ("Zstandard", Some("zstd")),
32019 => ("JPEG", None),
32022 => ("BitGroom", None),
@@ -455,8 +455,10 @@ pub(crate) mod tests {
let msg = FormatError::UnsupportedFilter(32026).to_string();
assert!(msg.contains("Blosc2") && msg.contains("`blosc2`"), "{msg}");
let msg = FormatError::UnsupportedFilter(32013).to_string();
assert!(msg.contains("ZFP") && msg.contains("`zfp`"), "{msg}");
let msg = FormatError::UnsupportedFilter(32019).to_string();
assert!(
msg.contains("ZFP") && msg.contains("not implemented"),
msg.contains("JPEG") && msg.contains("not implemented"),
"{msg}"
);
let msg = FormatError::UnsupportedFilter(32000).to_string();
+7
View File
@@ -432,6 +432,13 @@ pub(crate) static BUILTIN_FILTERS: &[BuiltinFilter] = &[
decode: crate::filters_bitshuffle::bitshuffle_decode,
encode: Some(crate::filters_bitshuffle::bitshuffle_encode),
},
#[cfg(feature = "zfp")]
BuiltinFilter {
id: crate::filter_pipeline::FILTER_ZFP,
name: "zfp",
decode: crate::filters_zfp::zfp_decode,
encode: None,
},
#[cfg(feature = "zstd")]
BuiltinFilter {
id: FILTER_ZSTD,
File diff suppressed because it is too large Load Diff
+2
View File
@@ -94,6 +94,8 @@ mod filters_bzip2;
#[cfg(feature = "lzf")]
pub mod filters_lzf;
mod filters_szip;
#[cfg(feature = "zfp")]
pub mod filters_zfp;
pub mod fixed_array;
pub mod float16;
pub mod fractal_heap;