WebAssembly
OpenReadout’s readers also compile to WebAssembly, so a web page or a Node program can read instrument files without a server. The demo page is built this way.
The npm package
The package openreadout-wasm (in packaging/wasm; not yet on npm) wraps the module in promises:
import { open } from "openreadout-wasm";
const f = await open(file); // a File or Blob, bytes, or { size, read(offset, length) }const info = await f.info(); // the `data` of `info --json`const report = await f.check(); // the `data` of `check --json`const { meta, png } = await f.preview(); // a preview image and what was drawnf.close();open takes a File or Blob, a byte array, or a range reader: an object with a size and a read(offset, length) method that the page implements, for example with HTTP range requests. Blobs up to 256 MiB are read whole. For larger ones the readers fetch only the blocks they need, so info on a multi-gigabyte file reads only a few megabytes.
Data sets that span several files or a directory (Bruker .d, ChemStation .D, Waters .raw, NMR experiment directories, multi-file OME-TIFF) need their sibling files. Open them with openAmong(files, path), where files is a dropped folder:
import { openAmong } from "openreadout-wasm";const f = await openAmong(droppedFiles, "run.D");Each method is named after a command-line command and returns the same JSON:
| method | command |
|---|---|
formats(), version() |
self formats, --version |
info() |
info |
info({ view: "full", vendor, provenance }) |
info --view full |
info({ view: "structure" }) |
info --view structure |
info({ view: "explain", ask }) |
info --view explain, --ask |
info({ view: "format" }) |
info --view format |
check({ headersOnly }) |
check, --headers-only |
preview(options) |
preview |
table(i, { firstRow, maxRows }) |
table |
trace(i, { sweep, firstSample, count, maxSamples }) |
trace |
spectra({ ms_level, rt_range, limit, ... }) |
spectra (scan headers) |
spectra({ index, scan, centroid, max_points }) |
spectra --index, --scan |
Errors are thrown as InstrumentError, with code, exitCode and hint fields that match the command line’s JSON errors.
What the WebAssembly build leaves out
- the OME-Zarr reader
- writing files:
exportand the other writers watch,indexand the MCP server- multi-threaded decoding (it all runs on the calling thread)
- the check for files still being written, because there is no modification time
A bug that makes Rust panic stops the WebAssembly instance. The readers are fuzzed so that malformed files give errors instead, but a page that must survive a panic should load the module again.
Privacy of the demo page
The demo page in web/ can’t upload your file. Its Content-Security-Policy is:
default-src 'none'; script-src 'self' 'wasm-unsafe-eval'; style-src 'self'; img-src 'self' blob:; connect-src 'none'; form-action 'none'; base-uri 'none'so the browser blocks any request the page makes after loading its own files. The policy also blocks fetching the WebAssembly module, so the build inlines the module into the page.
Reading from other sources in Rust
The readers do not read from a path directly. They read from a byte source: a local file, a buffer in memory, or a function the host provides that reads byte ranges. That way the same readers can run in a browser, open Python bytes and file objects, and read from S3 or an HTTP server through code the host writes. OpenReadout itself doesn’t open network connections.
use std::sync::Arc;use openreadout_core::source::{CachedSource, CallbackSource, Input};
// A range reader written by the host (HTTP range requests, S3 GetObject with Range, ...).let remote = CallbackSource::new("plate1.czi", size, move |offset, buf| my_http_range(offset, buf));let input = Input::from_source("plate1.czi", Arc::new(CachedSource::with_defaults(Arc::new(remote))));let (detection, dataset) = registry.open_input(&input)?;let info = dataset.info()?;A buffer is Input::from_bytes(name, bytes), and a set of files held in memory is a MemFs. docs/architecture.md describes the design.
Building the module
rustup target add wasm32-unknown-unknowncargo install wasm-bindgen-cli --locked --version <the wasm-bindgen version in Cargo.lock>scripts/wasm.sh build # writes packaging/wasm/pkg/scripts/wasm.sh test # tests in Nodescripts/wasm.sh demo # the demo page, after `npm run build` in book/scripts/wasm.sh build runs wasm-opt to shrink the module when binaryen is installed (cd packaging/wasm && npm install provides it).
On macOS, some Rust toolchains ship a rust-lld that fails with Library not loaded: @rpath/libLLVM.dylib. A small linker wrapper fixes this without changing the toolchain:
wasm_sysroot="$(rustc --print sysroot)"wasm_host="$(rustc -vV | sed -n 's/^host: //p')"cat > /tmp/openreadout-wasm-linker <<EOF#!/bin/shexport DYLD_LIBRARY_PATH="$wasm_sysroot/lib"exec "$wasm_sysroot/lib/rustlib/$wasm_host/bin/rust-lld" "\$@"EOFchmod +x /tmp/openreadout-wasm-linkerCARGO_TARGET_WASM32_UNKNOWN_UNKNOWN_LINKER=/tmp/openreadout-wasm-linker scripts/wasm.sh build