@aics/vole-core 3.12.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.txt +26 -0
- package/README.md +119 -0
- package/es/Atlas2DSlice.js +224 -0
- package/es/Channel.js +264 -0
- package/es/FileSaver.js +31 -0
- package/es/FusedChannelData.js +192 -0
- package/es/Histogram.js +250 -0
- package/es/ImageInfo.js +127 -0
- package/es/Light.js +74 -0
- package/es/Lut.js +500 -0
- package/es/MarchingCubes.js +507 -0
- package/es/MeshVolume.js +334 -0
- package/es/NaiveSurfaceNets.js +251 -0
- package/es/PathTracedVolume.js +482 -0
- package/es/RayMarchedAtlasVolume.js +250 -0
- package/es/RenderToBuffer.js +31 -0
- package/es/ThreeJsPanel.js +633 -0
- package/es/Timing.js +28 -0
- package/es/TrackballControls.js +538 -0
- package/es/View3d.js +848 -0
- package/es/Volume.js +352 -0
- package/es/VolumeCache.js +161 -0
- package/es/VolumeDims.js +16 -0
- package/es/VolumeDrawable.js +702 -0
- package/es/VolumeMaker.js +101 -0
- package/es/VolumeRenderImpl.js +1 -0
- package/es/VolumeRenderSettings.js +203 -0
- package/es/constants/basicShaders.js +29 -0
- package/es/constants/colors.js +59 -0
- package/es/constants/denoiseShader.js +43 -0
- package/es/constants/lights.js +42 -0
- package/es/constants/materials.js +85 -0
- package/es/constants/pathtraceOutputShader.js +13 -0
- package/es/constants/scaleBarSVG.js +21 -0
- package/es/constants/time.js +34 -0
- package/es/constants/volumePTshader.js +153 -0
- package/es/constants/volumeRayMarchShader.js +123 -0
- package/es/constants/volumeSliceShader.js +115 -0
- package/es/index.js +21 -0
- package/es/loaders/IVolumeLoader.js +131 -0
- package/es/loaders/JsonImageInfoLoader.js +255 -0
- package/es/loaders/OmeZarrLoader.js +495 -0
- package/es/loaders/OpenCellLoader.js +65 -0
- package/es/loaders/RawArrayLoader.js +89 -0
- package/es/loaders/TiffLoader.js +219 -0
- package/es/loaders/VolumeLoadError.js +44 -0
- package/es/loaders/VolumeLoaderUtils.js +221 -0
- package/es/loaders/index.js +40 -0
- package/es/loaders/zarr_utils/ChunkPrefetchIterator.js +143 -0
- package/es/loaders/zarr_utils/WrappedStore.js +51 -0
- package/es/loaders/zarr_utils/types.js +24 -0
- package/es/loaders/zarr_utils/utils.js +225 -0
- package/es/loaders/zarr_utils/validation.js +49 -0
- package/es/test/ChunkPrefetchIterator.test.js +208 -0
- package/es/test/RequestQueue.test.js +442 -0
- package/es/test/SubscribableRequestQueue.test.js +244 -0
- package/es/test/VolumeCache.test.js +118 -0
- package/es/test/VolumeRenderSettings.test.js +71 -0
- package/es/test/lut.test.js +671 -0
- package/es/test/num_utils.test.js +140 -0
- package/es/test/volume.test.js +98 -0
- package/es/test/zarr_utils.test.js +358 -0
- package/es/types/Atlas2DSlice.d.ts +41 -0
- package/es/types/Channel.d.ts +44 -0
- package/es/types/FileSaver.d.ts +6 -0
- package/es/types/FusedChannelData.d.ts +26 -0
- package/es/types/Histogram.d.ts +57 -0
- package/es/types/ImageInfo.d.ts +87 -0
- package/es/types/Light.d.ts +27 -0
- package/es/types/Lut.d.ts +67 -0
- package/es/types/MarchingCubes.d.ts +53 -0
- package/es/types/MeshVolume.d.ts +40 -0
- package/es/types/NaiveSurfaceNets.d.ts +11 -0
- package/es/types/PathTracedVolume.d.ts +65 -0
- package/es/types/RayMarchedAtlasVolume.d.ts +41 -0
- package/es/types/RenderToBuffer.d.ts +17 -0
- package/es/types/ThreeJsPanel.d.ts +107 -0
- package/es/types/Timing.d.ts +11 -0
- package/es/types/TrackballControls.d.ts +51 -0
- package/es/types/View3d.d.ts +357 -0
- package/es/types/Volume.d.ts +152 -0
- package/es/types/VolumeCache.d.ts +43 -0
- package/es/types/VolumeDims.d.ts +28 -0
- package/es/types/VolumeDrawable.d.ts +108 -0
- package/es/types/VolumeMaker.d.ts +49 -0
- package/es/types/VolumeRenderImpl.d.ts +22 -0
- package/es/types/VolumeRenderSettings.d.ts +98 -0
- package/es/types/constants/basicShaders.d.ts +4 -0
- package/es/types/constants/colors.d.ts +2 -0
- package/es/types/constants/denoiseShader.d.ts +40 -0
- package/es/types/constants/lights.d.ts +38 -0
- package/es/types/constants/materials.d.ts +20 -0
- package/es/types/constants/pathtraceOutputShader.d.ts +11 -0
- package/es/types/constants/scaleBarSVG.d.ts +2 -0
- package/es/types/constants/time.d.ts +19 -0
- package/es/types/constants/volumePTshader.d.ts +137 -0
- package/es/types/constants/volumeRayMarchShader.d.ts +117 -0
- package/es/types/constants/volumeSliceShader.d.ts +109 -0
- package/es/types/glsl.d.js +0 -0
- package/es/types/index.d.ts +28 -0
- package/es/types/loaders/IVolumeLoader.d.ts +113 -0
- package/es/types/loaders/JsonImageInfoLoader.d.ts +80 -0
- package/es/types/loaders/OmeZarrLoader.d.ts +87 -0
- package/es/types/loaders/OpenCellLoader.d.ts +9 -0
- package/es/types/loaders/RawArrayLoader.d.ts +33 -0
- package/es/types/loaders/TiffLoader.d.ts +45 -0
- package/es/types/loaders/VolumeLoadError.d.ts +18 -0
- package/es/types/loaders/VolumeLoaderUtils.d.ts +38 -0
- package/es/types/loaders/index.d.ts +22 -0
- package/es/types/loaders/zarr_utils/ChunkPrefetchIterator.d.ts +22 -0
- package/es/types/loaders/zarr_utils/WrappedStore.d.ts +24 -0
- package/es/types/loaders/zarr_utils/types.d.ts +94 -0
- package/es/types/loaders/zarr_utils/utils.d.ts +23 -0
- package/es/types/loaders/zarr_utils/validation.d.ts +7 -0
- package/es/types/test/ChunkPrefetchIterator.test.d.ts +1 -0
- package/es/types/test/RequestQueue.test.d.ts +1 -0
- package/es/types/test/SubscribableRequestQueue.test.d.ts +1 -0
- package/es/types/test/VolumeCache.test.d.ts +1 -0
- package/es/types/test/VolumeRenderSettings.test.d.ts +1 -0
- package/es/types/test/lut.test.d.ts +1 -0
- package/es/types/test/num_utils.test.d.ts +1 -0
- package/es/types/test/volume.test.d.ts +1 -0
- package/es/types/test/zarr_utils.test.d.ts +1 -0
- package/es/types/types.d.ts +115 -0
- package/es/types/utils/RequestQueue.d.ts +112 -0
- package/es/types/utils/SubscribableRequestQueue.d.ts +52 -0
- package/es/types/utils/num_utils.d.ts +43 -0
- package/es/types/workers/VolumeLoaderContext.d.ts +106 -0
- package/es/types/workers/types.d.ts +101 -0
- package/es/types/workers/util.d.ts +3 -0
- package/es/types.js +75 -0
- package/es/typings.d.js +0 -0
- package/es/utils/RequestQueue.js +267 -0
- package/es/utils/SubscribableRequestQueue.js +187 -0
- package/es/utils/num_utils.js +231 -0
- package/es/workers/FetchTiffWorker.js +153 -0
- package/es/workers/VolumeLoadWorker.js +129 -0
- package/es/workers/VolumeLoaderContext.js +271 -0
- package/es/workers/types.js +41 -0
- package/es/workers/util.js +8 -0
- package/package.json +83 -0
package/LICENSE.txt
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
Allen Institute Software License – This software license is the 2-clause BSD license
|
|
2
|
+
plus clause a third clause that prohibits redistribution for commercial purposes without further permission.
|
|
3
|
+
|
|
4
|
+
Copyright © 2017. Allen Institute. All rights reserved.
|
|
5
|
+
|
|
6
|
+
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
|
|
7
|
+
following conditions are met:
|
|
8
|
+
|
|
9
|
+
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the
|
|
10
|
+
following disclaimer.
|
|
11
|
+
|
|
12
|
+
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
|
|
13
|
+
following disclaimer in the documentation and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Redistributions for commercial purposes are not permitted without the Allen Institute’s written permission.
|
|
16
|
+
For purposes of this license, commercial purposes is the incorporation of the Allen Institute's software into
|
|
17
|
+
anything for which you will charge fees or other compensation. Contact terms@alleninstitute.org for commercial
|
|
18
|
+
licensing opportunities.
|
|
19
|
+
|
|
20
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
|
21
|
+
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
23
|
+
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
24
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
|
25
|
+
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
|
|
26
|
+
USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
package/README.md
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# Vol-E core
|
|
2
|
+
|
|
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.
|
|
4
|
+
|
|
5
|
+
The Vol-E core package exposes several key modules:
|
|
6
|
+
|
|
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.
|
|
12
|
+
|
|
13
|
+
There are several ways to deliver volume data to the viewer:
|
|
14
|
+
|
|
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` ).
|
|
18
|
+
|
|
19
|
+
# Example
|
|
20
|
+
|
|
21
|
+
See [`public/index.ts`](./public/index.ts) for a working example. (`npm install; npm run dev` will run that code)
|
|
22
|
+
|
|
23
|
+
The basic code to get the viewer up and running is as follows:
|
|
24
|
+
|
|
25
|
+
```javascript
|
|
26
|
+
// find a div that will hold the 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);
|
|
31
|
+
|
|
32
|
+
// create the viewer. it will try to fill the parent element.
|
|
33
|
+
const view3D = new View3d(el);
|
|
34
|
+
view3D.loaderContext = loaderContext;
|
|
35
|
+
|
|
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);
|
|
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
|
+
});
|
|
56
|
+
// tell the viewer about the image
|
|
57
|
+
view3D.addVolume(volume);
|
|
58
|
+
// start requesting volume data
|
|
59
|
+
loader.loadVolumeData(volume);
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
# React example
|
|
63
|
+
|
|
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.
|
|
65
|
+
|
|
66
|
+
# Acknowledgements
|
|
67
|
+
|
|
68
|
+
The ray marched volume shader is a heavily modified version of one that has its origins in [Bisque](http://bioimage.ucsb.edu/bisque).
|
|
69
|
+
The core path tracing implementation was adapted from ExposureRender.
|
|
70
|
+
|
|
71
|
+
## BisQue license
|
|
72
|
+
|
|
73
|
+
Center for Bio-Image Informatics, University of California at Santa Barbara
|
|
74
|
+
|
|
75
|
+
Copyright (c) 2007-2017 by the Regents of the University of California
|
|
76
|
+
All rights reserved
|
|
77
|
+
|
|
78
|
+
Redistribution and use in source and binary forms, in whole or in parts, with or without
|
|
79
|
+
modification, are permitted provided that the following conditions are met:
|
|
80
|
+
|
|
81
|
+
Redistributions of source code must retain the above copyright
|
|
82
|
+
notice, this list of conditions, and the following disclaimer.
|
|
83
|
+
|
|
84
|
+
Redistributions in binary form must reproduce the above copyright
|
|
85
|
+
notice, this list of conditions, and the following disclaimer in
|
|
86
|
+
the documentation and/or other materials provided with the
|
|
87
|
+
distribution.
|
|
88
|
+
|
|
89
|
+
Use or redistribution must display the attribution with the logo
|
|
90
|
+
or project name and the project URL link in a location commonly
|
|
91
|
+
visible by the end users, unless specifically permitted by the
|
|
92
|
+
license holders.
|
|
93
|
+
|
|
94
|
+
THIS SOFTWARE IS PROVIDED BY THE REGENTS OF THE UNIVERSITY OF CALIFORNIA ''AS IS'' AND ANY
|
|
95
|
+
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
96
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|
97
|
+
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OF THE UNIVERSITY OF CALIFORNIA OR
|
|
98
|
+
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
|
99
|
+
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
100
|
+
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
101
|
+
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|
102
|
+
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
103
|
+
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
104
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
105
|
+
|
|
106
|
+
The views and conclusions contained in the software and documentation
|
|
107
|
+
are those of the authors and should not be interpreted as representing
|
|
108
|
+
official policies, either expressed or implied, of the Regents of the University of California.
|
|
109
|
+
|
|
110
|
+
## Exposure Render license
|
|
111
|
+
|
|
112
|
+
Copyright (c) 2011, T. Kroes <t.kroes@tudelft.nl>
|
|
113
|
+
All rights reserved.
|
|
114
|
+
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
|
115
|
+
- Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
|
116
|
+
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
|
117
|
+
- Neither the name of the TU Delft nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
|
118
|
+
|
|
119
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { Box3, Box3Helper, Color, Group, Matrix4, Mesh, PlaneGeometry, ShaderMaterial, Vector2, Vector3 } from "three";
|
|
2
|
+
import { sliceFragmentShaderSrc, sliceShaderUniforms, sliceVertexShaderSrc } from "./constants/volumeSliceShader.js";
|
|
3
|
+
import { SettingsFlags, VolumeRenderSettings } from "./VolumeRenderSettings.js";
|
|
4
|
+
import FusedChannelData from "./FusedChannelData.js";
|
|
5
|
+
const BOUNDING_BOX_DEFAULT_COLOR = new Color(0xffff00);
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Creates a plane that renders a 2D XY slice of volume atlas data.
|
|
9
|
+
*/
|
|
10
|
+
export default class Atlas2DSlice {
|
|
11
|
+
sliceUpdateWaiting = false;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Creates a new Atlas2DSlice.
|
|
15
|
+
* @param volume The volume that this renderer should render data from.
|
|
16
|
+
* @param settings Optional settings object. If set, updates the renderer with
|
|
17
|
+
* the given settings. Otherwise, uses the default VolumeRenderSettings.
|
|
18
|
+
*/
|
|
19
|
+
constructor(volume, settings = new VolumeRenderSettings(volume)) {
|
|
20
|
+
this.volume = volume;
|
|
21
|
+
this.uniforms = sliceShaderUniforms();
|
|
22
|
+
[this.geometry, this.geometryMesh] = this.createGeometry(this.uniforms);
|
|
23
|
+
this.boxHelper = new Box3Helper(new Box3(new Vector3(-0.5, -0.5, -0.5), new Vector3(0.5, 0.5, 0.5)), BOUNDING_BOX_DEFAULT_COLOR);
|
|
24
|
+
this.boxHelper.updateMatrixWorld();
|
|
25
|
+
this.boxHelper.visible = false;
|
|
26
|
+
this.geometryTransformNode = new Group();
|
|
27
|
+
this.geometryTransformNode.name = "VolumeContainerNode";
|
|
28
|
+
this.geometryTransformNode.add(this.boxHelper, this.geometryMesh);
|
|
29
|
+
this.setUniform("Z_SLICE", Math.floor(volume.imageInfo.volumeSize.z / 2));
|
|
30
|
+
this.settings = settings;
|
|
31
|
+
this.updateVolumeDimensions();
|
|
32
|
+
this.updateSettings(settings, SettingsFlags.ALL);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Syncs `this.settings.zSlice` with the corresponding shader uniform, or defers syncing until the slice is loaded.
|
|
37
|
+
* @returns a boolean indicating whether the slice is out of bounds of the volume entirely.
|
|
38
|
+
*/
|
|
39
|
+
updateSlice() {
|
|
40
|
+
const slice = Math.floor(this.settings.zSlice);
|
|
41
|
+
const sizez = this.volume.imageInfo.volumeSize.z;
|
|
42
|
+
if (slice < 0 || slice >= sizez) {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
const regionMinZ = this.volume.imageInfo.subregionOffset.z;
|
|
46
|
+
const regionMaxZ = regionMinZ + this.volume.imageInfo.subregionSize.z;
|
|
47
|
+
if (slice < regionMinZ || slice >= regionMaxZ) {
|
|
48
|
+
// If the slice is outside the current loaded subregion, defer until the subregion is updated
|
|
49
|
+
this.sliceUpdateWaiting = true;
|
|
50
|
+
} else {
|
|
51
|
+
this.setUniform("Z_SLICE", slice);
|
|
52
|
+
this.sliceUpdateWaiting = false;
|
|
53
|
+
}
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
updateVolumeDimensions() {
|
|
57
|
+
const volumeScale = this.volume.normPhysicalSize.clone().multiply(this.settings.scale);
|
|
58
|
+
const regionScale = volumeScale.clone().multiply(this.volume.normRegionSize);
|
|
59
|
+
const volumeOffset = this.volume.getContentCenter().clone().multiply(this.settings.scale);
|
|
60
|
+
this.geometryMesh.position.copy(volumeOffset);
|
|
61
|
+
// set scale
|
|
62
|
+
this.geometryMesh.scale.copy(regionScale);
|
|
63
|
+
this.setUniform("volumeScale", regionScale);
|
|
64
|
+
this.boxHelper.box.set(volumeScale.clone().multiplyScalar(-0.5), volumeScale.clone().multiplyScalar(0.5));
|
|
65
|
+
const {
|
|
66
|
+
atlasTileDims,
|
|
67
|
+
subregionSize,
|
|
68
|
+
volumeSize
|
|
69
|
+
} = this.volume.imageInfo;
|
|
70
|
+
const atlasSize = new Vector2(subregionSize.x, subregionSize.y).multiply(atlasTileDims);
|
|
71
|
+
|
|
72
|
+
// set lots of dimension uniforms
|
|
73
|
+
this.setUniform("ATLAS_DIMS", atlasTileDims);
|
|
74
|
+
this.setUniform("textureRes", atlasSize);
|
|
75
|
+
this.setUniform("SLICES", volumeSize.z);
|
|
76
|
+
if (this.sliceUpdateWaiting) {
|
|
77
|
+
this.updateSlice();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// (re)create channel data
|
|
81
|
+
if (!this.channelData || this.channelData.width !== atlasSize.x || this.channelData.height !== atlasSize.y) {
|
|
82
|
+
this.channelData?.cleanup();
|
|
83
|
+
this.channelData = new FusedChannelData(atlasSize.x, atlasSize.y);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
updateSettings(newSettings, dirtyFlags) {
|
|
87
|
+
if (dirtyFlags === undefined) {
|
|
88
|
+
dirtyFlags = SettingsFlags.ALL;
|
|
89
|
+
}
|
|
90
|
+
this.settings = newSettings;
|
|
91
|
+
if (dirtyFlags & SettingsFlags.VIEW) {
|
|
92
|
+
this.geometryMesh.visible = this.settings.visible;
|
|
93
|
+
// Configure ortho
|
|
94
|
+
this.setUniform("orthoScale", this.settings.orthoScale);
|
|
95
|
+
this.setUniform("isOrtho", this.settings.isOrtho ? 1.0 : 0.0);
|
|
96
|
+
// Ortho line thickness
|
|
97
|
+
const axis = this.settings.viewAxis;
|
|
98
|
+
if (this.settings.isOrtho && axis !== null) {
|
|
99
|
+
const maxVal = this.settings.bounds.bmax[axis];
|
|
100
|
+
const minVal = this.settings.bounds.bmin[axis];
|
|
101
|
+
const thicknessPct = maxVal - minVal;
|
|
102
|
+
this.setUniform("orthoThickness", thicknessPct);
|
|
103
|
+
} else {
|
|
104
|
+
this.setUniform("orthoThickness", 1.0);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (dirtyFlags & SettingsFlags.BOUNDING_BOX) {
|
|
108
|
+
// Configure bounding box
|
|
109
|
+
this.boxHelper.visible = this.settings.showBoundingBox;
|
|
110
|
+
const colorVector = this.settings.boundingBoxColor;
|
|
111
|
+
const newBoxColor = new Color(colorVector[0], colorVector[1], colorVector[2]);
|
|
112
|
+
this.boxHelper.material.color = newBoxColor;
|
|
113
|
+
}
|
|
114
|
+
if (dirtyFlags & SettingsFlags.TRANSFORM) {
|
|
115
|
+
// Set rotation and translation
|
|
116
|
+
this.geometryTransformNode.position.copy(this.settings.translation);
|
|
117
|
+
this.geometryTransformNode.rotation.copy(this.settings.rotation);
|
|
118
|
+
this.setUniform("flipVolume", this.settings.flipAxes);
|
|
119
|
+
}
|
|
120
|
+
if (dirtyFlags & SettingsFlags.MATERIAL) {
|
|
121
|
+
this.setUniform("DENSITY", this.settings.density);
|
|
122
|
+
}
|
|
123
|
+
if (dirtyFlags & SettingsFlags.CAMERA) {
|
|
124
|
+
this.setUniform("BRIGHTNESS", this.settings.brightness * 2.0);
|
|
125
|
+
// Gamma
|
|
126
|
+
this.setUniform("GAMMA_MIN", this.settings.gammaMin);
|
|
127
|
+
this.setUniform("GAMMA_MAX", this.settings.gammaMax);
|
|
128
|
+
this.setUniform("GAMMA_SCALE", this.settings.gammaLevel);
|
|
129
|
+
}
|
|
130
|
+
if (dirtyFlags & SettingsFlags.ROI) {
|
|
131
|
+
// Normalize and set bounds
|
|
132
|
+
const bounds = this.settings.bounds;
|
|
133
|
+
const {
|
|
134
|
+
normRegionSize,
|
|
135
|
+
normRegionOffset
|
|
136
|
+
} = this.volume;
|
|
137
|
+
const offsetToCenter = normRegionSize.clone().divideScalar(2).add(normRegionOffset).subScalar(0.5);
|
|
138
|
+
const bmin = bounds.bmin.clone().sub(offsetToCenter).divide(normRegionSize).clampScalar(-0.5, 0.5);
|
|
139
|
+
const bmax = bounds.bmax.clone().sub(offsetToCenter).divide(normRegionSize).clampScalar(-0.5, 0.5);
|
|
140
|
+
this.setUniform("AABB_CLIP_MIN", bmin);
|
|
141
|
+
this.setUniform("AABB_CLIP_MAX", bmax);
|
|
142
|
+
const sliceInBounds = this.updateSlice();
|
|
143
|
+
if (sliceInBounds) {
|
|
144
|
+
const sliceLowerBound = Math.floor(this.settings.zSlice) / this.volume.imageInfo.volumeSize.z;
|
|
145
|
+
const sliceUpperBound = (Math.floor(this.settings.zSlice) + 1) / this.volume.imageInfo.volumeSize.z;
|
|
146
|
+
this.volume.updateRequiredData({
|
|
147
|
+
subregion: new Box3(new Vector3(0, 0, sliceLowerBound), new Vector3(1, 1, sliceUpperBound))
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
if (dirtyFlags & SettingsFlags.SAMPLING) {
|
|
152
|
+
this.setUniform("interpolationEnabled", this.settings.useInterpolation);
|
|
153
|
+
this.setUniform("iResolution", this.settings.resolution);
|
|
154
|
+
}
|
|
155
|
+
if (dirtyFlags & SettingsFlags.MASK_ALPHA) {
|
|
156
|
+
this.setUniform("maskAlpha", this.settings.maskChannelIndex < 0 ? 1.0 : this.settings.maskAlpha);
|
|
157
|
+
}
|
|
158
|
+
if (dirtyFlags & SettingsFlags.MASK_DATA) {
|
|
159
|
+
this.channelData.setChannelAsMask(this.settings.maskChannelIndex, this.volume.getChannel(this.settings.maskChannelIndex));
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
createGeometry(uniforms) {
|
|
163
|
+
const geom = new PlaneGeometry(1.0, 1.0);
|
|
164
|
+
const mesh = new Mesh(geom);
|
|
165
|
+
mesh.name = "Plane";
|
|
166
|
+
|
|
167
|
+
// shader,vtx and frag.
|
|
168
|
+
const vtxsrc = sliceVertexShaderSrc;
|
|
169
|
+
const fgmtsrc = sliceFragmentShaderSrc;
|
|
170
|
+
const threeMaterial = new ShaderMaterial({
|
|
171
|
+
uniforms: uniforms,
|
|
172
|
+
vertexShader: vtxsrc,
|
|
173
|
+
fragmentShader: fgmtsrc,
|
|
174
|
+
transparent: true,
|
|
175
|
+
depthTest: true,
|
|
176
|
+
depthWrite: false
|
|
177
|
+
});
|
|
178
|
+
mesh.material = threeMaterial;
|
|
179
|
+
return [geom, mesh];
|
|
180
|
+
}
|
|
181
|
+
cleanup() {
|
|
182
|
+
this.geometry.dispose();
|
|
183
|
+
this.geometryMesh.material.dispose();
|
|
184
|
+
this.channelData.cleanup();
|
|
185
|
+
}
|
|
186
|
+
viewpointMoved() {
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
doRender(renderer, camera) {
|
|
190
|
+
if (!this.geometryMesh.visible) {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
this.channelData.gpuFuse(renderer);
|
|
194
|
+
this.setUniform("textureAtlas", this.channelData.getFusedTexture());
|
|
195
|
+
this.setUniform("textureAtlasMask", this.channelData.maskTexture);
|
|
196
|
+
this.geometryTransformNode.updateMatrixWorld(true);
|
|
197
|
+
const mvm = new Matrix4();
|
|
198
|
+
mvm.multiplyMatrices(camera.matrixWorldInverse, this.geometryMesh.matrixWorld);
|
|
199
|
+
const mi = new Matrix4();
|
|
200
|
+
mi.copy(mvm).invert();
|
|
201
|
+
this.setUniform("inverseModelViewMatrix", mi);
|
|
202
|
+
}
|
|
203
|
+
get3dObject() {
|
|
204
|
+
return this.geometryTransformNode;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// channelcolors is array of {rgbColor, lut} and channeldata is volume.channels
|
|
208
|
+
updateActiveChannels(channelcolors, channeldata) {
|
|
209
|
+
this.channelData.fuse(channelcolors, channeldata);
|
|
210
|
+
|
|
211
|
+
// update to fused texture
|
|
212
|
+
this.setUniform("textureAtlas", this.channelData.getFusedTexture());
|
|
213
|
+
this.setUniform("textureAtlasMask", this.channelData.maskTexture);
|
|
214
|
+
}
|
|
215
|
+
setUniform(name, value) {
|
|
216
|
+
if (!this.uniforms[name]) {
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
this.uniforms[name].value = value;
|
|
220
|
+
}
|
|
221
|
+
setRenderUpdateListener(_listener) {
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
}
|
package/es/Channel.js
ADDED
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import { DataTexture, LuminanceFormat, RedFormat, RedIntegerFormat, UnsignedByteType, ByteType, FloatType, IntType, UnsignedIntType, ShortType, UnsignedShortType, RGBAFormat, LinearFilter, NearestFilter, UVMapping, ClampToEdgeWrapping } from "three";
|
|
2
|
+
import Histogram from "./Histogram.js";
|
|
3
|
+
import { Lut, LUT_ARRAY_LENGTH } from "./Lut.js";
|
|
4
|
+
import { ARRAY_CONSTRUCTORS } from "./types.js";
|
|
5
|
+
// Data and processing for a single channel
|
|
6
|
+
export default class Channel {
|
|
7
|
+
constructor(name) {
|
|
8
|
+
this.loaded = false;
|
|
9
|
+
this.dtype = "uint8";
|
|
10
|
+
this.imgData = {
|
|
11
|
+
data: new Uint8Array(),
|
|
12
|
+
width: 0,
|
|
13
|
+
height: 0
|
|
14
|
+
};
|
|
15
|
+
this.rawMin = 0;
|
|
16
|
+
this.rawMax = 255;
|
|
17
|
+
|
|
18
|
+
// on gpu
|
|
19
|
+
this.dataTexture = new DataTexture(new Uint8Array(), 0, 0);
|
|
20
|
+
this.lutTexture = new DataTexture(new Uint8Array(LUT_ARRAY_LENGTH), 256, 1, RGBAFormat, UnsignedByteType);
|
|
21
|
+
this.lutTexture.minFilter = this.lutTexture.magFilter = LinearFilter;
|
|
22
|
+
this.lutTexture.generateMipmaps = false;
|
|
23
|
+
this.volumeData = new Uint8Array();
|
|
24
|
+
this.name = name;
|
|
25
|
+
this.histogram = new Histogram(new Uint8Array());
|
|
26
|
+
this.dims = [0, 0, 0];
|
|
27
|
+
|
|
28
|
+
// intensity remapping lookup table
|
|
29
|
+
this.lut = new Lut().createFromMinMax(0, 255);
|
|
30
|
+
|
|
31
|
+
// per-intensity color labeling (disabled initially)
|
|
32
|
+
this.colorPalette = new Uint8Array(LUT_ARRAY_LENGTH).fill(0);
|
|
33
|
+
// store in 0..1 range. 1 means fully colorPalette, 0 means fully lut.
|
|
34
|
+
this.colorPaletteAlpha = 0.0;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// rgbColor is [0..255, 0..255, 0..255]
|
|
38
|
+
combineLuts(rgbColor, out) {
|
|
39
|
+
const ret = out ? out : new Uint8Array(LUT_ARRAY_LENGTH);
|
|
40
|
+
if (!rgbColor) {
|
|
41
|
+
return ret;
|
|
42
|
+
}
|
|
43
|
+
const rgb = [rgbColor[0] / 255.0, rgbColor[1] / 255.0, rgbColor[2] / 255.0];
|
|
44
|
+
// colorPalette*alpha + rgb*lut*(1-alpha)
|
|
45
|
+
// a tiny bit faster for the edge cases
|
|
46
|
+
if (this.colorPaletteAlpha === 1.0) {
|
|
47
|
+
ret.set(this.colorPalette);
|
|
48
|
+
} else if (this.colorPaletteAlpha === 0.0) {
|
|
49
|
+
ret.set(this.lut.lut);
|
|
50
|
+
for (let i = 0; i < LUT_ARRAY_LENGTH / 4; ++i) {
|
|
51
|
+
ret[i * 4 + 0] *= rgb[0];
|
|
52
|
+
ret[i * 4 + 1] *= rgb[1];
|
|
53
|
+
ret[i * 4 + 2] *= rgb[2];
|
|
54
|
+
}
|
|
55
|
+
} else {
|
|
56
|
+
for (let i = 0; i < LUT_ARRAY_LENGTH / 4; ++i) {
|
|
57
|
+
ret[i * 4 + 0] = this.colorPalette[i * 4 + 0] * this.colorPaletteAlpha + this.lut.lut[i * 4 + 0] * (1.0 - this.colorPaletteAlpha) * rgb[0];
|
|
58
|
+
ret[i * 4 + 1] = this.colorPalette[i * 4 + 1] * this.colorPaletteAlpha + this.lut.lut[i * 4 + 1] * (1.0 - this.colorPaletteAlpha) * rgb[1];
|
|
59
|
+
ret[i * 4 + 2] = this.colorPalette[i * 4 + 2] * this.colorPaletteAlpha + this.lut.lut[i * 4 + 2] * (1.0 - this.colorPaletteAlpha) * rgb[2];
|
|
60
|
+
ret[i * 4 + 3] = this.colorPalette[i * 4 + 3] * this.colorPaletteAlpha + this.lut.lut[i * 4 + 3] * (1.0 - this.colorPaletteAlpha);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
this.lutTexture.image.data.set(ret);
|
|
64
|
+
this.lutTexture.needsUpdate = true;
|
|
65
|
+
return ret;
|
|
66
|
+
}
|
|
67
|
+
setRawDataRange(min, max) {
|
|
68
|
+
// remap the lut which was based on rawMin and rawMax to new min and max
|
|
69
|
+
// If either of the min/max ranges are both zero, then we have undefined behavior and should
|
|
70
|
+
// not remap the lut. This situation can happen at first load, for example,
|
|
71
|
+
// when one channel has arrived but others haven't.
|
|
72
|
+
if (!(this.rawMin === 0 && this.rawMax === 0) && !(min === 0 && max === 0)) {
|
|
73
|
+
this.lut.remapDomains(this.rawMin, this.rawMax, min, max);
|
|
74
|
+
this.rawMin = min;
|
|
75
|
+
this.rawMax = max;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
getHistogram() {
|
|
79
|
+
return this.histogram;
|
|
80
|
+
}
|
|
81
|
+
getIntensity(x, y, z) {
|
|
82
|
+
return this.volumeData[x + y * this.dims[0] + z * (this.dims[0] * this.dims[1])];
|
|
83
|
+
}
|
|
84
|
+
normalizeRaw(val) {
|
|
85
|
+
return (val - this.rawMin) / (this.rawMax - this.rawMin);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// how to index into tiled texture atlas
|
|
89
|
+
getIntensityFromAtlas(x, y, z) {
|
|
90
|
+
const numXtiles = this.imgData.width / this.dims[0];
|
|
91
|
+
const tilex = z % numXtiles;
|
|
92
|
+
const tiley = Math.floor(z / numXtiles);
|
|
93
|
+
const offset = tilex * this.dims[0] + x + (tiley * this.dims[1] + y) * this.imgData.width;
|
|
94
|
+
return this.imgData.data[offset];
|
|
95
|
+
}
|
|
96
|
+
rebuildDataTexture(data, w, h) {
|
|
97
|
+
if (this.dataTexture) {
|
|
98
|
+
this.dataTexture.dispose();
|
|
99
|
+
}
|
|
100
|
+
let format = LuminanceFormat;
|
|
101
|
+
let dataType = UnsignedByteType;
|
|
102
|
+
let internalFormat = "LUMINANCE";
|
|
103
|
+
switch (this.dtype) {
|
|
104
|
+
case "uint8":
|
|
105
|
+
dataType = UnsignedByteType;
|
|
106
|
+
format = RedIntegerFormat;
|
|
107
|
+
internalFormat = "R8UI";
|
|
108
|
+
break;
|
|
109
|
+
case "int8":
|
|
110
|
+
dataType = ByteType;
|
|
111
|
+
format = RedIntegerFormat;
|
|
112
|
+
internalFormat = "R8I";
|
|
113
|
+
break;
|
|
114
|
+
case "uint16":
|
|
115
|
+
dataType = UnsignedShortType;
|
|
116
|
+
format = RedIntegerFormat;
|
|
117
|
+
internalFormat = "R16UI";
|
|
118
|
+
break;
|
|
119
|
+
case "int16":
|
|
120
|
+
dataType = ShortType;
|
|
121
|
+
format = RedIntegerFormat;
|
|
122
|
+
internalFormat = "R16I";
|
|
123
|
+
break;
|
|
124
|
+
case "uint32":
|
|
125
|
+
dataType = UnsignedIntType;
|
|
126
|
+
format = RedIntegerFormat;
|
|
127
|
+
internalFormat = "R32UI";
|
|
128
|
+
break;
|
|
129
|
+
case "int32":
|
|
130
|
+
dataType = IntType;
|
|
131
|
+
format = RedIntegerFormat;
|
|
132
|
+
internalFormat = "R32I";
|
|
133
|
+
break;
|
|
134
|
+
case "float32":
|
|
135
|
+
dataType = FloatType;
|
|
136
|
+
format = RedFormat;
|
|
137
|
+
internalFormat = "R32F";
|
|
138
|
+
break;
|
|
139
|
+
default:
|
|
140
|
+
console.warn("unsupported dtype for channel data", this.dtype);
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
this.dataTexture = new DataTexture(data, w, h, format, dataType, UVMapping, ClampToEdgeWrapping, ClampToEdgeWrapping, NearestFilter, NearestFilter);
|
|
144
|
+
this.dataTexture.internalFormat = internalFormat;
|
|
145
|
+
this.dataTexture.needsUpdate = true;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// give the channel fresh data and initialize from that data
|
|
149
|
+
// data is formatted as a texture atlas where each tile is a z slice of the volume
|
|
150
|
+
setFromAtlas(bitsArray, w, h, dtype, rawMin, rawMax, subregionSize) {
|
|
151
|
+
this.dtype = dtype;
|
|
152
|
+
this.imgData = {
|
|
153
|
+
data: bitsArray,
|
|
154
|
+
width: w,
|
|
155
|
+
height: h
|
|
156
|
+
};
|
|
157
|
+
this.rebuildDataTexture(this.imgData.data, w, h);
|
|
158
|
+
this.loaded = true;
|
|
159
|
+
this.histogram = new Histogram(bitsArray);
|
|
160
|
+
|
|
161
|
+
// reuse old lut but auto-remap it to new data range
|
|
162
|
+
this.setRawDataRange(rawMin, rawMax);
|
|
163
|
+
this.unpackFromAtlas(subregionSize.x, subregionSize.y, subregionSize.z);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// let's rearrange this.imgData.data into a 3d array.
|
|
167
|
+
// it is assumed to be coming in as a flat Uint8Array of size x*y*z
|
|
168
|
+
// with x*y*z layout (first row of first plane is the first data in the layout,
|
|
169
|
+
// then second row of first plane, etc)
|
|
170
|
+
unpackFromAtlas(x, y, z) {
|
|
171
|
+
const volimgdata = this.imgData.data;
|
|
172
|
+
this.dims = [x, y, z];
|
|
173
|
+
const ctor = ARRAY_CONSTRUCTORS[this.dtype];
|
|
174
|
+
this.volumeData = new ctor(x * y * z);
|
|
175
|
+
const numXtiles = this.imgData.width / x;
|
|
176
|
+
const atlasrow = this.imgData.width;
|
|
177
|
+
let tilex = 0,
|
|
178
|
+
tiley = 0,
|
|
179
|
+
tileoffset = 0,
|
|
180
|
+
tilerowoffset = 0,
|
|
181
|
+
destOffset = 0;
|
|
182
|
+
for (let i = 0; i < z; ++i) {
|
|
183
|
+
// tile offset
|
|
184
|
+
tilex = i % numXtiles;
|
|
185
|
+
tiley = Math.floor(i / numXtiles);
|
|
186
|
+
tileoffset = tilex * x + tiley * y * atlasrow;
|
|
187
|
+
for (let j = 0; j < y; ++j) {
|
|
188
|
+
tilerowoffset = j * atlasrow;
|
|
189
|
+
destOffset = i * (x * y) + j * x;
|
|
190
|
+
this.volumeData.set(volimgdata.subarray(tileoffset + tilerowoffset, tileoffset + tilerowoffset + x), destOffset);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// give the channel fresh volume data and initialize from that data
|
|
196
|
+
setFromVolumeData(bitsArray, vx, vy, vz, ax, ay, rawMin, rawMax, dtype) {
|
|
197
|
+
this.dims = [vx, vy, vz];
|
|
198
|
+
this.volumeData = bitsArray;
|
|
199
|
+
this.dtype = dtype;
|
|
200
|
+
// TODO FIXME performance hit for shuffling the data and storing 2 versions of it (could do this in worker at least?)
|
|
201
|
+
this.packToAtlas(vx, vy, vz, ax, ay);
|
|
202
|
+
this.loaded = true;
|
|
203
|
+
// update from current histogram?
|
|
204
|
+
this.setRawDataRange(rawMin, rawMax);
|
|
205
|
+
this.histogram = new Histogram(this.volumeData);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// given this.volumeData, let's unpack it into a flat textureatlas and fill up this.imgData.
|
|
209
|
+
packToAtlas(vx, vy, vz, ax, ay) {
|
|
210
|
+
// big assumptions:
|
|
211
|
+
// atlassize is a perfect multiple of volumesize in both x and y
|
|
212
|
+
// ax % vx == 0
|
|
213
|
+
// ay % vy == 0
|
|
214
|
+
// and num slices <= num possible slices in atlas.
|
|
215
|
+
// (ax/vx) * (ay/vy) >= vz
|
|
216
|
+
if (ax % vx !== 0 || ay % vy !== 0 || ax / vx * (ay / vy) < vz) {
|
|
217
|
+
console.log("ERROR - atlas and volume dims are inconsistent");
|
|
218
|
+
console.log(ax, ay, vx, vy, vz);
|
|
219
|
+
}
|
|
220
|
+
const ctor = ARRAY_CONSTRUCTORS[this.dtype];
|
|
221
|
+
this.imgData = {
|
|
222
|
+
width: ax,
|
|
223
|
+
height: ay,
|
|
224
|
+
data: new ctor(ax * ay)
|
|
225
|
+
};
|
|
226
|
+
this.imgData.data.fill(0);
|
|
227
|
+
|
|
228
|
+
// deposit slices one by one into the imgData.data from volData.
|
|
229
|
+
const volimgdata = this.imgData.data;
|
|
230
|
+
const x = vx,
|
|
231
|
+
y = vy,
|
|
232
|
+
z = vz;
|
|
233
|
+
const numXtiles = this.imgData.width / x;
|
|
234
|
+
const atlasrow = this.imgData.width;
|
|
235
|
+
let tilex = 0,
|
|
236
|
+
tiley = 0,
|
|
237
|
+
tileoffset = 0,
|
|
238
|
+
tilerowoffset = 0,
|
|
239
|
+
sourceOffset = 0;
|
|
240
|
+
for (let i = 0; i < z; ++i) {
|
|
241
|
+
// tile offset
|
|
242
|
+
tilex = i % numXtiles;
|
|
243
|
+
tiley = Math.floor(i / numXtiles);
|
|
244
|
+
tileoffset = tilex * x + tiley * y * atlasrow;
|
|
245
|
+
for (let j = 0; j < y; ++j) {
|
|
246
|
+
tilerowoffset = j * atlasrow;
|
|
247
|
+
sourceOffset = i * (x * y) + j * x;
|
|
248
|
+
volimgdata.set(this.volumeData.subarray(sourceOffset, sourceOffset + x), tileoffset + tilerowoffset);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
this.rebuildDataTexture(this.imgData.data, ax, ay);
|
|
252
|
+
}
|
|
253
|
+
setLut(lut) {
|
|
254
|
+
this.lut = lut;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// palette should be an uint8array of 256*4 elements (256 rgba8 values)
|
|
258
|
+
setColorPalette(palette) {
|
|
259
|
+
this.colorPalette = palette;
|
|
260
|
+
}
|
|
261
|
+
setColorPaletteAlpha(alpha) {
|
|
262
|
+
this.colorPaletteAlpha = alpha;
|
|
263
|
+
}
|
|
264
|
+
}
|
package/es/FileSaver.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export default class FileSaver {
|
|
2
|
+
static save(blob, fname) {
|
|
3
|
+
const url = window.URL.createObjectURL(blob);
|
|
4
|
+
const anch = document.createElement("a");
|
|
5
|
+
anch.href = url;
|
|
6
|
+
anch.download = fname;
|
|
7
|
+
anch.style.display = "none";
|
|
8
|
+
document.body.appendChild(anch);
|
|
9
|
+
anch.click();
|
|
10
|
+
document.body.removeChild(anch);
|
|
11
|
+
window.URL.revokeObjectURL(url);
|
|
12
|
+
}
|
|
13
|
+
static saveString(s, fname) {
|
|
14
|
+
const blob = new Blob([s], {
|
|
15
|
+
type: "text/plain;charset=utf-8"
|
|
16
|
+
});
|
|
17
|
+
FileSaver.save(blob, fname);
|
|
18
|
+
}
|
|
19
|
+
static saveBinary(s, fname) {
|
|
20
|
+
const blob = new Blob([new Uint8Array(s)], {
|
|
21
|
+
type: "application/octet-stream"
|
|
22
|
+
});
|
|
23
|
+
FileSaver.save(blob, fname);
|
|
24
|
+
}
|
|
25
|
+
static saveArrayBuffer(buffer, filename) {
|
|
26
|
+
const blob = new Blob([buffer], {
|
|
27
|
+
type: "application/octet-stream"
|
|
28
|
+
});
|
|
29
|
+
FileSaver.save(blob, filename);
|
|
30
|
+
}
|
|
31
|
+
}
|