Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/test-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
uses: mymindstorm/setup-emsdk@v14
with:
# Make sure to set a version number!
version: 3.1.68
version: 4.0.23
# This is the name of the cache folder.
# The cache folder will be placed in the build directory,
# so make sure it doesn't conflict with anything!
Expand Down
15 changes: 8 additions & 7 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ project(H5WASM
LANGUAGES CXX C
)

set (BASE_URL "https://github.com/usnistgov/libhdf5-wasm/releases/download/v0.5.0_3.1.68" CACHE STRING "")
set (BASE_URL "https://github.com/usnistgov/libhdf5-wasm/releases/download/v0.6.0_4.0.23" CACHE STRING "")
# set (BASE_URL "$ENV{HOME}/dev/libhdf5-wasm" CACHE STRING "")

FetchContent_Declare(
libhdf5-wasm
URL ${BASE_URL}/HDF5-1.14.6-Emscripten.tar.gz
URL_HASH SHA256=90c26256f3d1fd0b421b7e0defebe1894f179b2a50252aeb059aaa1fa6bdb7e9
URL ${BASE_URL}/HDF5-2.0.0-Emscripten.tar.gz
URL_HASH SHA256=d4dc6719ef164679728d9a50d6a4d97c826f563e22a40c4dec63e485b29781be
)
if (NOT libhdf5-wasm_POPULATED)
FetchContent_MakeAvailable(libhdf5-wasm)
Expand All @@ -22,7 +22,7 @@ set(HDF5_DIR ${libhdf5-wasm_SOURCE_DIR}/cmake)
find_package(HDF5 REQUIRED CONFIG)

add_executable(hdf5_util src/hdf5_util.cc)
target_link_libraries(hdf5_util hdf5_hl-static)
target_link_libraries(hdf5_util hdf5::hdf5_hl-static)

set (EXPORTED_FUNCTIONS)
list (APPEND EXPORTED_FUNCTIONS
Expand All @@ -36,6 +36,7 @@ list (APPEND EXPORTED_FUNCTIONS
pthread_create pthread_mutex_lock pthread_mutex_unlock pthread_atfork pthread_once
pthread_cond_init pthread_barrier_init pthread_attr_init pthread_attr_setdetachstate
H5Pget_chunk H5Tget_class H5Sget_simple_extent_dims
H5_libinit_g H5_libterm_g
H5Pget_filter_by_id2 H5Pmodify_filter H5Pexist
H5Tget_size H5Tget_native_type H5Tget_order
H5Epush2 siprintf getTempRet0 __wasm_setjmp
Expand Down Expand Up @@ -67,15 +68,15 @@ set_target_properties(hdf5_util PROPERTIES
-s SINGLE_FILE \
-s EXPORT_ES6=1 \
-s FORCE_FILESYSTEM=1 \
-s EXPORTED_RUNTIME_METHODS=\"['ccall', 'cwrap', 'FS', 'AsciiToString', 'UTF8ToString']\" \
-s EXPORTED_RUNTIME_METHODS=\"['ccall', 'cwrap', 'FS', 'AsciiToString', 'UTF8ToString', 'HEAPU8']\" \
-s EXPORTED_FUNCTIONS=\"${EXPORTED_FUNCTIONS_STRING}\""
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/dist/esm
RUNTIME_OUTPUT_NAME hdf5_util
POSITION_INDEPENDENT_CODE ON
)

add_executable(hdf5_util_node src/hdf5_util.cc)
target_link_libraries(hdf5_util_node hdf5_hl-static)
target_link_libraries(hdf5_util_node hdf5::hdf5_hl-static)
set_target_properties(hdf5_util_node PROPERTIES
LINK_FLAGS "-O2 --bind \
-s MAIN_MODULE=2 \
Expand All @@ -92,7 +93,7 @@ set_target_properties(hdf5_util_node PROPERTIES
-s SINGLE_FILE \
-s EXPORT_ES6=1 \
-s ASSERTIONS=1 \
-s EXPORTED_RUNTIME_METHODS=\"['ccall', 'cwrap', 'FS', 'AsciiToString', 'UTF8ToString']\" \
-s EXPORTED_RUNTIME_METHODS=\"['ccall', 'cwrap', 'FS', 'AsciiToString', 'UTF8ToString', 'HEAPU8']\" \
-s EXPORTED_FUNCTIONS=\"${EXPORTED_FUNCTIONS_STRING}\""
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/dist/node
RUNTIME_OUTPUT_NAME hdf5_util
Expand Down
170 changes: 137 additions & 33 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ a zero-dependency WebAssembly-powered library for [reading](#reading) and [writi
(built on the [HDF5 C API](http://portal.hdfgroup.org/pages/viewpage.action?pageId=50073943))

The built binaries (esm and node) will be attached to the [latest release](https://github.com/usnistgov/h5wasm/releases/latest/) as h5wasm-{version}.tgz

The wasm-compiled libraries `libhdf5.a`, `libhdf5_cpp.a` ... and the related `include/` folder are retrieved from [libhdf5-wasm](https://github.com/usnistgov/libhdf5-wasm) during the build.

Instead of importing a namespace "*", it is now possible to import the important h5wasm components in an object, from the default export:
Expand All @@ -14,24 +14,46 @@ export const h5wasm = {
File,
Group,
Dataset,
Datatype,
DatasetRegion,
ready,
ACCESS_MODES
}
```

The Emscripten filesystem is important for operations, and it can be accessed after the WASM is loaded as below.

## Contents

- [QuickStart](#quickstart)
- [Browser (no-build)](#browser-no-build)
- [Worker usage](#worker-usage)
- [Browser target (build system)](#browser-target-build-system)
- [Node.js](#nodejs)
- [User Guide](#user-guide)
- [Opening a file](#opening-a-file)
- [Reading](#reading)
- [Writing](#writing)
- [Editing](#editing)
- [SWMR (single writer multiple readers)](#swmr-single-writer-multiple-readers)
- [Links](#links)
- [Library version bounds (libver)](#library-version-bounds-libver)
- [Web Helpers](#web-helpers)
- [Persistent file store (web)](#persistent-file-store-web)

# QuickStart

## Browser (no-build)
```js
import h5wasm from "https://cdn.jsdelivr.net/npm/h5wasm@0latest/dist/esm/hdf5_hl.js";
import h5wasm from "https://cdn.jsdelivr.net/npm/h5wasm@latest/dist/esm/hdf5_hl.js";

// the WASM loads asychronously, and you can get the module like this:
const Module = await h5wasm.ready;

// then you can get the FileSystem object from the Module:
const { FS } = Module;

// Or, you can directly get the FS if you don't care about the rest
// Or, you can directly get the FS if you don't care about the rest
// of the module:
// const { FS } = await h5wasm.ready;

Expand All @@ -45,13 +67,13 @@ let f = new h5wasm.File("sans59510.nxs.ngv", "r");
// File {path: "/", file_id: 72057594037927936n, filename: "data.h5", mode: "r"}
```

### Worker usage
## Worker usage
Since ESM is not supported in all web worker contexts (e.g. Firefox), an additional ```./dist/iife/h5wasm.js``` is provided in the package for `h5wasm>=0.4.8`; it can be loaded in a worker and used as in the example below (which uses the WORKERFS file system for random access on local files):
```js
// worker.js
onmessage = async function(e) {
const { FS } = await h5wasm.ready;

// send in a file opened from an <input type="file" />
const f_in = e.data[0];

Expand Down Expand Up @@ -85,7 +107,7 @@ The host filesystem is made available through Emscripten "NODERAWFS=1".
Enabling BigInt support may be required for nodejs < 16
```bash
npm i h5wasm
node --experimental-wasm-bigint
node

```

Expand All @@ -100,12 +122,37 @@ File {
file_id: 72057594037927936n,
filename: '/home/brian/Downloads/sans59510.nxs.ngv',
mode: 'r'
}
}
*/
```

## Usage
# User Guide
_(all examples are written in ESM - for Typescript some type casting is probably required, as `get` returns either Group or Dataset)_

## Opening a file

```js
new h5wasm.File(filename, mode?, options?)
```

| Argument | Type | Default | Description |
|---|---|---|---|
| `filename` | `string` | — | Path to the HDF5 file |
| `mode` | `string` | `"r"` | Access mode (see table below) |
| `options.track_order` | `boolean` | `false` | Preserve insertion order of groups and attributes |
| `options.libver` | `string \| [string, string]` | — | Library version bound(s) for new objects (see [libver](#library-version-bounds-libver)) |

Available modes:

| Mode | Description |
|---|---|
| `"r"` | Read-only |
| `"a"` | Read/write (file must exist) |
| `"w"` | Create / truncate |
| `"x"` | Create, fail if exists |
| `"Sw"` | SWMR write |
| `"Sr"` | SWMR read |

### Reading
```js
let f = new h5wasm.File("sans59510.nxs.ngv", "r");
Expand All @@ -121,7 +168,7 @@ let data = f.get("entry/instrument/detector_MR/data")
// Dataset {path: "/entry/instrument/detector_MR/data", file_id: 72057594037927936n}

data.metadata
/*
/*
{
"signed": true,
"vlen": false,
Expand Down Expand Up @@ -187,19 +234,6 @@ data.to_array()
*/
```

### SWMR Read
(single writer multiple readers)
```js
const swmr_file = new h5wasm.File("swmr.h5", "Sr");
let dset = swmr_file.get("data");
dset.shape;
// 12
// ...later
dset.refresh();
dset.shape;
// 16
```

### Writing
```js
let new_file = new h5wasm.File("myfile.h5", "w");
Expand All @@ -223,7 +257,7 @@ new_file.get("entry/data").value
//Float32Array(4) [3.0999999046325684, 4.099999904632568, 0, -1]

// create a dataset with shape=[2,2]
// The dataset stored in the HDF5 file with the correct shape,
// The dataset stored in the HDF5 file with the correct shape,
// but no attempt is made to make a 2x2 array out of it in javascript
new_file.get("entry").create_dataset({name: "square_data", data: [3.1, 4.1, 0.0, -1.0], shape: [2,2], dtype: '<d'});
new_file.get("entry/square_data").shape
Expand Down Expand Up @@ -270,6 +304,56 @@ new_file.close()

```

### Editing
One can also open an existing file and write to it:
```js
let f = new h5wasm.File("myfile.h5", "a");

f.create_attribute("new_attr", "something wicked this way comes");
f.close()
```

### SWMR (single writer multiple readers)

SWMR requires a file created with at least `libver: "v110"` and a chunked, extensible dataset.

```js
// First, create a SWMR-compatible file
const f = new h5wasm.File("swmr.h5", "w", { libver: "v110" });
f.create_dataset({
name: "data",
data: new Float32Array([1, 2, 3]),
maxshape: [null], // unlimited along first axis
chunks: [10]
});
f.flush();
f.close();

// Open for SWMR write and SWMR read simultaneously
const f_write = new h5wasm.File("swmr.h5", "Sw");
const f_read = new h5wasm.File("swmr.h5", "Sr");

const dset_write = f_write.get("data");
const dset_read = f_read.get("data");

// Extend the dataset and write new values
dset_write.resize([6]);
dset_write.write_slice([[3, 6]], new Float32Array([4, 5, 6]));
f_write.flush();

// The read handle still sees the old shape until refreshed
dset_read.shape;
// [3]
dset_read.refresh();
dset_read.shape;
// [6]
dset_read.value;
// Float32Array(6) [1, 2, 3, 4, 5, 6]

f_write.close();
f_read.close();
```

### Links
```js
let new_file = new h5wasm.File("myfile.h5", "w");
Expand All @@ -279,12 +363,12 @@ new_file.get("entry").create_dataset({name: "auto", data: [3.1, 4.1, 0.0, -1.0]}
// create a soft link in root:
new_file.create_soft_link("/entry/auto", "my_soft_link");
new_file.get("my_soft_link").value;
// Float64Array(4) [3.1, 4.1, 0, -1]
// Float64Array(4) [3.1, 4.1, 0, -1]

// create a hard link:
new_file.create_hard_link("/entry/auto", "my_hard_link");
new_file.get("my_hard_link").value;
// Float64Array(4) [3.1, 4.1, 0, -1]
// Float64Array(4) [3.1, 4.1, 0, -1]

// create an external link:
new_file.create_external_link("other_file.h5", "other_dataset", "my_external_link");
Expand All @@ -296,7 +380,7 @@ new_file.create_group("links");
const links_group = new_file.get("links");
links_group.create_soft_link("/entry/auto", "soft_link");
new_file.get("/links/soft_link").value;
// Float64Array(4) [3.1, 4.1, 0, -1]
// Float64Array(4) [3.1, 4.1, 0, -1]
new_file.get_link("/links/soft_link");
// "/entry/auto"
new_file.get_link("/entry/auto");
Expand All @@ -305,24 +389,44 @@ new_file.get_link("/entry/auto");
new_file.close()
```

### Edit
One can also open an existing file and write to it:
## Library version bounds (libver)

HDF5 supports controlling the minimum and maximum library version used when writing objects to a file, via `libver`. This can be set using the `FileOptions` object when opening a file.

Valid `libver` string values are: `"earliest"`, `"v108"`, `"v110"`, `"v112"`, `"v114"`, `"v200"`, and `"latest"`.

### Single bound (low and high set to the same value)
```js
let f = new h5wasm.File("myfile.h5", "a");
// Require at least HDF5 v1.10 format features:
const f = new h5wasm.File("myfile.h5", "w", { libver: "v110" });
f.create_dataset({ name: "data", data: new Float32Array([1, 2, 3]) });
f.close();

// Read back the actual bounds stored in the file:
const f_read = new h5wasm.File("myfile.h5", "r");
f_read.libver;
// ["v110", "v110"]
f_read.close();
```

f.create_attribute("new_attr", "something wicked this way comes");
f.close()
### Asymmetric bounds (different low and high)
```js
// Allow any format from v1.10 up to the latest supported version:
const f = new h5wasm.File("myfile.h5", "w", { libver: ["v110", "latest"] });
f.create_dataset({ name: "data", data: new Float32Array([1, 2, 3]) });
f.close();
```

## Web Helpers
Optional, to support uploads and downloads

```js
import {uploader, download, UPLOADED_FILES} from "https://cdn.jsdelivr.net/npm/h5wasm@latest/dist/esm/file_handlers.js";
//
//
// Attach to a file input element:
// will save to Module.FS (memfs) with the name of the uploaded file
document.getElementById("upload_selector").onchange = uploader;
// file can be found with
// file can be found with
let f = new h5wasm.File(UPLOADED_FILES[UPLOADED_FILES.length -1], "r");

let new_file = new h5wasm.File("myfile.h5", "w");
Expand Down
Loading
Loading