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:
@@ -0,0 +1,97 @@
|
||||
# HDF5 viewer in the browser
|
||||
|
||||
A single page that opens an HDF5 or NetCDF-4 file entirely in the browser
|
||||
with `clawhdf5-wasm` (clawhdf5's reader compiled to WebAssembly): drop a
|
||||
file, browse its groups, and look at a dataset's type, shape, attributes
|
||||
and values (a 50 x 12 window at a time, read as a hyperslab, with the
|
||||
leading dimensions of a 3-D+ dataset held at chosen indices). The file never
|
||||
leaves the page.
|
||||
|
||||
## Build and open
|
||||
|
||||
```bash
|
||||
rustup target add wasm32-unknown-unknown
|
||||
cargo install wasm-bindgen-cli --version 0.2.129 # must equal the crate version; build.sh checks
|
||||
bash examples/wasm-viewer/build.sh # writes examples/wasm-viewer/pkg/ (not committed)
|
||||
python3 -m http.server -d examples/wasm-viewer 8000 # wasm cannot load from file://
|
||||
```
|
||||
|
||||
Then open <http://localhost:8000/>. `?file=<url>&path=<object>` opens a
|
||||
file from a URL (same origin, or one serving CORS headers) and selects an
|
||||
object in it, e.g. `?file=data/run1.h5&path=/results/energy`.
|
||||
|
||||
## JavaScript API
|
||||
|
||||
```js
|
||||
import init, { open } from "./pkg/clawhdf5_wasm.js";
|
||||
await init();
|
||||
const f = open(new Uint8Array(await blob.arrayBuffer()));
|
||||
f.list("/"); // [{ name, kind: "group" | "dataset" }], groups first
|
||||
f.info("/grid"); // { shape, maxshape, dtype, elementShape }
|
||||
f.attrs("/grid"); // [{ name, value, dtype }]
|
||||
f.read("/grid"); // { shape, dtype, data }
|
||||
f.readHyperslab("/grid", [0, 0], [10, 5], [2, 1]); // start, count, stride?, block?
|
||||
f.free();
|
||||
```
|
||||
|
||||
`data` is the typed array of the stored width (`Float64Array`,
|
||||
`Float32Array` also for `f16`, `Int8Array` ... `BigInt64Array`,
|
||||
`BigUint64Array`), or an array of strings for fixed- and variable-length
|
||||
strings and enumerations (h5py booleans read as `"TRUE"`/`"FALSE"`). Array
|
||||
datatypes are flattened, their dimensions appended to `shape`. Anything
|
||||
else throws an `Error` naming the type.
|
||||
|
||||
## Limits
|
||||
|
||||
- Read-only, and the whole file is held in memory (no range requests).
|
||||
- Compound, reference, opaque and variable-length-sequence datasets are
|
||||
refused with an error. Attributes of those types are listed with
|
||||
`value: null` and their `dtype`.
|
||||
- No Zstd or SZIP filters (they link C): such a dataset fails with
|
||||
`unsupported filter`. Deflate, shuffle, Fletcher-32, LZ4, N-Bit and
|
||||
scale-offset are read (within the limits in `docs/known-issues.md`).
|
||||
- Virtual datasets whose sources are in other files, and external links,
|
||||
cannot be followed: there is no file system.
|
||||
|
||||
## Tests
|
||||
|
||||
`test/run.sh` builds the package, writes `fixture.h5` (h5py) and
|
||||
`fixture.nc` (netCDF4) with `test/make_fixture.py`, then:
|
||||
|
||||
- runs `test/test.mjs` under Node: every dataset (whole and a strided
|
||||
hyperslab), listing and attribute is compared with what libhdf5 reads
|
||||
back, error paths are checked, and so are the page's DOM-free helpers
|
||||
(`viewer-lib.js`);
|
||||
- runs `test/browser.sh`: loads the page in headless Chromium with
|
||||
`?file=fixture.h5&path=...` for eight objects and checks the rendered tree,
|
||||
types, shapes, attribute and value cells, and the error shown for an
|
||||
unsupported type. Skipped when no Chromium is found (`CHROME` names one;
|
||||
a Playwright download under `~/.cache/ms-playwright` is picked up).
|
||||
Drag-and-drop and the file picker are not driven by it; they share
|
||||
`load()` with the `?file=` path.
|
||||
|
||||
The same expectations are checked natively, without Node, by
|
||||
`crates/clawhdf5-wasm/tests/h5py_interop.rs`, which is what CI runs (the CI
|
||||
container has no Node or browser).
|
||||
|
||||
## Size
|
||||
|
||||
Measured 2026-09-26 on tank (rustc 1.98.1, wasm-bindgen 0.2.129, gzip 1.14,
|
||||
`gzip -9 -n`), after `bash examples/wasm-viewer/build.sh`:
|
||||
|
||||
| | raw | gzip -9 |
|
||||
|---|---:|---:|
|
||||
| `pkg/clawhdf5_wasm_bg.wasm` (profile `wasm-release`, opt-level `s`) | 627,501 B | 191,639 B |
|
||||
| `pkg/clawhdf5_wasm.js` (wasm-bindgen glue) | 21,826 B | 4,487 B |
|
||||
| same wasm at opt-level `z` | 693,068 B | 192,550 B |
|
||||
| same wasm at opt-level `3` | 544,035 B | 198,803 B |
|
||||
| h5wasm 0.10.3: wasm embedded in `dist/esm/hdf5_util.js` | 3,544,184 B | 907,096 B |
|
||||
| h5wasm 0.10.3: `dist/esm/hdf5_util.js` as shipped | 4,150,134 B | 986,699 B |
|
||||
|
||||
h5wasm figures: `npm pack [email protected]` (npm reports
|
||||
`dist.unpackedSize` 14,731,385 B for the whole package), wasm extracted from
|
||||
the `binaryDecode` literal in `hdf5_util.js`. h5wasm is the whole of libhdf5
|
||||
(writing, every datatype, plugins), so this compares download size, not
|
||||
equal functionality. No `wasm-opt` pass was applied (binaryen is not
|
||||
installed on tank). opt-level `s` is used because it is the smallest
|
||||
compressed.
|
||||
Reference in New Issue
Block a user