@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,231 @@
1
+ import { parseTimeUnit, TimeUnit } from "../constants/time.js";
2
+ import { Axis } from "../VolumeRenderSettings.js";
3
+ export const DEFAULT_SIG_FIGS = 5;
4
+ const SECONDS_IN_MS = 1000;
5
+ const MINUTES_IN_MS = SECONDS_IN_MS * 60;
6
+ const HOURS_IN_MS = MINUTES_IN_MS * 60;
7
+ const DAYS_IN_MS = HOURS_IN_MS * 24;
8
+
9
+ // Adapted from https://gist.github.com/ArneS/2ecfbe4a9d7072ac56c0.
10
+ function digitToUnicodeSupercript(n) {
11
+ const subst = [0x2070, 185, 178, 179, 0x2074, 0x2075, 0x2076, 0x2077, 0x2078, 0x2079];
12
+ return String.fromCharCode(subst[n]);
13
+ }
14
+
15
+ /**
16
+ * Converts a number to scientific notation with the specified number of significant
17
+ * figures, handling negative numbers and rounding.
18
+ * @param input The number to convert.
19
+ * @param significantFigures the number of signficant figures/digits. Must be >= 1.
20
+ * @returns a string, formatted as a number in scientific notation.
21
+ * @example
22
+ * ```
23
+ * numberToSciNotation(1, 3) // "1.00×10⁰"
24
+ * numberToSciNotation(0.99, 2) // "9.9×10⁻¹"
25
+ * numberToSciNotation(0.999, 2) // "1.0×10⁰"
26
+ * numberToSciNotation(-0.05, 1) // "-5×10⁻²"
27
+ * numberToSciNotation(1400, 3) // "1.40×10³"
28
+ * ```
29
+ */
30
+ function numberToSciNotation(input, sigFigs = DEFAULT_SIG_FIGS) {
31
+ const nativeExpForm = input.toExponential(sigFigs - 1);
32
+ const [significand, exponent] = nativeExpForm.split("e");
33
+ const expSign = exponent[0] === "-" ? "⁻" : "";
34
+ const expDigits = exponent.slice(1).split("");
35
+ const expSuperscript = expDigits.map(digit => digitToUnicodeSupercript(Number(digit))).join("");
36
+ return `${significand}×10${expSign}${expSuperscript}`;
37
+ }
38
+
39
+ /**
40
+ * Returns a string-encoded number rounded to a specified decimal precision, without ever formatting in scientific
41
+ * notation like `Number.toPrecision` might do.
42
+ */
43
+ function toSigFigs(value, sigFigs) {
44
+ const exponent = Math.floor(Math.log10(Math.abs(value)));
45
+ return value.toFixed(Math.max(sigFigs - exponent - 1, 0));
46
+ }
47
+
48
+ /** Trims trailing instances of `char` off the end of `str`. */
49
+ // This is not technically a number utility, but it's useful to `formatNumber` below.
50
+ function trimTrailing(str, char) {
51
+ let i = str.length - 1;
52
+ while (str[i] === char) {
53
+ i--;
54
+ }
55
+ return str.slice(0, i + 1);
56
+ }
57
+
58
+ /**
59
+ * Formats numbers for display as a string with a (hopefully) limited length.
60
+ *
61
+ * - If the number is an integer with 4 or fewer digits, it is returned as a string.
62
+ * - If the number is a decimal, it is rounded to `sigFigs` significant figures. (default 5)
63
+ * - If the number's absolute value is over 10,000 or less than 0.01, it is formatted in scientific notation to
64
+ * `sciSigFigs` significant figures. (Default `sigFigs - 2`, so 3 if neither are specified. The `- 2` leaves space
65
+ * for the exponential part. Remember: the purpose of this function is keeping number strings *consistently* short!)
66
+ */
67
+ export function formatNumber(value, sigFigs = DEFAULT_SIG_FIGS, sciSigFigs = sigFigs - 2) {
68
+ const valueAbs = Math.abs(value);
69
+ if (Number.isInteger(value)) {
70
+ // Format integers with 5+ digits in scientific notation
71
+ if (valueAbs >= 10_000) {
72
+ return numberToSciNotation(value, sciSigFigs);
73
+ }
74
+ // Just stringify other integers
75
+ return value.toString();
76
+ } else {
77
+ const numStr = toSigFigs(value, sigFigs);
78
+ const numRounded = Math.abs(Number(numStr));
79
+ if (numRounded >= 10_000 || numRounded < 0.01) {
80
+ return numberToSciNotation(value, sciSigFigs);
81
+ }
82
+ const trimmed = trimTrailing(numStr, "0");
83
+ return trimmed.endsWith(".") ? trimmed.slice(0, -1) : trimmed;
84
+ }
85
+ }
86
+ const timeUnitEnumToMilliseconds = {
87
+ [TimeUnit.MILLISECOND]: 1,
88
+ [TimeUnit.SECOND]: SECONDS_IN_MS,
89
+ [TimeUnit.MINUTE]: MINUTES_IN_MS,
90
+ [TimeUnit.HOUR]: HOURS_IN_MS,
91
+ [TimeUnit.DAY]: DAYS_IN_MS
92
+ };
93
+ export function timeToMilliseconds(time, unit) {
94
+ const timeUnitMultiplier = timeUnitEnumToMilliseconds[unit];
95
+ if (timeUnitMultiplier === undefined) {
96
+ throw new Error("Unrecognized time unit");
97
+ }
98
+ return time * timeUnitMultiplier;
99
+ }
100
+
101
+ /**
102
+ * Pads the `value` with zeroes to the specified `length` if `shouldPad` is true
103
+ * and returns the resulting string. Otherwise, returns the string representation of `value`.
104
+ */
105
+ function padConditionally(value, length, shouldPad) {
106
+ return shouldPad ? value.toString().padStart(length, "0") : value.toString();
107
+ }
108
+ function formatTimestamp(timeMs, options) {
109
+ const {
110
+ useMs,
111
+ useSec,
112
+ useMin,
113
+ useHours,
114
+ useDays
115
+ } = options;
116
+ const digits = [];
117
+ const units = [];
118
+ if (useDays) {
119
+ const days = Math.floor(timeMs / DAYS_IN_MS);
120
+ digits.push(days.toString());
121
+ units.push("d");
122
+ }
123
+ if (useHours) {
124
+ const hours = Math.floor(timeMs % DAYS_IN_MS / HOURS_IN_MS);
125
+ // If the previous unit is included, pad the hours to 2 digits so the
126
+ // timestamp is consistent.
127
+ digits.push(padConditionally(hours, 2, useDays));
128
+ units.push("h");
129
+ }
130
+ if (useMin) {
131
+ const minutes = Math.floor(timeMs % HOURS_IN_MS / MINUTES_IN_MS);
132
+ digits.push(padConditionally(minutes, 2, useHours));
133
+ units.push("m");
134
+ }
135
+ if (useSec) {
136
+ const seconds = Math.floor(timeMs % MINUTES_IN_MS / SECONDS_IN_MS);
137
+ let secondString = padConditionally(seconds, 2, useMin);
138
+ units.push("s");
139
+ // If using milliseconds, add as a decimal to the seconds string.
140
+ if (useMs) {
141
+ const milliseconds = Math.floor(timeMs % SECONDS_IN_MS);
142
+ secondString += "." + milliseconds.toString().padStart(3, "0");
143
+ // Do not add milliseconds to unit label, since they'll be shown as
144
+ // part of the seconds string.
145
+ }
146
+ digits.push(secondString);
147
+ } else if (useMs) {
148
+ const milliseconds = Math.floor(timeMs % SECONDS_IN_MS);
149
+ digits.push(milliseconds.toString());
150
+ units.push("ms");
151
+ }
152
+ return {
153
+ timestamp: digits.join(":"),
154
+ units: units.join(":")
155
+ };
156
+ }
157
+
158
+ /**
159
+ * Gets a timestamp formatted as `{time} / {total} {unit}`. If `unit` is a recognized
160
+ * time unit, the timestamp will be formatted as a `d:hh:mm:ss.ms` string.
161
+ *
162
+ * @param time Current time, in specified units.
163
+ * @param total Total time, in specified units.
164
+ * @param unit The unit of time.
165
+ * @returns A formatted timestamp string.
166
+ * - If `unit` is not recognized, the timestamp will be formatted as `{time} / {total} {unit}`,
167
+ * where `time` and `total` are formatted with significant digits as needed.
168
+ * - If `unit` is recognized, the timestamp will be formatted as `d:hh:mm:ss.ms`, specifying
169
+ * the most significant unit based on the total time, and the least significant unit with
170
+ * `unit`. See `parseTimeUnit()` for recognized time units.
171
+ */
172
+ export function getTimestamp(time, total, unit) {
173
+ const timeUnit = parseTimeUnit(unit);
174
+ if (timeUnit === undefined) {
175
+ return `${formatNumber(time)} / ${formatNumber(total)} ${unit}`;
176
+ }
177
+ const timeMs = timeToMilliseconds(time, timeUnit);
178
+ const totalMs = timeToMilliseconds(total, timeUnit);
179
+
180
+ // Toggle each unit based on the total time and the provided timeUnit.
181
+ // Exploit an enum property where TimeUnit.Milliseconds < TimeUnit.Second < TimeUnit.Minute ... etc.
182
+ const options = {
183
+ useMs: timeUnit == TimeUnit.MILLISECOND,
184
+ useSec: timeUnit == TimeUnit.SECOND || timeUnit <= TimeUnit.SECOND && totalMs >= SECONDS_IN_MS,
185
+ useMin: timeUnit == TimeUnit.MINUTE || timeUnit <= TimeUnit.MINUTE && totalMs >= MINUTES_IN_MS,
186
+ useHours: timeUnit == TimeUnit.HOUR || timeUnit <= TimeUnit.HOUR && totalMs >= HOURS_IN_MS,
187
+ useDays: timeUnit == TimeUnit.DAY || timeUnit <= TimeUnit.DAY && totalMs >= DAYS_IN_MS
188
+ };
189
+ const {
190
+ timestamp,
191
+ units
192
+ } = formatTimestamp(timeMs, options);
193
+ const {
194
+ timestamp: totalTimestamp
195
+ } = formatTimestamp(totalMs, options);
196
+ return `${timestamp} / ${totalTimestamp} ${units}`;
197
+ }
198
+
199
+ /**
200
+ * Constrains the `src` vector relative to the `target` so it only has freedom along the
201
+ * specified `axis`. Does nothing if `axis = Axis.NONE`.
202
+ *
203
+ * @example
204
+ * ```
205
+ * const src = [1, 2, 3];
206
+ * const target = [4, 5, 6];
207
+ * const constrained = constrainToAxis(src, target, Axis.X);
208
+ * console.log(constrained); // [1, 5, 6]
209
+ * ```
210
+ */
211
+ export function constrainToAxis(src, target, axis) {
212
+ switch (axis) {
213
+ case Axis.X:
214
+ return [src[0], target[1], target[2]];
215
+ case Axis.Y:
216
+ return [target[0], src[1], target[2]];
217
+ case Axis.Z:
218
+ return [target[0], target[1], src[2]];
219
+ default:
220
+ return [...src];
221
+ }
222
+ }
223
+ export function getDataRange(data) {
224
+ let min = data[0];
225
+ let max = data[0];
226
+ for (let i = 1; i < data.length; i++) {
227
+ min = Math.min(min, data[i]);
228
+ max = Math.max(max, data[i]);
229
+ }
230
+ return [min, max];
231
+ }
@@ -0,0 +1,153 @@
1
+ import { fromUrl } from "geotiff";
2
+ import { serializeError } from "serialize-error";
3
+ import { VolumeLoadError, VolumeLoadErrorType } from "../loaders/VolumeLoadError.js";
4
+ // from TIFF
5
+ const SAMPLEFORMAT_UINT = 1;
6
+ const SAMPLEFORMAT_INT = 2;
7
+ const SAMPLEFORMAT_IEEEFP = 3;
8
+ function getDtype(sampleFormat, bytesPerPixel) {
9
+ if (sampleFormat === SAMPLEFORMAT_IEEEFP) {
10
+ if (bytesPerPixel === 4) {
11
+ return "float32";
12
+ }
13
+ } else if (sampleFormat === SAMPLEFORMAT_INT) {
14
+ if (bytesPerPixel === 1) {
15
+ return "int8";
16
+ } else if (bytesPerPixel === 2) {
17
+ return "int16";
18
+ } else if (bytesPerPixel === 4) {
19
+ return "int32";
20
+ }
21
+ } else if (sampleFormat === SAMPLEFORMAT_UINT) {
22
+ if (bytesPerPixel === 1) {
23
+ return "uint8";
24
+ } else if (bytesPerPixel === 2) {
25
+ return "uint16";
26
+ } else if (bytesPerPixel === 4) {
27
+ return "uint32";
28
+ }
29
+ }
30
+ console.error(`TIFF Worker: unsupported sample format ${sampleFormat} and bytes per pixel ${bytesPerPixel}`);
31
+ return "uint8";
32
+ }
33
+ function castToArray(buf, bytesPerPixel, sampleFormat) {
34
+ if (sampleFormat === SAMPLEFORMAT_IEEEFP) {
35
+ if (bytesPerPixel === 4) {
36
+ return new Float32Array(buf);
37
+ }
38
+ } else if (sampleFormat === SAMPLEFORMAT_INT) {
39
+ if (bytesPerPixel === 1) {
40
+ return new Int8Array(buf);
41
+ } else if (bytesPerPixel === 2) {
42
+ return new Int16Array(buf);
43
+ } else if (bytesPerPixel === 4) {
44
+ return new Int32Array(buf);
45
+ }
46
+ } else if (sampleFormat === SAMPLEFORMAT_UINT) {
47
+ if (bytesPerPixel === 1) {
48
+ return new Uint8Array(buf);
49
+ } else if (bytesPerPixel === 2) {
50
+ return new Uint16Array(buf);
51
+ } else if (bytesPerPixel === 4) {
52
+ return new Uint32Array(buf);
53
+ }
54
+ }
55
+ console.error(`TIFF Worker: unsupported sample format ${sampleFormat} and bytes per pixel ${bytesPerPixel}`);
56
+ return new Uint8Array(buf);
57
+ }
58
+ async function loadTiffChannel(e) {
59
+ // TODO index images by time
60
+ // const time = e.data.time;
61
+
62
+ const channelIndex = e.data.channel;
63
+ const tilesizex = e.data.tilesizex;
64
+ const tilesizey = e.data.tilesizey;
65
+ const sizez = e.data.sizez;
66
+ const sizec = e.data.sizec;
67
+ const dimensionOrder = e.data.dimensionOrder;
68
+ const bytesPerSample = e.data.bytesPerSample;
69
+ const tiff = await fromUrl(e.data.url, {
70
+ allowFullFile: true
71
+ });
72
+
73
+ // load the images of this channel from the tiff
74
+ // today assume TCZYX so the slices are already in order.
75
+ let startindex = 0;
76
+ let incrementz = 1;
77
+ if (dimensionOrder === "XYZCT") {
78
+ // we have XYZCT which is the "good" case
79
+ // TCZYX
80
+ startindex = sizez * channelIndex;
81
+ incrementz = 1;
82
+ } else if (dimensionOrder === "XYCZT") {
83
+ // we have to loop differently to increment channels
84
+ // TZCYX
85
+ startindex = channelIndex;
86
+ incrementz = sizec;
87
+ }
88
+
89
+ // huge assumption: planes are in a particular order relative to z and c
90
+
91
+ // get first plane, to get some details about the image
92
+ const image = await tiff.getImage(startindex);
93
+ // on first image, set up some stuff:
94
+ const sampleFormat = image.getSampleFormat();
95
+ const bytesPerPixel = image.getBytesPerPixel();
96
+ // allocate a buffer for one channel
97
+ const buffer = new ArrayBuffer(tilesizex * tilesizey * sizez * bytesPerPixel);
98
+ const u8 = new Uint8Array(buffer);
99
+ for (let imageIndex = startindex, zslice = 0; zslice < sizez; imageIndex += incrementz, ++zslice) {
100
+ const image = await tiff.getImage(imageIndex);
101
+ // download and downsample on client
102
+ const result = await image.readRasters({
103
+ width: tilesizex,
104
+ height: tilesizey
105
+ });
106
+ const arrayresult = Array.isArray(result) ? result[0] : result;
107
+ // deposit in full channel array in the right place
108
+ const offset = zslice * tilesizex * tilesizey;
109
+ if (arrayresult.BYTES_PER_ELEMENT > 4) {
110
+ throw new VolumeLoadError("byte size not supported yet: " + arrayresult.BYTES_PER_ELEMENT, {
111
+ type: VolumeLoadErrorType.INVALID_METADATA
112
+ });
113
+ } else if (arrayresult.BYTES_PER_ELEMENT !== bytesPerSample) {
114
+ throw new VolumeLoadError("tiff bytes per element mismatch with OME metadata", {
115
+ type: VolumeLoadErrorType.INVALID_METADATA
116
+ });
117
+ } else {
118
+ u8.set(new Uint8Array(arrayresult.buffer), offset * arrayresult.BYTES_PER_ELEMENT);
119
+ }
120
+ }
121
+ // all slices collected, now resample to 8 bits full data range
122
+ const src = castToArray(buffer, bytesPerPixel, sampleFormat);
123
+ const dtype = getDtype(sampleFormat, bytesPerPixel);
124
+ let chmin = src[0];
125
+ let chmax = src[0];
126
+ for (let j = 0; j < src.length; ++j) {
127
+ const val = src[j];
128
+ if (val < chmin) {
129
+ chmin = val;
130
+ }
131
+ if (val > chmax) {
132
+ chmax = val;
133
+ }
134
+ }
135
+ return {
136
+ data: src,
137
+ channel: channelIndex,
138
+ range: [chmin, chmax],
139
+ dtype: dtype,
140
+ isError: false
141
+ };
142
+ }
143
+ self.onmessage = async e => {
144
+ try {
145
+ const result = await loadTiffChannel(e);
146
+ self.postMessage(result, [result.data.buffer]);
147
+ } catch (err) {
148
+ self.postMessage({
149
+ isError: true,
150
+ error: serializeError(err)
151
+ });
152
+ }
153
+ };
@@ -0,0 +1,129 @@
1
+ import { serializeError } from "serialize-error";
2
+ import VolumeCache from "../VolumeCache.js";
3
+ import { VolumeFileFormat, createVolumeLoader, pathToFileType } from "../loaders/index.js";
4
+ import { VolumeLoadError } from "../loaders/VolumeLoadError.js";
5
+ import RequestQueue from "../utils/RequestQueue.js";
6
+ import SubscribableRequestQueue from "../utils/SubscribableRequestQueue.js";
7
+ import { WorkerEventType, WorkerMsgType, WorkerResponseResult } from "./types.js";
8
+ import { rebuildLoadSpec } from "./util.js";
9
+ let cache = undefined;
10
+ let queue = undefined;
11
+ let subscribableQueue = undefined;
12
+ let loader = undefined;
13
+ let initialized = false;
14
+ let copyOnLoad = false;
15
+ const messageHandlers = {
16
+ [WorkerMsgType.INIT]: ({
17
+ maxCacheSize,
18
+ maxActiveRequests,
19
+ maxLowPriorityRequests
20
+ }) => {
21
+ if (!initialized) {
22
+ cache = new VolumeCache(maxCacheSize);
23
+ queue = new RequestQueue(maxActiveRequests, maxLowPriorityRequests);
24
+ subscribableQueue = new SubscribableRequestQueue(queue);
25
+ initialized = true;
26
+ }
27
+ return Promise.resolve();
28
+ },
29
+ [WorkerMsgType.CREATE_LOADER]: async ({
30
+ path,
31
+ options
32
+ }) => {
33
+ const pathString = Array.isArray(path) ? path[0] : path;
34
+ const fileType = options?.fileType || pathToFileType(pathString);
35
+ copyOnLoad = fileType === VolumeFileFormat.JSON;
36
+ loader = await createVolumeLoader(path, {
37
+ ...options,
38
+ cache,
39
+ queue: subscribableQueue
40
+ });
41
+ return loader !== undefined;
42
+ },
43
+ [WorkerMsgType.CREATE_VOLUME]: async loadSpec => {
44
+ if (loader === undefined) {
45
+ throw new VolumeLoadError("No loader created");
46
+ }
47
+ return await loader.createImageInfo(rebuildLoadSpec(loadSpec));
48
+ },
49
+ [WorkerMsgType.LOAD_DIMS]: async loadSpec => {
50
+ if (loader === undefined) {
51
+ throw new VolumeLoadError("No loader created");
52
+ }
53
+ return await loader.loadDims(rebuildLoadSpec(loadSpec));
54
+ },
55
+ [WorkerMsgType.LOAD_VOLUME_DATA]: ({
56
+ imageInfo,
57
+ loadSpec,
58
+ loaderId,
59
+ loadId
60
+ }) => {
61
+ if (loader === undefined) {
62
+ throw new VolumeLoadError("No loader created");
63
+ }
64
+ return loader.loadRawChannelData(imageInfo, rebuildLoadSpec(loadSpec), (imageInfo, loadSpec) => {
65
+ const message = {
66
+ responseResult: WorkerResponseResult.EVENT,
67
+ eventType: WorkerEventType.METADATA_UPDATE,
68
+ loaderId,
69
+ loadId,
70
+ imageInfo,
71
+ loadSpec
72
+ };
73
+ self.postMessage(message);
74
+ }, (channelIndex, dtype, data, ranges, atlasDims) => {
75
+ const message = {
76
+ responseResult: WorkerResponseResult.EVENT,
77
+ eventType: WorkerEventType.CHANNEL_LOAD,
78
+ loaderId,
79
+ loadId,
80
+ channelIndex,
81
+ dtype,
82
+ data,
83
+ ranges,
84
+ atlasDims
85
+ };
86
+ self.postMessage(message, copyOnLoad ? [] : data.map(d => d.buffer));
87
+ });
88
+ },
89
+ [WorkerMsgType.SET_PREFETCH_PRIORITY_DIRECTIONS]: directions => {
90
+ // Silently does nothing if the loader isn't an `OMEZarrLoader`
91
+ loader?.setPrefetchPriority(directions);
92
+ return Promise.resolve();
93
+ },
94
+ [WorkerMsgType.SYNCHRONIZE_MULTICHANNEL_LOADING]: syncChannels => {
95
+ loader?.syncMultichannelLoading(syncChannels);
96
+ return Promise.resolve();
97
+ },
98
+ [WorkerMsgType.UPDATE_FETCH_OPTIONS]: fetchOptions => {
99
+ loader?.updateFetchOptions(fetchOptions);
100
+ return Promise.resolve();
101
+ }
102
+ };
103
+ self.onmessage = async ({
104
+ data
105
+ }) => {
106
+ const {
107
+ msgId,
108
+ type,
109
+ payload
110
+ } = data;
111
+ let message;
112
+ try {
113
+ const response = await messageHandlers[type](payload);
114
+ message = {
115
+ responseResult: WorkerResponseResult.SUCCESS,
116
+ msgId,
117
+ type,
118
+ payload: response
119
+ };
120
+ } catch (e) {
121
+ message = {
122
+ responseResult: WorkerResponseResult.ERROR,
123
+ msgId,
124
+ type,
125
+ payload: serializeError(e)
126
+ };
127
+ }
128
+ self.postMessage(message);
129
+ };