|
1 |
| -# AICS Volume Viewer |
| 1 | +# Vol-E core |
2 | 2 |
|
3 |
| -This is a WebGL canvas-based volume viewer. It can display multichannel volume data of 8-bit intensity values. |
4 |
| -Volume data is provided to the core 3d viewer in two parts. The first part is via a json object containing dimensions and other metadata. The second part is the volume data itself. |
| 3 | +This is a WebGL canvas-based volume viewer. It can display multichannel volume data with high channel counts, and is optimized for OME-Zarr files. With OME-Zarr, the viewer can prefetch and cache Zarr chunks in browser memory for optimized performance. |
5 | 4 |
|
6 |
| -The volume-viewer package exposes two key modules: |
| 5 | +The Vol-E core package exposes several key modules: |
7 | 6 |
|
8 |
| -- `View3d` is the viewing component that contains a canvas and supports zoom/pan/rotate interaction with the volume. |
9 |
| -- `Volume` is the class that holds the volume data. After initialization, this is generally a read-only holder for raw data. |
| 7 | +- `View3d` is the viewing component that contains a canvas and supports zoom/pan/rotate interaction with the volume via `VolumeDrawable`. |
| 8 | +- `Volume` is the class that holds the volume dimensions and a collection of `Channel`s that contain the volume pixel data. After initialization, this is generally a read-only holder for raw data. |
| 9 | +- `VolumeLoaderContext` is an interface that lets you initialize asynchronous data loading of different formats via its `createLoader` method. |
| 10 | +- `IVolumeLoader` is an interface for requesting volume dimensions and data. |
| 11 | +- `LoadSpec` is a small bundle of information to guide the IVolumeLoader on exactly what to load. |
10 | 12 |
|
11 |
| -It also provides the following two utility modules: |
| 13 | +There are several ways to deliver volume data to the viewer: |
12 | 14 |
|
13 |
| -- `VolumeLoader` is a convenience class for downloading and unpacking texture atlases from .png files (up to 3 channels per png) into a Volume. |
14 |
| -- `VolumeMaker` is a convenience module for creating simple test volume data |
15 |
| - |
16 |
| -There are two ways to deliver volume data to the viewer: |
17 |
| - |
18 |
| -- raw Uint8Arrays of 3d volume data (one Uint8Array per channel). ( `Volume.setChannelDataFromVolume` ) |
19 |
| -- texture atlases (png files or Uint8Arrays containing volume slices tiled across a 2d image) ( `Volume.setChannelDataFromAtlas` ) |
| 15 | +- Load OME-Zarr from publicly accessible web links. Authentication is not explicitly supported in Vol-E. |
| 16 | +- Load raw TypedArrays of 3d volume data ( see `RawArrayLoader` and `Volume.setChannelDataFromVolume` ). |
| 17 | +- (legacy) Load texture atlases as .png files or Uint8Arrays containing volume slices tiled across a 2d image ( see `JsonImageInfoLoader` and `Volume.setChannelDataFromAtlas` ). |
20 | 18 |
|
21 | 19 | # Example
|
22 | 20 |
|
23 |
| -See public/index.ts for a working example. (`npm install; npm run dev` will run that code) The basic code to get the volume viewer up and running is as follows: |
| 21 | +See [`public/index.ts`](./public/index.ts) for a working example. (`npm install; npm run dev` will run that code) |
24 | 22 |
|
25 |
| -```javascript |
26 |
| -import { View3d, Volume, VolumeLoader, VolumeMaker } from "volume-viewer"; |
| 23 | +The basic code to get the viewer up and running is as follows: |
27 | 24 |
|
| 25 | +```javascript |
28 | 26 | // find a div that will hold the viewer
|
29 |
| -const el = document.getElementById("volume-viewer"); |
| 27 | +const el = document.getElementById("vol-e"); |
| 28 | + |
| 29 | +// create the loaderContext |
| 30 | +const loaderContext = new VolumeLoaderContext(CACHE_MAX_SIZE, CONCURRENCY_LIMIT, PREFETCH_CONCURRENCY_LIMIT); |
30 | 31 |
|
31 | 32 | // create the viewer. it will try to fill the parent element.
|
32 | 33 | const view3D = new View3d(el);
|
| 34 | +view3D.loaderContext = loaderContext; |
33 | 35 |
|
34 |
| -// create a volume image with dimensions passed in via jsondata |
35 |
| -// this json format is documented in Volume.ts as ImageInfo |
36 |
| -const aimg = new Volume(jsondata); |
| 36 | +// ensure the loader worker is ready |
| 37 | +await loaderContext.onOpen(); |
| 38 | +// get the actual loader. In most cases this will create a WorkerLoader that uses a OmeZarrLoader internally. |
| 39 | +const loader = await loaderContext.createLoader(path); |
37 | 40 |
|
| 41 | +const loadSpec = new LoadSpec(); |
| 42 | +// give the loader a callback to call when it receives channel data asynchronously |
| 43 | +const volume = await loader.createVolume(loadSpec, (v: Volume, channelIndex: number) => { |
| 44 | + const currentVol = v; |
| 45 | + |
| 46 | + // currently, this must be called when channel data arrives (here in this callback) |
| 47 | + view3D.onVolumeData(currentVol, [channelIndex]); |
| 48 | + |
| 49 | + view3D.setVolumeChannelEnabled(currentVol, channelIndex, true); |
| 50 | + |
| 51 | + // these calls tell the viewer that things are out of date |
| 52 | + view3D.updateActiveChannels(currentVol); |
| 53 | + view3D.updateLuts(currentVol); |
| 54 | + view3D.redraw(); |
| 55 | +}); |
38 | 56 | // tell the viewer about the image
|
39 |
| -view3D.addVolume(aimg); |
40 |
| - |
41 |
| -// load volume data into the image. volumeData here is an array of Uint8Arrays. |
42 |
| -// each element in volumeData is a flattened 3d volume stored in xyz order in a Uint8Array. |
43 |
| -// Intensities must have been be scaled to fit in uint8. |
44 |
| -for (let i = 0; i < volumeData.length; ++i) { |
45 |
| - aimg.setChannelDataFromVolume(i, volumeData[i], [0, 255]); |
46 |
| - // optional: initialize with a lookup table suitable for visualizing noisy biological data |
47 |
| - const hmin = aimg.getHistogram(i).findBinOfPercentile(0.5); |
48 |
| - const hmax = aimg.getHistogram(i).findBinOfPercentile(0.983); |
49 |
| - const lut = new Lut().createFromMinMax(hmin, hmax); |
50 |
| - aimg.setLut(i, lut); |
51 |
| -} |
52 |
| - |
53 |
| -// enable only the first 3 channels |
54 |
| -for (var ch = 0; ch < aimg.num_channels; ++ch) { |
55 |
| - view3D.setVolumeChannelEnabled(aimg, ch, ch < 3); |
56 |
| -} |
57 |
| - |
58 |
| -// set some viewing parameters |
59 |
| -view3D.updateDensity(aimg, 0.05); |
60 |
| -view3D.updateExposure(0.75); |
61 |
| -// tell the viewer to update because new data has been added. |
62 |
| -view3D.updateActiveChannels(aimg); |
63 |
| -view3D.updateLuts(aimg); |
| 57 | +view3D.addVolume(volume); |
| 58 | +// start requesting volume data |
| 59 | +loader.loadVolumeData(volume); |
64 | 60 | ```
|
65 | 61 |
|
66 | 62 | # React example
|
67 | 63 |
|
68 |
| -- in `VolumeViewer.jsx` |
69 |
| - |
70 |
| -```JavaScript |
71 |
| -import * as React from "react"; |
72 |
| - |
73 |
| -import { View3d, Volume, VolumeLoader, VolumeMaker } from 'volume-viewer'; |
74 |
| - |
75 |
| - |
76 |
| -const url = 'https://s3-us-west-2.amazonaws.com/bisque.allencell.org/v1.4.0/Cell-Viewer_Thumbnails/AICS-11/'; |
77 |
| -const volumeToLoad = 'AICS-11_3136_atlas.json'; |
78 |
| -export class VolumeViewer extends React.Component { |
79 |
| - constructor(props) { |
80 |
| - super(props); |
81 |
| - this.volumeViewer = React.createRef(); |
82 |
| - } |
83 |
| - |
84 |
| - componentDidMount() { |
85 |
| - const ref = this.volumeViewer; |
86 |
| - if (!ref.current) { |
87 |
| - return; |
88 |
| - } |
89 |
| - const el = ref.current; |
90 |
| - this.view3D = new View3d(el); |
91 |
| - // to download a volume encoded as a json plus tiled png images: |
92 |
| - // this format is documented in Volume.ts as ImageInfo |
93 |
| - return fetch(`${url}/${volumeToLoad}`) |
94 |
| - .then((response) => { |
95 |
| - return response.json(); |
96 |
| - }) |
97 |
| - .then(jsondata => { |
98 |
| - // when json file is received, create Volume object |
99 |
| - const aimg = new Volume(jsondata); |
100 |
| - // tell the 3d view about it. |
101 |
| - this.view3D.addVolume(aimg); |
102 |
| - |
103 |
| - jsondata.images = jsondata.images.map(img => ({ ...img, name: `${url$}${img.name}` })); |
104 |
| - // download the volume data itself in the form of tiled png files |
105 |
| - VolumeLoader.loadVolumeAtlasData(aimg, jsondata.images, (url, channelIndex) => { |
106 |
| - // initialize each channel as it arrives and tell the view to update. |
107 |
| - const hmin = aimg.getHistogram(channelIndex).findBinOfPercentile(0.5); |
108 |
| - const hmax = aimg.getHistogram(channelIndex).findBinOfPercentile(0.983); |
109 |
| - const lut = new Lut().createFromMinMax(hmin, hmax); |
110 |
| - aimg.setLut(i, lut); |
111 |
| - |
112 |
| - this.view3D.setVolumeChannelEnabled(aimg, channelIndex, channelIndex < 3); |
113 |
| - this.view3D.updateActiveChannels(aimg); |
114 |
| - |
115 |
| - this.view3D.updateLuts(aimg); |
116 |
| - }); |
117 |
| - // set some initial viewing parameters |
118 |
| - this.view3D.setCameraMode('3D'); |
119 |
| - this.view3D.updateDensity(aimg, 0.05); |
120 |
| - this.view3D.updateExposure(0.75); |
121 |
| - }); |
122 |
| - } |
123 |
| - |
124 |
| - render() { |
125 |
| - return ( |
126 |
| - <div |
127 |
| - style={{height: 1000, width: '100%'}} |
128 |
| - ref={this.volumeViewer} |
129 |
| - /> |
130 |
| - ) |
131 |
| - } |
132 |
| -``` |
| 64 | +See [vole-app](https://github.com/allen-cell-animated/website-3d-cell-viewer) for a complete application that wraps View3D in a React component. |
133 | 65 |
|
134 | 66 | # Acknowledgements
|
135 | 67 |
|
|
0 commit comments