@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
|
@@ -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 };
|