@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,255 @@
1
+ import { Box3, Vector3 } from "three";
2
+ import { ThreadableVolumeLoader } from "./IVolumeLoader.js";
3
+ import { computeAtlasSize } from "../ImageInfo.js";
4
+ import { getDataRange } from "../utils/num_utils.js";
5
+
6
+ /* eslint-disable @typescript-eslint/naming-convention */
7
+
8
+ /* eslint-enable @typescript-eslint/naming-convention */
9
+
10
+ const rescalePixelSize = json => {
11
+ // the pixel_size_x/y/z are the physical size of the original pixels represented by
12
+ // width and height. We need to get a physical pixel size that is consistent
13
+ // with the tile_width and tile_height.
14
+ const px = json.pixel_size_x * json.width / json.tile_width;
15
+ const py = json.pixel_size_y * json.height / json.tile_height;
16
+ const pz = json.pixel_size_z;
17
+ return [px, py, pz];
18
+ };
19
+ const convertImageInfo = json => {
20
+ const [px, py, pz] = rescalePixelSize(json);
21
+ // translation is in pixels that are in the space of json.width, json.height.
22
+ // We need to convert this to the space of the tile_width and tile_height.
23
+ const tr = json.transform?.translation ?? [0, 0, 0];
24
+ tr[0] = tr[0] * json.tile_width / json.width;
25
+ tr[1] = tr[1] * json.tile_height / json.height;
26
+ return {
27
+ name: json.name,
28
+ atlasTileDims: [json.cols, json.rows],
29
+ subregionSize: [json.tile_width, json.tile_height, json.tiles],
30
+ subregionOffset: [0, 0, 0],
31
+ combinedNumChannels: json.channels,
32
+ channelNames: json.channel_names,
33
+ channelColors: json.channel_colors,
34
+ multiscaleLevel: 0,
35
+ multiscaleLevelDims: [{
36
+ shape: [json.times || 1, json.channels, json.tiles, json.tile_height, json.tile_width],
37
+ spacing: [json.time_scale || 1, 1, pz, py, px],
38
+ spaceUnit: json.pixel_size_unit || "μm",
39
+ timeUnit: json.time_unit || "s",
40
+ dataType: "uint8"
41
+ }],
42
+ transform: {
43
+ translation: tr,
44
+ rotation: json.transform?.rotation ? json.transform.rotation : [0, 0, 0],
45
+ scale: [1, 1, 1]
46
+ },
47
+ userData: {
48
+ ...json.userData,
49
+ // for metadata display reasons
50
+ originalVolumeSize: [json.width, json.height, json.tiles],
51
+ originalPhysicalPixelSize: [json.pixel_size_x, json.pixel_size_y, json.pixel_size_z]
52
+ }
53
+ };
54
+ };
55
+ class JsonImageInfoLoader extends ThreadableVolumeLoader {
56
+ syncChannels = false;
57
+ constructor(urls, cache) {
58
+ super();
59
+ if (Array.isArray(urls)) {
60
+ this.urls = urls;
61
+ } else {
62
+ this.urls = [urls];
63
+ }
64
+ this.jsonInfo = new Array(this.urls.length);
65
+ this.cache = cache;
66
+ }
67
+ async getJsonImageInfo(time) {
68
+ const cachedInfo = this.jsonInfo[time];
69
+ if (cachedInfo) {
70
+ return cachedInfo;
71
+ }
72
+ const response = await fetch(this.urls[time]);
73
+ const imageInfo = await response.json();
74
+ imageInfo.pixel_size_unit = imageInfo.pixel_size_unit || "μm";
75
+ imageInfo.times = imageInfo.times || this.urls.length;
76
+ this.jsonInfo[time] = imageInfo;
77
+ return imageInfo;
78
+ }
79
+ syncMultichannelLoading(sync) {
80
+ this.syncChannels = sync;
81
+ }
82
+ async loadDims(loadSpec) {
83
+ const jsonInfo = await this.getJsonImageInfo(loadSpec.time);
84
+ const [px, py, pz] = rescalePixelSize(jsonInfo);
85
+ const d = {
86
+ shape: [jsonInfo.times || 1, jsonInfo.channels, jsonInfo.tiles, jsonInfo.tile_height, jsonInfo.tile_width],
87
+ spacing: [1, 1, pz, py, px],
88
+ spaceUnit: jsonInfo.pixel_size_unit ?? "μm",
89
+ dataType: "uint8",
90
+ timeUnit: jsonInfo.time_unit ?? "s"
91
+ };
92
+ return [d];
93
+ }
94
+ async createImageInfo(loadSpec) {
95
+ const jsonInfo = await this.getJsonImageInfo(loadSpec.time);
96
+ return {
97
+ imageInfo: convertImageInfo(jsonInfo),
98
+ loadSpec
99
+ };
100
+ }
101
+ async loadRawChannelData(imageInfo, loadSpec, onUpdateMetadata, onData) {
102
+ // if you need to adjust image paths prior to download,
103
+ // now is the time to do it.
104
+ // Try to figure out the urlPrefix from the LoadSpec.
105
+ // For this format we assume the image data is in the same directory as the json file.
106
+ const jsonInfo = await this.getJsonImageInfo(loadSpec.time);
107
+ let images = jsonInfo?.images;
108
+ if (!images) {
109
+ return;
110
+ }
111
+ const requestedChannels = loadSpec.channels;
112
+ if (requestedChannels) {
113
+ // If only some channels are requested, load only images which contain at least one requested channel
114
+ images = images.filter(({
115
+ channels
116
+ }) => channels.some(ch => requestedChannels.includes(ch)));
117
+ }
118
+
119
+ // This regex removes everything after the last slash, so the url had better be simple.
120
+ const urlPrefix = this.urls[loadSpec.time].replace(/[^/]*$/, "");
121
+ images = images.map(element => ({
122
+ ...element,
123
+ name: urlPrefix + element.name
124
+ }));
125
+
126
+ // Update `image`'s `loadSpec` before loading
127
+ const adjustedLoadSpec = {
128
+ ...loadSpec,
129
+ // `subregion` and `multiscaleLevel` are unused by this loader
130
+ subregion: new Box3(new Vector3(0, 0, 0), new Vector3(1, 1, 1)),
131
+ multiscaleLevel: 0,
132
+ // include all channels in any loaded images
133
+ channels: images.flatMap(({
134
+ channels
135
+ }) => channels)
136
+ };
137
+ onUpdateMetadata(undefined, adjustedLoadSpec);
138
+ const [w, h] = computeAtlasSize(imageInfo);
139
+ const wrappedOnData = (ch, dtype, data, ranges) => onData(ch, dtype, data, ranges, [w, h]);
140
+ await JsonImageInfoLoader.loadVolumeAtlasData(images, wrappedOnData, this.cache, this.syncChannels);
141
+ }
142
+
143
+ /**
144
+ * load per-channel volume data from a batch of image files containing the volume slices tiled across the images
145
+ * @param {Array.<{name:string, channels:Array.<number>}>} imageArray
146
+ * @param {RawChannelDataCallback} onData Per-channel callback. Called when each channel's atlased volume data is loaded
147
+ * @param {VolumeCache} cache
148
+ * @example loadVolumeAtlasData([{
149
+ * "name": "AICS-10_5_5.ome.tif_atlas_0.png",
150
+ * "channels": [0, 1, 2]
151
+ * }, {
152
+ * "name": "AICS-10_5_5.ome.tif_atlas_1.png",
153
+ * "channels": [3, 4, 5]
154
+ * }, {
155
+ * "name": "AICS-10_5_5.ome.tif_atlas_2.png",
156
+ * "channels": [6, 7, 8]
157
+ * }], mycallback);
158
+ */
159
+ static async loadVolumeAtlasData(imageArray, onData, cache, syncChannels = false) {
160
+ const resultChannelIndices = [];
161
+ const resultChannelDtype = [];
162
+ const resultChannelData = [];
163
+ const resultChannelRanges = [];
164
+ const imagePromises = imageArray.map(async image => {
165
+ // Because the data is fetched such that one fetch returns a whole batch,
166
+ // if any in batch is cached then they all should be. So if any in batch is NOT cached,
167
+ // then we will have to do a batch request. This logic works both ways because it's all or nothing.
168
+ let cacheHit = true;
169
+ for (let j = 0; j < Math.min(image.channels.length, 4); ++j) {
170
+ const chindex = image.channels[j];
171
+ const cacheResult = cache?.get(`${image.name}/${chindex}`);
172
+ if (cacheResult) {
173
+ // all data coming from this loader is natively 8-bit
174
+ const channelData = new Uint8Array(cacheResult);
175
+ if (syncChannels) {
176
+ // if we are synchronizing channels, we need to keep track of the data
177
+ resultChannelIndices.push(chindex);
178
+ resultChannelDtype.push("uint8");
179
+ resultChannelData.push(channelData);
180
+ resultChannelRanges.push(getDataRange(channelData));
181
+ } else {
182
+ onData([chindex], ["uint8"], [channelData], [getDataRange(channelData)]);
183
+ }
184
+ } else {
185
+ cacheHit = false;
186
+ // we can stop checking because we know we are going to have to fetch the whole batch
187
+ break;
188
+ }
189
+ }
190
+
191
+ // if all channels were in cache then we can move on to the next
192
+ // image (batch) without requesting
193
+ if (cacheHit) {
194
+ return;
195
+ }
196
+ const response = await fetch(image.name, {
197
+ mode: "cors"
198
+ });
199
+ const blob = await response.blob();
200
+ const bitmap = await createImageBitmap(blob);
201
+ const canvas = new OffscreenCanvas(bitmap.width, bitmap.height);
202
+ const ctx = canvas.getContext("2d");
203
+ if (!ctx) {
204
+ console.log("Error creating canvas 2d context for " + image.name);
205
+ return;
206
+ }
207
+ ctx.globalCompositeOperation = "copy";
208
+ ctx.globalAlpha = 1.0;
209
+ ctx.drawImage(bitmap, 0, 0);
210
+ const iData = ctx.getImageData(0, 0, bitmap.width, bitmap.height);
211
+ const channelsBits = [];
212
+ const length = bitmap.width * bitmap.height;
213
+
214
+ // allocate channels in batch
215
+ for (let ch = 0; ch < Math.min(image.channels.length, 4); ++ch) {
216
+ channelsBits.push(new Uint8Array(length));
217
+ }
218
+
219
+ // extract the data
220
+ const channelRange = [];
221
+ for (let j = 0; j < Math.min(image.channels.length, 4); ++j) {
222
+ let rawMin = Infinity;
223
+ let rawMax = -Infinity;
224
+ for (let px = 0; px < length; px++) {
225
+ channelsBits[j][px] = iData.data[px * 4 + j];
226
+ rawMin = Math.min(rawMin, channelsBits[j][px]);
227
+ rawMax = Math.max(rawMax, channelsBits[j][px]);
228
+ }
229
+ channelRange[j] = [rawMin, rawMax];
230
+ }
231
+
232
+ // done with `iData` and `canvas` now.
233
+
234
+ for (let ch = 0; ch < Math.min(image.channels.length, 4); ++ch) {
235
+ const chindex = image.channels[ch];
236
+ cache?.insert(`${image.name}/${chindex}`, channelsBits[ch]);
237
+ // NOTE: the atlas dimensions passed in here are currently unused by `JSONImageInfoLoader`
238
+ // all data coming from this loader is natively 8-bit
239
+ if (syncChannels) {
240
+ resultChannelIndices.push(chindex);
241
+ resultChannelDtype.push("uint8");
242
+ resultChannelData.push(channelsBits[ch]);
243
+ resultChannelRanges.push(channelRange[ch]);
244
+ } else {
245
+ onData([chindex], ["uint8"], [channelsBits[ch]], [channelRange[ch]], [bitmap.width, bitmap.height]);
246
+ }
247
+ }
248
+ });
249
+ await Promise.all(imagePromises);
250
+ if (syncChannels) {
251
+ onData(resultChannelIndices, resultChannelDtype, resultChannelData, resultChannelRanges);
252
+ }
253
+ }
254
+ }
255
+ export { JsonImageInfoLoader };