This module provides JavaScript functions to load data from files in H5J format. A H5J file is an HDF5 container with one or more channels of 3D volumetric data with 12-bit values compressed using H.265 (a.k.a. HEVC or High Efficiency Video Coding). H5J is a "visually lossless" format useful for fluorescence microscopy data.
The HDF5 container is read with the jsfive
module. The H.265 data is decoded with the ffmpeg.wasm
module, which uses WebAssembly (wasm). The ffmpeg.wasm
module was built using Emscripten, to transpile the original FFmpeg C++ code into WebAssembly.
First, install the NPM module:
npm install @janelia/web-h5j-loader
Then, the following code loads an H5J file from a URL and decompresses one channel of data into 8-bit values in a Uint8Array
:
import { openH5J, getH5JAttrs, readH5JChannelUint8 } from '@janelia/web-h5j-loader';
try {
const fileH5J = await openH5J('http://example.org/example.h5j');
const attrs = getH5JAttrs(fileH5J);
const dataUint8 = await readH5JChannelUint8(attrs.channels.names[0], fileH5J);
...
} catch (e) {
console.log(e.message);
}
Reading the original 12-bit values into an 8-bit array in this manner sacrifies some accuracy (see below), but is useful for some applications like high-performance direct volume rendering.
As an alternative, the original 12-bit values can be read into a 16-bit Uint16Array
as shown in this next example. This example also shows loading the H5J from a file on the local host, in the onChange
callback for an <input type="file" />
element:
import { openH5J, getH5JAttrs, readH5JChannelUint16 } from '@janelia/web-h5j-loader';
const onChange = async (event) => {
try {
const fileH5J = await openH5J(event.target.files[0]);
const attrs = getH5JAttrs(fileH5J);
const dataUint16 = await readH5JChannelUint16(attrs.channels.names[0], fileH5J);
...
} catch (e) {
console.log(e.message);
}
}
If run with a basic server, the example code above will produce an exception:
SharedArrayBuffer is not defined
The problem is that ffmpeg.wasm
uses multiple threads to improve performance, and these threads require a SharedArrayBuffer
to implement shared memory. Due to security risks, SharedArrayBuffer
is disabled in most browsers unless it is used with cross-origin isolation. To enable cross-origin isolation, a site must be served with two additional headers:
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
For a site created with create-react-app
, a way to add these headers to the development server is to use the CRACO (Create React App Configuration Override) package.
- Install CRACO:
(With newer versions of NPM, it may be necessary to append the
npm install @craco/craco --save
--legacy-peer-deps
argument to the end of the previous installation line.) - Add a
craco.config.js
file (as a sibling to the site'spackage.json
file) with the following contents:module.exports = { devServer: { headers: { 'Cross-Origin-Embedder-Policy': 'require-corp', 'Cross-Origin-Opener-Policy': 'same-origin' }, }, };
- Then change the
react-scripts
tocraco
in most entries of thescripts
section of the site'spackage.json
file:... "scripts": { "start": "craco start", "build": "craco build", "test": "craco test", "eject": "react-scripts eject" }, ...
The tests for web-h5j-loader
use the Jest framework, and can be run from the command line as follows:
npm run test
The final two tests in the web-h5j-loader.test.js
file load a special data set, testData/h64w64d4096_uint16_0-4095.h5j
, containing all possible 12-bit data values. These tests verify how this data is convereted to 8-bit values (Uint8Array
) and 16-bit values (Uint16Array
).
The special data set, testData/h64w64d4096_uint16_0-4095.h5j
, consists of slices (of width 64 and height 64) with a constant 12-bit data value. There are
When this data set is loaded into a Uint8Array
by readH5JChannelUint8()
, the expected behavior would be that 16 12-bit values would be mapped to the same 8-bit value. For slice
When testData/h64w64d4096_uint16_0-4095.h5j
is loaded into a Uint16Array
by readH5JChannelUint16()
, each 12-bit value is preseved, as expected. For slice
The testData
subdirectory contains various data sets for testing. One is the h64w64d4096_uint16_0-4095.h5j
data set mentioned above, for accuracy testing.
Another has a sphere at the origin, a fat cone pointing along the positive Uint8Array
, the data value for the sphere is 64, for the fat cone is 96, for the thin cone is 128, and for the cylinder is 160. There are several copies of the data set at different resolutions:
- Low resolution (64 x 64 x 64):
sphere64cone96cone128cylinder160_w64h64d64th3.h5j
- Medium resolution (256 x 256 x 256):
sphere64cone96cone128cylinder160_w256h256d256th3.h5j
- High resolution (512 x 512 x 512):
sphere64cone96cone128cylinder160_w512h512d512th3.h5j
- Non-cubical (256 x 128 x 64):
sphere64cone96cone128cylinder160_w256h128d64th3.h5j
The Python script makeTestStack.py
generates the TIFF stack that is the original data represented in these sphere64cone96cone128cylinder160...h5j
files. See the next section for more about creating H5J files.
The final data set is and real fluorescence microscopy volume from the FlyLight Generation 1 MCFO collection (citation: https://dx.doi.org/10.1016/j.celrep.2012.09.011):
R10E08-20191011_61_I8-m-40x-central-GAL4-JRC2018_Unisex_20x_HR-aligned_stack.h5j
It has resolution 1210 x 566 x 174.
The easiest way to make a H5J file is to convert a TIFF stack, a multi-frame (multi-page) TIFF image where each frame represents a step in depth. To convert the TIFF stack /tmp/example.tif
, use Docker as follows:
docker run -v /tmp:/data janeliascicomp/flylight_tools:1.1.0 /app/scripts/cmd/convert.sh /data/example.tif /data/example.h5j /data/example.yml 0 1
docker run -v /tmp:/data janeliascicomp/flylight_tools:1.1.0 /app/scripts/cmd/h5jMetadata.sh /data/example.h5j /data/example.yml
The /tmp/example.yml
file should have a format like the following (perhaps with more than just the one Channel_0
):
attrs:
image_size: [256.0, 128.0, 64.0]
voxel_size: [1.0, 1.0, 1.0]
channel_spec: r
groups:
Channels:
attrs:
frames: [64]
height: [128]
pad_bottom: [0]
pad_right: [0]
width: [256]
groups:
Channel_0:
attrs: {content_type: reference}