feat(wasm): examples/wasm-viewer, an HDF5/NetCDF-4 viewer page
Drop a file (or pass ?file=<url>&path=<object>), browse the tree lazily, see a dataset's type, shape, max shape and attributes, and page through its values as 50x12 hyperslab windows (leading dims of 3-D+ data held at chosen indices). build.sh produces pkg/ (not committed) with wasm-bindgen --target web and checks the CLI matches the crate version. test/run.sh builds it and runs test.mjs under Node against the h5py/ netCDF4 fixture (250 checks: every dataset whole and as a strided hyperslab, listings, attributes, error paths, the page's DOM-free helpers), then browser.sh renders the page in headless Chromium for eight objects and checks the DOM. The fixture gains LZ4 (read) and Zstd (refused: links C) datasets and a compound attribute (value null plus its type). ci-test.sh runs it when node and wasm-bindgen exist; the CI container has neither, so CI relies on the native h5py_interop test. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
@@ -22,6 +22,11 @@ import h5py
|
||||
import netCDF4
|
||||
import numpy as np
|
||||
|
||||
try: # registers the LZ4/Zstd filters with libhdf5; optional
|
||||
import hdf5plugin
|
||||
except ImportError:
|
||||
hdf5plugin = None
|
||||
|
||||
# netCDF4 1.7 trips numpy 2.5's shape-setting deprecation on assignment.
|
||||
warnings.filterwarnings("ignore", category=DeprecationWarning)
|
||||
|
||||
@@ -37,6 +42,8 @@ with h5py.File(h5, "w") as f:
|
||||
f.attrs["scale"] = np.array([0.5, 2.0])
|
||||
f.attrs["big"] = np.uint64(2**63 + 5)
|
||||
f.attrs.create("vlen_note", "héllo", dtype=h5py.string_dtype())
|
||||
# No plain JavaScript form: listed with value null and its type.
|
||||
f.attrs["origin"] = np.array((1.5, 2), dtype=[("x", "<f8"), ("n", "<i4")])
|
||||
f.create_dataset(
|
||||
"grid", data=np.arange(60, dtype="<f8").reshape(6, 10) / 4,
|
||||
chunks=(4, 3), compression="gzip", shuffle=True,
|
||||
@@ -62,6 +69,12 @@ with h5py.File(h5, "w") as f:
|
||||
"cube", data=np.arange(2 * 5 * 6, dtype="<i4").reshape(2, 5, 6),
|
||||
chunks=(1, 2, 3), compression="gzip",
|
||||
)
|
||||
if hdf5plugin is not None:
|
||||
# LZ4 is built into clawhdf5-wasm; Zstd links C and is not.
|
||||
f.create_dataset("lz4", data=np.arange(40, dtype="<i4"), chunks=(10,),
|
||||
**hdf5plugin.LZ4())
|
||||
f.create_dataset("zstd", data=np.arange(40, dtype="<i4"), chunks=(10,),
|
||||
**hdf5plugin.Zstd())
|
||||
comp = np.zeros(2, dtype=[("x", "<f8"), ("n", "<i4")])
|
||||
f.create_dataset("table", data=comp)
|
||||
g = f.create_group("sensors")
|
||||
@@ -123,6 +136,8 @@ def entry(ds, slab=None):
|
||||
|
||||
def attr(v):
|
||||
v = np.asarray(v) if not isinstance(v, (str, bytes)) else v
|
||||
if isinstance(v, np.ndarray) and v.dtype.names:
|
||||
return {"raw": "compound"}
|
||||
if isinstance(v, bytes):
|
||||
return {"string": v.decode()}
|
||||
if isinstance(v, str):
|
||||
@@ -160,6 +175,8 @@ def describe(path):
|
||||
walk(key.rstrip("/") + "/" + n, o)
|
||||
elif obj.dtype.names:
|
||||
expected["errors"][key] = "compound"
|
||||
elif key == "/zstd":
|
||||
expected["errors"][key] = "unsupported filter: 32015"
|
||||
else:
|
||||
expected["datasets"][key] = entry(obj, slab_for(obj))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user