fix(format): write and read the registered HDF5 LZ4 filter format
Filter 32004 chunks were framed as a 4-byte little-endian size plus one LZ4 block. That is not the registered HDF5 LZ4 format (H5Zlz4.c: 8-byte big-endian total size, 4-byte big-endian block size, then per block a 4-byte big-endian compressed length and the block, stored raw when the length equals the block size), so libhdf5 + hdf5plugin could not read our LZ4 datasets and we could not read theirs (h5ex_d_lz4.h5: "lz4: 0 is not a valid match offset"). Write the registered format (cd_values[0] is honoured as the block size, default 1 GiB like the plugin) and read it, multi-block and raw blocks included. Chunks in the old framing stay readable: an HDF5 chunk is under 4 GiB, so a registered chunk always starts with four zero bytes and is at least 12 bytes long, while an old one starts with four zero bytes only when empty (5 bytes). Tests: lz4_reads_registered_hdf5_format (chunk of the HDF Group's h5ex_d_lz4.h5, block size 3), lz4_writes_registered_hdf5_format, lz4_reads_legacy_clawhdf5_format, and hdf5plugin_reads_our_lz4 (ignored interop test; failed before with "filter returned failure during read"). Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
@@ -940,3 +940,48 @@ fn provenance_verify_written_file() {
|
||||
.unwrap();
|
||||
assert_eq!(result, clawhdf5_format::provenance::VerifyResult::Ok);
|
||||
}
|
||||
|
||||
// ---- hdf5plugin interop: registered third-party compression filters ----
|
||||
|
||||
/// Write `data` (f64, 1-D, chunked) with `configure` applied, then read it
|
||||
/// back with h5py + hdf5plugin (libhdf5's registered filter plugins) and
|
||||
/// return the values it decodes.
|
||||
#[cfg(any(feature = "lz4", feature = "zstd"))]
|
||||
fn hdf5plugin_roundtrip(
|
||||
tag: &str,
|
||||
data: &[f64],
|
||||
configure: impl FnOnce(&mut clawhdf5_format::type_builders::DatasetBuilder),
|
||||
) -> Vec<f64> {
|
||||
let mut fw = FileWriter::new();
|
||||
let ds = fw.create_dataset("data");
|
||||
ds.with_f64_data(data)
|
||||
.with_shape(&[data.len() as u64])
|
||||
.with_chunks(&[250]);
|
||||
configure(ds);
|
||||
let bytes = fw.finish().unwrap();
|
||||
let path = std::env::temp_dir().join(format!("clawhdf5_hdf5plugin_{tag}.h5"));
|
||||
std::fs::write(&path, &bytes).unwrap();
|
||||
let script = format!(
|
||||
"import h5py,hdf5plugin,json; f=h5py.File('{}','r'); print(json.dumps(f['data'][:].tolist()))",
|
||||
path.display()
|
||||
);
|
||||
let stdout = h5py_read(&path, &script);
|
||||
serde_json::from_str(&stdout).unwrap()
|
||||
}
|
||||
|
||||
/// libhdf5's LZ4 plugin must decode what we write (it could not while we
|
||||
/// wrote a private 4-byte-LE-size framing).
|
||||
#[cfg(feature = "lz4")]
|
||||
#[test]
|
||||
#[ignore = "requires Python h5py + hdf5plugin"]
|
||||
fn hdf5plugin_reads_our_lz4() {
|
||||
let data: Vec<f64> = (0..1000).map(|i| (i % 37) as f64 * 0.5).collect();
|
||||
let got = hdf5plugin_roundtrip("lz4", &data, |ds| {
|
||||
ds.with_lz4();
|
||||
});
|
||||
assert_eq!(got, data);
|
||||
let got = hdf5plugin_roundtrip("lz4_noshuffle", &data, |ds| {
|
||||
ds.with_lz4().without_shuffle();
|
||||
});
|
||||
assert_eq!(got, data);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user