@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.
Files changed (141) hide show
  1. package/LICENSE.txt +26 -0
  2. package/README.md +119 -0
  3. package/es/Atlas2DSlice.js +224 -0
  4. package/es/Channel.js +264 -0
  5. package/es/FileSaver.js +31 -0
  6. package/es/FusedChannelData.js +192 -0
  7. package/es/Histogram.js +250 -0
  8. package/es/ImageInfo.js +127 -0
  9. package/es/Light.js +74 -0
  10. package/es/Lut.js +500 -0
  11. package/es/MarchingCubes.js +507 -0
  12. package/es/MeshVolume.js +334 -0
  13. package/es/NaiveSurfaceNets.js +251 -0
  14. package/es/PathTracedVolume.js +482 -0
  15. package/es/RayMarchedAtlasVolume.js +250 -0
  16. package/es/RenderToBuffer.js +31 -0
  17. package/es/ThreeJsPanel.js +633 -0
  18. package/es/Timing.js +28 -0
  19. package/es/TrackballControls.js +538 -0
  20. package/es/View3d.js +848 -0
  21. package/es/Volume.js +352 -0
  22. package/es/VolumeCache.js +161 -0
  23. package/es/VolumeDims.js +16 -0
  24. package/es/VolumeDrawable.js +702 -0
  25. package/es/VolumeMaker.js +101 -0
  26. package/es/VolumeRenderImpl.js +1 -0
  27. package/es/VolumeRenderSettings.js +203 -0
  28. package/es/constants/basicShaders.js +29 -0
  29. package/es/constants/colors.js +59 -0
  30. package/es/constants/denoiseShader.js +43 -0
  31. package/es/constants/lights.js +42 -0
  32. package/es/constants/materials.js +85 -0
  33. package/es/constants/pathtraceOutputShader.js +13 -0
  34. package/es/constants/scaleBarSVG.js +21 -0
  35. package/es/constants/time.js +34 -0
  36. package/es/constants/volumePTshader.js +153 -0
  37. package/es/constants/volumeRayMarchShader.js +123 -0
  38. package/es/constants/volumeSliceShader.js +115 -0
  39. package/es/index.js +21 -0
  40. package/es/loaders/IVolumeLoader.js +131 -0
  41. package/es/loaders/JsonImageInfoLoader.js +255 -0
  42. package/es/loaders/OmeZarrLoader.js +495 -0
  43. package/es/loaders/OpenCellLoader.js +65 -0
  44. package/es/loaders/RawArrayLoader.js +89 -0
  45. package/es/loaders/TiffLoader.js +219 -0
  46. package/es/loaders/VolumeLoadError.js +44 -0
  47. package/es/loaders/VolumeLoaderUtils.js +221 -0
  48. package/es/loaders/index.js +40 -0
  49. package/es/loaders/zarr_utils/ChunkPrefetchIterator.js +143 -0
  50. package/es/loaders/zarr_utils/WrappedStore.js +51 -0
  51. package/es/loaders/zarr_utils/types.js +24 -0
  52. package/es/loaders/zarr_utils/utils.js +225 -0
  53. package/es/loaders/zarr_utils/validation.js +49 -0
  54. package/es/test/ChunkPrefetchIterator.test.js +208 -0
  55. package/es/test/RequestQueue.test.js +442 -0
  56. package/es/test/SubscribableRequestQueue.test.js +244 -0
  57. package/es/test/VolumeCache.test.js +118 -0
  58. package/es/test/VolumeRenderSettings.test.js +71 -0
  59. package/es/test/lut.test.js +671 -0
  60. package/es/test/num_utils.test.js +140 -0
  61. package/es/test/volume.test.js +98 -0
  62. package/es/test/zarr_utils.test.js +358 -0
  63. package/es/types/Atlas2DSlice.d.ts +41 -0
  64. package/es/types/Channel.d.ts +44 -0
  65. package/es/types/FileSaver.d.ts +6 -0
  66. package/es/types/FusedChannelData.d.ts +26 -0
  67. package/es/types/Histogram.d.ts +57 -0
  68. package/es/types/ImageInfo.d.ts +87 -0
  69. package/es/types/Light.d.ts +27 -0
  70. package/es/types/Lut.d.ts +67 -0
  71. package/es/types/MarchingCubes.d.ts +53 -0
  72. package/es/types/MeshVolume.d.ts +40 -0
  73. package/es/types/NaiveSurfaceNets.d.ts +11 -0
  74. package/es/types/PathTracedVolume.d.ts +65 -0
  75. package/es/types/RayMarchedAtlasVolume.d.ts +41 -0
  76. package/es/types/RenderToBuffer.d.ts +17 -0
  77. package/es/types/ThreeJsPanel.d.ts +107 -0
  78. package/es/types/Timing.d.ts +11 -0
  79. package/es/types/TrackballControls.d.ts +51 -0
  80. package/es/types/View3d.d.ts +357 -0
  81. package/es/types/Volume.d.ts +152 -0
  82. package/es/types/VolumeCache.d.ts +43 -0
  83. package/es/types/VolumeDims.d.ts +28 -0
  84. package/es/types/VolumeDrawable.d.ts +108 -0
  85. package/es/types/VolumeMaker.d.ts +49 -0
  86. package/es/types/VolumeRenderImpl.d.ts +22 -0
  87. package/es/types/VolumeRenderSettings.d.ts +98 -0
  88. package/es/types/constants/basicShaders.d.ts +4 -0
  89. package/es/types/constants/colors.d.ts +2 -0
  90. package/es/types/constants/denoiseShader.d.ts +40 -0
  91. package/es/types/constants/lights.d.ts +38 -0
  92. package/es/types/constants/materials.d.ts +20 -0
  93. package/es/types/constants/pathtraceOutputShader.d.ts +11 -0
  94. package/es/types/constants/scaleBarSVG.d.ts +2 -0
  95. package/es/types/constants/time.d.ts +19 -0
  96. package/es/types/constants/volumePTshader.d.ts +137 -0
  97. package/es/types/constants/volumeRayMarchShader.d.ts +117 -0
  98. package/es/types/constants/volumeSliceShader.d.ts +109 -0
  99. package/es/types/glsl.d.js +0 -0
  100. package/es/types/index.d.ts +28 -0
  101. package/es/types/loaders/IVolumeLoader.d.ts +113 -0
  102. package/es/types/loaders/JsonImageInfoLoader.d.ts +80 -0
  103. package/es/types/loaders/OmeZarrLoader.d.ts +87 -0
  104. package/es/types/loaders/OpenCellLoader.d.ts +9 -0
  105. package/es/types/loaders/RawArrayLoader.d.ts +33 -0
  106. package/es/types/loaders/TiffLoader.d.ts +45 -0
  107. package/es/types/loaders/VolumeLoadError.d.ts +18 -0
  108. package/es/types/loaders/VolumeLoaderUtils.d.ts +38 -0
  109. package/es/types/loaders/index.d.ts +22 -0
  110. package/es/types/loaders/zarr_utils/ChunkPrefetchIterator.d.ts +22 -0
  111. package/es/types/loaders/zarr_utils/WrappedStore.d.ts +24 -0
  112. package/es/types/loaders/zarr_utils/types.d.ts +94 -0
  113. package/es/types/loaders/zarr_utils/utils.d.ts +23 -0
  114. package/es/types/loaders/zarr_utils/validation.d.ts +7 -0
  115. package/es/types/test/ChunkPrefetchIterator.test.d.ts +1 -0
  116. package/es/types/test/RequestQueue.test.d.ts +1 -0
  117. package/es/types/test/SubscribableRequestQueue.test.d.ts +1 -0
  118. package/es/types/test/VolumeCache.test.d.ts +1 -0
  119. package/es/types/test/VolumeRenderSettings.test.d.ts +1 -0
  120. package/es/types/test/lut.test.d.ts +1 -0
  121. package/es/types/test/num_utils.test.d.ts +1 -0
  122. package/es/types/test/volume.test.d.ts +1 -0
  123. package/es/types/test/zarr_utils.test.d.ts +1 -0
  124. package/es/types/types.d.ts +115 -0
  125. package/es/types/utils/RequestQueue.d.ts +112 -0
  126. package/es/types/utils/SubscribableRequestQueue.d.ts +52 -0
  127. package/es/types/utils/num_utils.d.ts +43 -0
  128. package/es/types/workers/VolumeLoaderContext.d.ts +106 -0
  129. package/es/types/workers/types.d.ts +101 -0
  130. package/es/types/workers/util.d.ts +3 -0
  131. package/es/types.js +75 -0
  132. package/es/typings.d.js +0 -0
  133. package/es/utils/RequestQueue.js +267 -0
  134. package/es/utils/SubscribableRequestQueue.js +187 -0
  135. package/es/utils/num_utils.js +231 -0
  136. package/es/workers/FetchTiffWorker.js +153 -0
  137. package/es/workers/VolumeLoadWorker.js +129 -0
  138. package/es/workers/VolumeLoaderContext.js +271 -0
  139. package/es/workers/types.js +41 -0
  140. package/es/workers/util.js +8 -0
  141. 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
+ }
@@ -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
+ }