h5rs tools, browser reader, libhdf5 header checks, plugin filters, concurrency benchmark #14
@@ -406,9 +406,17 @@ impl fmt::Display for FormatError {
|
|||||||
FormatError::InvalidFilterPipelineVersion(v) => {
|
FormatError::InvalidFilterPipelineVersion(v) => {
|
||||||
write!(f, "invalid filter pipeline version: {v}")
|
write!(f, "invalid filter pipeline version: {v}")
|
||||||
}
|
}
|
||||||
FormatError::UnsupportedFilter(id) => {
|
FormatError::UnsupportedFilter(id) => match crate::filter_registry::known_filter(*id) {
|
||||||
write!(f, "unsupported filter: {id}")
|
Some((name, Some(feature))) => write!(
|
||||||
}
|
f,
|
||||||
|
"unsupported filter: {id} ({name}; this build lacks the `{feature}` feature)"
|
||||||
|
),
|
||||||
|
Some((name, None)) => write!(
|
||||||
|
f,
|
||||||
|
"unsupported filter: {id} ({name}, not implemented by clawhdf5)"
|
||||||
|
),
|
||||||
|
None => write!(f, "unsupported filter: {id}"),
|
||||||
|
},
|
||||||
FormatError::FilterError(msg) => {
|
FormatError::FilterError(msg) => {
|
||||||
write!(f, "filter error: {msg}")
|
write!(f, "filter error: {msg}")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -130,6 +130,30 @@ pub fn builtin_filter(id: u16) -> Option<&'static BuiltinFilter> {
|
|||||||
builtin_filters().iter().find(|f| f.id == id)
|
builtin_filters().iter().find(|f| f.id == id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Why a filter ID may be missing from this build: the filter's name, and
|
||||||
|
/// the cargo feature that provides it (`None`: clawhdf5 does not implement
|
||||||
|
/// it — register a codec for it with [`register_filter`]). `None` for an ID
|
||||||
|
/// clawhdf5 knows nothing about.
|
||||||
|
pub fn known_filter(id: u16) -> Option<(&'static str, Option<&'static str>)> {
|
||||||
|
Some(match id {
|
||||||
|
1 => ("deflate", Some("deflate")),
|
||||||
|
4 => ("SZIP", Some("szip")),
|
||||||
|
307 => ("bzip2", Some("bzip2")),
|
||||||
|
480 => ("pcodec", Some("pcodec")),
|
||||||
|
32000 => ("LZF", Some("lzf")),
|
||||||
|
32001 => ("Blosc", Some("blosc")),
|
||||||
|
32004 => ("LZ4", Some("lz4")),
|
||||||
|
32008 => ("bitshuffle", Some("bitshuffle")),
|
||||||
|
32013 => ("ZFP", None),
|
||||||
|
32015 => ("Zstandard", Some("zstd")),
|
||||||
|
32019 => ("JPEG", None),
|
||||||
|
32022 => ("BitGroom", None),
|
||||||
|
32023 => ("Granular BitRound", None),
|
||||||
|
32026 => ("Blosc2", None),
|
||||||
|
_ => return None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Whether a chunk filtered with `id` can be decoded: a built-in filter or a
|
/// Whether a chunk filtered with `id` can be decoded: a built-in filter or a
|
||||||
/// registered one.
|
/// registered one.
|
||||||
pub fn is_filter_available(id: u16) -> bool {
|
pub fn is_filter_available(id: u16) -> bool {
|
||||||
@@ -344,6 +368,23 @@ mod tests {
|
|||||||
assert!(builtin_filter(FILTER_SHUFFLE).is_some());
|
assert!(builtin_filter(FILTER_SHUFFLE).is_some());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unsupported_filter_error_names_the_filter() {
|
||||||
|
let msg = FormatError::UnsupportedFilter(32026).to_string();
|
||||||
|
assert!(
|
||||||
|
msg.contains("Blosc2") && msg.contains("not implemented"),
|
||||||
|
"{msg}"
|
||||||
|
);
|
||||||
|
let msg = FormatError::UnsupportedFilter(32013).to_string();
|
||||||
|
assert!(msg.contains("ZFP"), "{msg}");
|
||||||
|
let msg = FormatError::UnsupportedFilter(32000).to_string();
|
||||||
|
assert!(msg.contains("LZF") && msg.contains("`lzf`"), "{msg}");
|
||||||
|
assert_eq!(
|
||||||
|
FormatError::UnsupportedFilter(399).to_string(),
|
||||||
|
"unsupported filter: 399"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn builtin_table_is_sorted_and_unique() {
|
fn builtin_table_is_sorted_and_unique() {
|
||||||
let ids: Vec<u16> = builtin_filters().iter().map(|f| f.id).collect();
|
let ids: Vec<u16> = builtin_filters().iter().map(|f| f.id).collect();
|
||||||
|
|||||||
@@ -380,3 +380,38 @@ fn lzf_written_by_clawhdf5_reads_in_h5py() {
|
|||||||
ds.with_lzf().without_shuffle();
|
ds.with_lzf().without_shuffle();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Blosc2 and ZFP are not implemented: reading them must be a clear error
|
||||||
|
/// naming the filter, never data.
|
||||||
|
#[test]
|
||||||
|
fn unimplemented_filters_are_a_clear_error() {
|
||||||
|
if !have_python("h5py, hdf5plugin") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let dir = tempfile::tempdir().unwrap();
|
||||||
|
let path = dir.path().join("unimplemented.h5");
|
||||||
|
run_python(
|
||||||
|
r#"
|
||||||
|
import sys
|
||||||
|
import numpy as np, h5py, hdf5plugin
|
||||||
|
with h5py.File(sys.argv[1], 'w') as f:
|
||||||
|
d = np.arange(4096, dtype='<f4').reshape(64, 64)
|
||||||
|
f.create_dataset('blosc2', data=d, chunks=(16, 16), **hdf5plugin.Blosc2())
|
||||||
|
f.create_dataset('zfp', data=d, chunks=(16, 16), **hdf5plugin.Zfp(reversible=True))
|
||||||
|
"#,
|
||||||
|
&[path.to_str().unwrap()],
|
||||||
|
);
|
||||||
|
let file = File::open(&path).unwrap();
|
||||||
|
for (name, id, label) in [("blosc2", 32026u16, "Blosc2"), ("zfp", 32013, "ZFP")] {
|
||||||
|
let err = file
|
||||||
|
.dataset(name)
|
||||||
|
.unwrap()
|
||||||
|
.read_selection(&Selection::All)
|
||||||
|
.expect_err("an unimplemented filter must not read");
|
||||||
|
let msg = err.to_string();
|
||||||
|
assert!(
|
||||||
|
msg.contains(&id.to_string()) && msg.contains(label),
|
||||||
|
"{name}: {msg}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user