@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
@@ -0,0 +1,192 @@
1
+ import { Color, DataTexture, RedFormat, UnsignedByteType, ClampToEdgeWrapping, Scene, OrthographicCamera, WebGLRenderTarget, RGBAFormat, ShaderMaterial, Mesh, PlaneGeometry, OneFactor, CustomBlending, MaxEquation, LinearFilter, Vector2 } from "three";
2
+ import { renderToBufferVertShader } from "./constants/basicShaders.js";
3
+ /* babel-plugin-inline-import './constants/shaders/fuseUI.frag' */
4
+ const fuseShaderSrcUI = "precision highp float;\nprecision highp int;\nprecision highp usampler2D;\nprecision highp sampler3D;\n\n// the lut texture is a 256x1 rgba texture for each channel\nuniform sampler2D lutSampler;\n\nuniform vec2 lutMinMax;\n\n// src texture is the raw volume intensity data\nuniform usampler2D srcTexture;\n\nvoid main()\n{\n ivec2 vUv = ivec2(int(gl_FragCoord.x), int(gl_FragCoord.y));\n uint intensity = texelFetch(srcTexture, vUv, 0).r;\n float ilookup = float(float(intensity) - lutMinMax.x) / float(lutMinMax.y - lutMinMax.x);\n // apply lut to intensity:\n vec4 pix = texture(lutSampler, vec2(ilookup, 0.5));\n gl_FragColor = vec4(pix.xyz*pix.w, pix.w);\n}\n";
5
+ /* babel-plugin-inline-import './constants/shaders/fuseF.frag' */
6
+ const fuseShaderSrcF = "precision highp float;\nprecision highp int;\nprecision highp sampler2D;\nprecision highp sampler3D;\n\n// the lut texture is a 256x1 rgba texture for each channel\nuniform sampler2D lutSampler;\n\nuniform vec2 lutMinMax;\n\n// src texture is the raw volume intensity data\nuniform sampler2D srcTexture;\n\nvoid main()\n{\n ivec2 vUv = ivec2(int(gl_FragCoord.x), int(gl_FragCoord.y));\n\n // load from channel\n float intensity = texelFetch(srcTexture, vUv, 0).r;\n\n float ilookup = float(float(intensity) - lutMinMax.x) / float(lutMinMax.y - lutMinMax.x);\n // apply lut to intensity:\n vec4 pix = texture(lutSampler, vec2(ilookup, 0.5));\n gl_FragColor = vec4(pix.xyz*pix.w, pix.w);\n}\n";
7
+ /* babel-plugin-inline-import './constants/shaders/fuseI.frag' */
8
+ const fuseShaderSrcI = "precision highp float;\nprecision highp int;\nprecision highp sampler2D;\nprecision highp sampler3D;\n\n// the lut texture is a 256x1 rgba texture for each channel\nuniform sampler2D lutSampler;\n\nuniform vec2 lutMinMax;\n\n// src texture is the raw volume intensity data\nuniform isampler2D srcTexture;\n\nvoid main()\n{\n ivec2 vUv = ivec2(int(gl_FragCoord.x), int(gl_FragCoord.y));\n int intensity = texelFetch(srcTexture, vUv, 0).r;\n float ilookup = float(float(intensity) - lutMinMax.x) / float(lutMinMax.y - lutMinMax.x);\n // apply lut to intensity:\n vec4 pix = texture(lutSampler, vec2(ilookup, 0.5));\n gl_FragColor = vec4(pix.xyz*pix.w, pix.w);\n}\n";
9
+ // This is the owner of the fused RGBA volume texture atlas, and the mask texture atlas.
10
+ // This module is responsible for updating the fused texture, given the read-only volume channel data.
11
+ export default class FusedChannelData {
12
+ constructor(atlasX, atlasY) {
13
+ // allow for resizing
14
+ this.width = atlasX;
15
+ this.height = atlasY;
16
+ this.maskTexture = new DataTexture(new Uint8ClampedArray(this.width * this.height).fill(255), this.width, this.height, RedFormat, UnsignedByteType);
17
+ this.maskTexture.generateMipmaps = false;
18
+ this.maskTexture.magFilter = LinearFilter;
19
+ this.maskTexture.minFilter = LinearFilter;
20
+ this.maskTexture.wrapS = ClampToEdgeWrapping;
21
+ this.maskTexture.wrapT = ClampToEdgeWrapping;
22
+ // for single-channel tightly packed array data:
23
+ this.maskTexture.unpackAlignment = 1;
24
+ this.fuseRequested = null;
25
+ this.channelsDataToFuse = [];
26
+ this.fuseScene = new Scene();
27
+ this.quadCamera = new OrthographicCamera(-1, 1, 1, -1, 0, 1);
28
+ this.fuseRenderTarget = new WebGLRenderTarget(this.width, this.height, {
29
+ minFilter: LinearFilter,
30
+ magFilter: LinearFilter,
31
+ format: RGBAFormat,
32
+ type: UnsignedByteType,
33
+ // FloatType ?
34
+ depthBuffer: false,
35
+ stencilBuffer: false,
36
+ generateMipmaps: false,
37
+ wrapS: ClampToEdgeWrapping,
38
+ wrapT: ClampToEdgeWrapping
39
+ });
40
+ this.fuseMaterialProps = {
41
+ vertexShader: renderToBufferVertShader,
42
+ depthTest: false,
43
+ depthWrite: false,
44
+ blending: CustomBlending,
45
+ blendSrc: OneFactor,
46
+ blendDst: OneFactor,
47
+ blendEquation: MaxEquation
48
+ };
49
+ // this exists to keep one reference alive
50
+ // to make sure we do not fully delete and re-create
51
+ // a shader every time.
52
+ this.fuseMaterialF = this.setupFuseMaterial(fuseShaderSrcF);
53
+ this.fuseMaterialUI = this.setupFuseMaterial(fuseShaderSrcUI);
54
+ this.fuseMaterialI = this.setupFuseMaterial(fuseShaderSrcI);
55
+ this.fuseMaterialF.needsUpdate = true;
56
+ this.fuseMaterialUI.needsUpdate = true;
57
+ this.fuseMaterialI.needsUpdate = true;
58
+ this.fuseGeometry = new PlaneGeometry(2, 2);
59
+ }
60
+ setupFuseMaterial(fragShaderSrc) {
61
+ return new ShaderMaterial({
62
+ uniforms: {
63
+ lutSampler: {
64
+ value: null
65
+ },
66
+ lutMinMax: {
67
+ value: new Vector2(0, 255)
68
+ },
69
+ srcTexture: {
70
+ value: null
71
+ }
72
+ },
73
+ fragmentShader: fragShaderSrc,
74
+ ...this.fuseMaterialProps
75
+ });
76
+ }
77
+ getFusedTexture() {
78
+ return this.fuseRenderTarget.texture;
79
+ }
80
+ cleanup() {
81
+ this.fuseScene.clear();
82
+ this.maskTexture.dispose();
83
+ }
84
+ getShader(dtype) {
85
+ switch (dtype) {
86
+ case "float32":
87
+ return this.fuseMaterialF;
88
+ case "uint8":
89
+ case "uint16":
90
+ case "uint32":
91
+ return this.fuseMaterialUI;
92
+ case "int8":
93
+ case "int16":
94
+ case "int32":
95
+ return this.fuseMaterialI;
96
+ default:
97
+ throw new Error("Unsupported data type for fuse shader");
98
+ }
99
+ }
100
+ fuse(combination, channels) {
101
+ // we can fuse if we have any loaded channels that are showing.
102
+ // actually, we can fuse if no channels are showing (but they are loaded), too.
103
+ let canFuse = false;
104
+ for (let i = 0; i < combination.length; ++i) {
105
+ const c = combination[i];
106
+ const idx = c.chIndex;
107
+ if (channels[idx].loaded) {
108
+ // set the lut in this fuse combination.
109
+ // can optimize by calling combineLuts more lazily
110
+ c.lut = channels[idx].combineLuts(c.rgbColor, c.lut);
111
+ canFuse = true;
112
+ //break;
113
+ }
114
+ }
115
+ if (!canFuse) {
116
+ this.channelsDataToFuse = [];
117
+ this.fuseRequested = [];
118
+ return;
119
+ }
120
+ this.fuseRequested = combination;
121
+ this.channelsDataToFuse = channels;
122
+ }
123
+ gpuFuse(renderer) {
124
+ const combination = this.fuseRequested;
125
+ const channels = this.channelsDataToFuse;
126
+ if (!combination) {
127
+ return;
128
+ }
129
+
130
+ // webgl draw one mesh per channel to fuse. clear texture to 0,0,0,0
131
+
132
+ this.fuseScene.traverse(node => {
133
+ if (node instanceof Mesh) {
134
+ // materials were holding references to the channel data textures
135
+ // causing mem leak so we must dispose before clearing the scene
136
+ node.material.dispose();
137
+ }
138
+ });
139
+ this.fuseScene.clear();
140
+ for (let i = 0; i < combination.length; ++i) {
141
+ if (combination[i].rgbColor) {
142
+ const chIndex = combination[i].chIndex;
143
+ if (!channels[chIndex].loaded) {
144
+ continue;
145
+ }
146
+ // add a draw call per channel here.
147
+ // must clone the material to keep a unique set of uniforms
148
+ const mat = this.getShader(channels[chIndex].dtype).clone();
149
+ mat.uniforms.lutSampler.value = channels[chIndex].lutTexture;
150
+ // the lut texture is spanning only the data range of the channel, not the datatype range
151
+ mat.uniforms.lutMinMax.value = new Vector2(channels[chIndex].rawMin, channels[chIndex].rawMax);
152
+ mat.uniforms.srcTexture.value = channels[chIndex].dataTexture;
153
+ this.fuseScene.add(new Mesh(this.fuseGeometry, mat));
154
+ }
155
+ }
156
+ if (this.fuseScene.children.length > 0) {
157
+ renderer.setRenderTarget(this.fuseRenderTarget);
158
+ renderer.autoClearColor = true;
159
+ const prevClearColor = new Color();
160
+ renderer.getClearColor(prevClearColor);
161
+ const prevClearAlpha = renderer.getClearAlpha();
162
+ renderer.setClearColor(0x000000, 0);
163
+ renderer.render(this.fuseScene, this.quadCamera);
164
+ renderer.setRenderTarget(null);
165
+ renderer.setClearColor(prevClearColor, prevClearAlpha);
166
+ }
167
+ // "dirty flag"
168
+ this.fuseRequested = null;
169
+ }
170
+
171
+ // currently only one channel can be selected to participate as a mask
172
+ setChannelAsMask(idx, channel) {
173
+ if (!channel || !channel.loaded) {
174
+ return false;
175
+ }
176
+ // binarize the data
177
+ // (TODO consider whether it should be binarized or not?)
178
+ const datacopy = new Uint8ClampedArray(channel.imgData.data.length);
179
+ for (let i = 0; i < channel.imgData.data.length; i++) {
180
+ datacopy[i] = channel.imgData.data[i] > 0 ? 255 : 0;
181
+ }
182
+ const maskData = {
183
+ data: datacopy,
184
+ width: this.width,
185
+ height: this.height,
186
+ colorSpace: "srgb"
187
+ };
188
+ this.maskTexture.image = maskData;
189
+ this.maskTexture.needsUpdate = true;
190
+ return true;
191
+ }
192
+ }
@@ -0,0 +1,250 @@
1
+ const NBINS = 256;
2
+ /**
3
+ * Builds a histogram with 256 bins from a data array. Assume data is 8 bit single channel grayscale.
4
+ * @class
5
+ * @param {Array.<number>} data
6
+ */
7
+ export default class Histogram {
8
+ // no more than 2^32 pixels of any one intensity in the data!?!?!
9
+
10
+ /** Min value in the original raw data. */
11
+
12
+ /** Max value in the original raw data. */
13
+
14
+ /** Size of each histogram bin in the scale of the original data. */
15
+
16
+ /** Index of the first bin (other than 0) with at least 1 value. */
17
+
18
+ /** Index of the last bin (other than 0) with at least 1 value. */
19
+
20
+ constructor(data) {
21
+ this.dataMinBin = 0;
22
+ this.dataMaxBin = 0;
23
+ this.maxBin = 0;
24
+ this.bins = new Uint32Array();
25
+ this.min = 0;
26
+ this.max = 0;
27
+ this.binSize = 0;
28
+
29
+ // build up the histogram
30
+ const hinfo = Histogram.calculateHistogram(data, NBINS);
31
+ this.bins = hinfo.bins;
32
+ this.min = hinfo.min;
33
+ this.max = hinfo.max;
34
+ this.binSize = hinfo.binSize;
35
+
36
+ // TODO: These should always return 0 and NBINS - 1, respectively. Test if these
37
+ // can be removed.
38
+ for (let i = 0; i < this.bins.length; i++) {
39
+ if (this.bins[i] > 0) {
40
+ this.dataMinBin = i;
41
+ break;
42
+ }
43
+ }
44
+ for (let i = this.bins.length - 1; i >= 0; i--) {
45
+ if (this.bins[i] > 0) {
46
+ this.dataMaxBin = i;
47
+ break;
48
+ }
49
+ }
50
+ this.pixelCount = data.length;
51
+
52
+ // get the bin with the most frequently occurring NONZERO value
53
+ this.maxBin = 1;
54
+ let max = this.bins[1];
55
+ for (let i = 1; i < this.bins.length; i++) {
56
+ if (this.bins[i] > max) {
57
+ this.maxBin = i;
58
+ max = this.bins[i];
59
+ }
60
+ }
61
+ }
62
+
63
+ // return the bin index of the given data value
64
+ static findBin(dataValue, dataMin, binSize, numBins) {
65
+ let binIndex = Math.floor((dataValue - dataMin) / binSize);
66
+ // for values that lie exactly on last bin we need to subtract one
67
+ if (binIndex === numBins) {
68
+ binIndex--;
69
+ }
70
+ return binIndex;
71
+ }
72
+
73
+ // return the bin index of the given data value
74
+ findBinOfValue(value) {
75
+ return Histogram.findBin(value, this.min, this.binSize, NBINS);
76
+ }
77
+
78
+ /**
79
+ * Return the min data value
80
+ * @return {number}
81
+ */
82
+ getDataMin() {
83
+ return this.min;
84
+ }
85
+
86
+ /**
87
+ * Return the max data value
88
+ * @return {number}
89
+ */
90
+ getDataMax() {
91
+ return this.max;
92
+ }
93
+
94
+ /**
95
+ * Returns the first bin index with at least 1 value, other than the 0th bin.
96
+ * @return {number}
97
+ */
98
+ getMin() {
99
+ return this.dataMinBin;
100
+ }
101
+
102
+ /**
103
+ * Returns the last bin index with at least 1 value, other than the 0th bin.
104
+ * @return {number}
105
+ */
106
+ getMax() {
107
+ // Note that this will always return `NBINS - 1`.
108
+ return this.dataMaxBin;
109
+ }
110
+ getNumBins() {
111
+ return this.bins.length;
112
+ }
113
+ getBin(i) {
114
+ return this.bins[i];
115
+ }
116
+ getBinRange(i) {
117
+ return [this.min + i * this.binSize, this.min + (i + 1) * this.binSize];
118
+ }
119
+
120
+ /**
121
+ * Find the bin that contains the percentage of pixels below it
122
+ * @return {number}
123
+ * @param {number} pct
124
+ */
125
+ findBinOfPercentile(pct) {
126
+ const limit = this.pixelCount * pct;
127
+ let i = 0;
128
+ let count = 0;
129
+ for (i = 0; i < this.bins.length; ++i) {
130
+ count += this.bins[i];
131
+ if (count > limit) {
132
+ break;
133
+ }
134
+ }
135
+ return i;
136
+ }
137
+
138
+ // Find bins at 10th / 90th percentile
139
+ findBestFitBins() {
140
+ const pixcount = this.pixelCount;
141
+ //const pixcount = this.imgData.data.length;
142
+ const limit = pixcount / 10;
143
+ let i = 0;
144
+ let count = 0;
145
+ for (i = 1; i < this.bins.length; ++i) {
146
+ count += this.bins[i];
147
+ if (count > limit) {
148
+ break;
149
+ }
150
+ }
151
+ const hmin = i;
152
+ count = 0;
153
+ for (i = this.bins.length - 1; i >= 1; --i) {
154
+ count += this.bins[i];
155
+ if (count > limit) {
156
+ break;
157
+ }
158
+ }
159
+ const hmax = i;
160
+ return [hmin, hmax];
161
+ }
162
+
163
+ // Find min and max bins attempting to replicate ImageJ's "Auto" button
164
+ findAutoIJBins() {
165
+ // note that consecutive applications of this should modify the auto threshold. see:
166
+ // https://github.com/imagej/ImageJ/blob/7746fcb0f5744a7a7758244c5dcd2193459e6e0e/ij/plugin/frame/ContrastAdjuster.java#L816
167
+ const AUTO_THRESHOLD = 5000;
168
+ const pixcount = this.pixelCount;
169
+ // const pixcount = this.imgData.data.length;
170
+ const limit = pixcount / 10;
171
+ const threshold = pixcount / AUTO_THRESHOLD;
172
+
173
+ // this will skip the "zero" bin which contains pixels of zero intensity.
174
+ let hmin = this.bins.length - 1;
175
+ let hmax = 1;
176
+ for (let i = 1; i < this.bins.length; ++i) {
177
+ if (this.bins[i] > threshold && this.bins[i] <= limit) {
178
+ hmin = i;
179
+ break;
180
+ }
181
+ }
182
+ for (let i = this.bins.length - 1; i >= 1; --i) {
183
+ if (this.bins[i] > threshold && this.bins[i] <= limit) {
184
+ hmax = i;
185
+ break;
186
+ }
187
+ }
188
+ if (hmax < hmin) {
189
+ hmin = 0;
190
+ hmax = 255;
191
+ }
192
+ return [hmin, hmax];
193
+ }
194
+
195
+ // Find min and max bins using a percentile of the most commonly occurring value
196
+ findAutoMinMax() {
197
+ // simple linear mapping cutting elements with small appearence
198
+ // get 10% threshold
199
+ const PERCENTAGE = 0.1;
200
+ const th = Math.floor(this.bins[this.maxBin] * PERCENTAGE);
201
+ let b = 0;
202
+ let e = this.bins.length - 1;
203
+ for (let x = 1; x < this.bins.length; ++x) {
204
+ if (this.bins[x] > th) {
205
+ b = x;
206
+ break;
207
+ }
208
+ }
209
+ for (let x = this.bins.length - 1; x >= 1; --x) {
210
+ if (this.bins[x] > th) {
211
+ e = x;
212
+ break;
213
+ }
214
+ }
215
+ return [b, e];
216
+ }
217
+ static calculateHistogram(arr, numBins = 1) {
218
+ if (numBins < 1) {
219
+ numBins = 1;
220
+ }
221
+
222
+ // calculate min and max of arr
223
+ // TODO See convertChannel, which will also compute min and max!
224
+ // We could save a whole extra loop over the data, or have convertChannel compute the whole histogram.
225
+ // need to be careful about computing over chunks or whole ready-to-display volume
226
+
227
+ let min = arr[0];
228
+ let max = arr[0];
229
+ for (let i = 1; i < arr.length; i++) {
230
+ if (arr[i] < min) {
231
+ min = arr[i];
232
+ } else if (arr[i] > max) {
233
+ max = arr[i];
234
+ }
235
+ }
236
+ const bins = new Uint32Array(numBins).fill(0);
237
+ const binSize = (max - min) / numBins === 0 ? 1 : (max - min) / numBins;
238
+ for (let i = 0; i < arr.length; i++) {
239
+ const item = arr[i];
240
+ const binIndex = Histogram.findBin(item, min, binSize, numBins);
241
+ bins[binIndex]++;
242
+ }
243
+ return {
244
+ bins,
245
+ min,
246
+ max,
247
+ binSize
248
+ };
249
+ }
250
+ }
@@ -0,0 +1,127 @@
1
+ import { volumeSize, physicalPixelSize } from "./VolumeDims.js";
2
+ import { Vector3, Vector2 } from "three";
3
+ export function defaultImageInfo() {
4
+ return {
5
+ name: "",
6
+ atlasTileDims: [1, 1],
7
+ subregionSize: [1, 1, 1],
8
+ subregionOffset: [0, 0, 0],
9
+ combinedNumChannels: 1,
10
+ channelNames: ["0"],
11
+ channelColors: [[255, 255, 255]],
12
+ multiscaleLevel: 0,
13
+ multiscaleLevelDims: [{
14
+ shape: [1, 1, 1, 1, 1],
15
+ spacing: [1, 1, 1, 1, 1],
16
+ spaceUnit: "",
17
+ timeUnit: "",
18
+ dataType: "uint8"
19
+ }],
20
+ transform: {
21
+ translation: [0, 0, 0],
22
+ rotation: [0, 0, 0],
23
+ scale: [1, 1, 1]
24
+ }
25
+ };
26
+ }
27
+ export class CImageInfo {
28
+ constructor(imageInfo) {
29
+ this.imageInfo = imageInfo || defaultImageInfo();
30
+ }
31
+ get currentLevelDims() {
32
+ return this.imageInfo.multiscaleLevelDims[this.imageInfo.multiscaleLevel];
33
+ }
34
+
35
+ /** Number of channels in the image */
36
+ get numChannels() {
37
+ return this.imageInfo.combinedNumChannels;
38
+ }
39
+
40
+ /** XYZ size of the *original* (not downsampled) volume, in pixels */
41
+ get originalSize() {
42
+ return volumeSize(this.imageInfo.multiscaleLevelDims[0]);
43
+ }
44
+
45
+ /** Size of the volume, in pixels */
46
+ get volumeSize() {
47
+ return volumeSize(this.currentLevelDims);
48
+ }
49
+
50
+ /** Size of a single *original* (not downsampled) pixel, in spatial units */
51
+ get physicalPixelSize() {
52
+ return physicalPixelSize(this.imageInfo.multiscaleLevelDims[0]);
53
+ }
54
+
55
+ /** Symbol of physical spatial unit used by `physicalPixelSize` */
56
+ get spatialUnit() {
57
+ return this.imageInfo.multiscaleLevelDims[0].spaceUnit;
58
+ }
59
+
60
+ /** Number of timesteps in the time series, or 1 if the image is not a time series */
61
+ get times() {
62
+ // 0 is T
63
+ return this.currentLevelDims.shape[0];
64
+ }
65
+
66
+ /** Size of each timestep in temporal units */
67
+ get timeScale() {
68
+ // 0 is T
69
+ return this.currentLevelDims.spacing[0];
70
+ }
71
+
72
+ /** Symbol of physical time unit used by `timeScale` */
73
+ get timeUnit() {
74
+ return this.currentLevelDims.timeUnit;
75
+ }
76
+
77
+ /** Number of scale levels available for this volume */
78
+ get numMultiscaleLevels() {
79
+ return this.imageInfo.multiscaleLevelDims.length;
80
+ }
81
+
82
+ /** The names of each channel */
83
+ get channelNames() {
84
+ return this.imageInfo.channelNames;
85
+ }
86
+
87
+ /** Optional overrides to default channel colors, in 0-255 range */
88
+ get channelColors() {
89
+ return this.imageInfo.channelColors;
90
+ }
91
+
92
+ /** Size of the currently loaded subregion, in pixels */
93
+ get subregionSize() {
94
+ return new Vector3(...this.imageInfo.subregionSize);
95
+ }
96
+
97
+ /** Offset of the loaded subregion into the total volume, in pixels */
98
+ get subregionOffset() {
99
+ return new Vector3(...this.imageInfo.subregionOffset);
100
+ }
101
+ get multiscaleLevel() {
102
+ return this.imageInfo.multiscaleLevel;
103
+ }
104
+
105
+ /**
106
+ * XY dimensions of the texture atlas used by `RayMarchedAtlasVolume` and `Atlas2DSlice`, in number of z-slice
107
+ * tiles (not pixels). Chosen by the loader to lay out the 3D volume in the squarest possible 2D texture atlas.
108
+ */
109
+ get atlasTileDims() {
110
+ return new Vector2(...this.imageInfo.atlasTileDims);
111
+ }
112
+ get transform() {
113
+ return {
114
+ translation: new Vector3(...this.imageInfo.transform.translation),
115
+ rotation: new Vector3(...this.imageInfo.transform.rotation),
116
+ scale: new Vector3(...this.imageInfo.transform.scale)
117
+ };
118
+ }
119
+ }
120
+ export function computeAtlasSize(imageInfo) {
121
+ const {
122
+ atlasTileDims
123
+ } = imageInfo;
124
+ const volDims = imageInfo.multiscaleLevelDims[imageInfo.multiscaleLevel];
125
+ // TCZYX: 4 = x, 3 = y
126
+ return [atlasTileDims[0] * volDims.shape[4], atlasTileDims[1] * volDims.shape[3]];
127
+ }
package/es/Light.js ADDED
@@ -0,0 +1,74 @@
1
+ import { Vector3 } from "three";
2
+ export const AREA_LIGHT = 0;
3
+ export const SKY_LIGHT = 1;
4
+ export class Light {
5
+ // type = 1 for sky light, 0 for area light
6
+ constructor(type) {
7
+ this.mTheta = 14 * Math.PI / 180.0;
8
+ this.mPhi = 54 * Math.PI / 180.0;
9
+ this.mWidth = 1.0;
10
+ this.mHeight = 1.0;
11
+ this.mHalfWidth = 0.5 * this.mWidth;
12
+ this.mHalfHeight = 0.5 * this.mHeight;
13
+ this.mDistance = 4.0;
14
+ this.mSkyRadius = 1000.0;
15
+ this.mP = new Vector3();
16
+ this.mTarget = new Vector3();
17
+ this.mArea = 1.0;
18
+ this.mAreaPdf = 1.0 / this.mArea;
19
+ this.mColor = new Vector3(75, 75, 75);
20
+ this.mColorTop = new Vector3(0.3, 0.3, 0.3);
21
+ this.mColorMiddle = new Vector3(0.3, 0.3, 0.3);
22
+ this.mColorBottom = new Vector3(0.3, 0.3, 0.3);
23
+
24
+ // type = 1 for sky light, 0 for area light
25
+ this.mT = type;
26
+
27
+ // secondary properties:
28
+ this.mN = new Vector3(0, 0, 1);
29
+ this.mU = new Vector3(1, 0, 0);
30
+ this.mV = new Vector3(0, 1, 0);
31
+ this.update(new Vector3(0, 0, 0));
32
+ }
33
+ update(targetPoint, cameraMatrix) {
34
+ this.mHalfWidth = 0.5 * this.mWidth;
35
+ this.mHalfHeight = 0.5 * this.mHeight;
36
+ this.mTarget.copy(targetPoint);
37
+
38
+ // Determine light position
39
+ this.mP.x = this.mDistance * Math.sin(this.mPhi) * Math.sin(this.mTheta);
40
+ this.mP.z = this.mDistance * Math.sin(this.mPhi) * Math.cos(this.mTheta);
41
+ this.mP.y = this.mDistance * Math.cos(this.mPhi);
42
+ this.mP.add(this.mTarget);
43
+ if (cameraMatrix) {
44
+ // We want to treat the lights as positioned relative to the camera, so camera rotations should not move them.
45
+ // In other words, when we rotate the camera, it should seem like we are tumbling the volume under fixed lighting.
46
+ this.mP.applyMatrix4(cameraMatrix);
47
+ this.mTarget.applyMatrix4(cameraMatrix);
48
+ }
49
+
50
+ // Determine area
51
+ if (this.mT === AREA_LIGHT) {
52
+ this.mArea = this.mWidth * this.mHeight;
53
+ this.mAreaPdf = 1.0 / this.mArea;
54
+ } else if (this.mT === SKY_LIGHT) {
55
+ this.mP.copy(targetPoint);
56
+ // shift by nonzero amount
57
+ this.mTarget.addVectors(this.mP, new Vector3(0.0, 0.0, 1.0));
58
+ this.mSkyRadius = 1000.0 * targetPoint.length() * 2.0;
59
+ this.mArea = 4.0 * Math.PI * Math.pow(this.mSkyRadius, 2.0);
60
+ this.mAreaPdf = 1.0 / this.mArea;
61
+ }
62
+
63
+ // Compute orthogonal basis frame
64
+ this.mN.subVectors(this.mTarget, this.mP).normalize();
65
+ // if N and "up" are parallel, then just choose a different "up"
66
+ if (this.mN.y === 1.0 || this.mN.y === -1.0) {
67
+ this.mU.crossVectors(this.mN, new Vector3(1.0, 0.0, 0.0)).normalize();
68
+ } else {
69
+ // standard "up" vector
70
+ this.mU.crossVectors(this.mN, new Vector3(0.0, 1.0, 0.0)).normalize();
71
+ }
72
+ this.mV.crossVectors(this.mN, this.mU).normalize();
73
+ }
74
+ }