@fideus-labs/ngff-zarr 0.1.0 → 0.2.1

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 (70) hide show
  1. package/README.md +1 -0
  2. package/esm/browser-mod.d.ts +14 -0
  3. package/esm/browser-mod.d.ts.map +1 -0
  4. package/esm/browser-mod.js +23 -0
  5. package/esm/io/itk_image_to_ngff_image.d.ts +5 -0
  6. package/esm/io/itk_image_to_ngff_image.d.ts.map +1 -1
  7. package/esm/io/itk_image_to_ngff_image.js +20 -20
  8. package/esm/io/ngff_image_to_itk_image.d.ts.map +1 -1
  9. package/esm/io/ngff_image_to_itk_image.js +2 -0
  10. package/esm/io/to_multiscales.js +1 -1
  11. package/esm/io/to_ngff_zarr.js +16 -0
  12. package/esm/methods/itkwasm-browser.d.ts +6 -0
  13. package/esm/methods/itkwasm-browser.d.ts.map +1 -0
  14. package/esm/methods/itkwasm-browser.js +462 -0
  15. package/esm/methods/itkwasm-node.d.ts +6 -0
  16. package/esm/methods/itkwasm-node.d.ts.map +1 -0
  17. package/esm/methods/itkwasm-node.js +462 -0
  18. package/esm/methods/itkwasm-shared.d.ts +68 -0
  19. package/esm/methods/itkwasm-shared.d.ts.map +1 -0
  20. package/esm/methods/itkwasm-shared.js +489 -0
  21. package/esm/methods/itkwasm.d.ts +11 -3
  22. package/esm/methods/itkwasm.d.ts.map +1 -1
  23. package/esm/methods/itkwasm.js +11 -810
  24. package/esm/schemas/coordinate_systems.d.ts +159 -552
  25. package/esm/schemas/coordinate_systems.d.ts.map +1 -1
  26. package/esm/schemas/coordinate_systems.js +0 -1
  27. package/esm/schemas/ome_zarr.d.ts +105 -69
  28. package/esm/schemas/ome_zarr.d.ts.map +1 -1
  29. package/esm/schemas/rfc4.d.ts +26 -131
  30. package/esm/schemas/rfc4.d.ts.map +1 -1
  31. package/esm/schemas/units.d.ts +70 -5
  32. package/esm/schemas/units.d.ts.map +1 -1
  33. package/esm/schemas/units.js +2 -15
  34. package/esm/schemas/zarr_metadata.d.ts +13 -300
  35. package/esm/schemas/zarr_metadata.d.ts.map +1 -1
  36. package/package.json +27 -3
  37. package/script/browser-mod.d.ts +14 -0
  38. package/script/browser-mod.d.ts.map +1 -0
  39. package/script/browser-mod.js +48 -0
  40. package/script/io/itk_image_to_ngff_image.d.ts +5 -0
  41. package/script/io/itk_image_to_ngff_image.d.ts.map +1 -1
  42. package/script/io/itk_image_to_ngff_image.js +20 -20
  43. package/script/io/ngff_image_to_itk_image.d.ts.map +1 -1
  44. package/script/io/ngff_image_to_itk_image.js +2 -0
  45. package/script/io/to_multiscales.js +1 -1
  46. package/script/io/to_ngff_zarr.js +16 -0
  47. package/script/methods/itkwasm-browser.d.ts +6 -0
  48. package/script/methods/itkwasm-browser.d.ts.map +1 -0
  49. package/script/methods/itkwasm-browser.js +488 -0
  50. package/script/methods/itkwasm-node.d.ts +6 -0
  51. package/script/methods/itkwasm-node.d.ts.map +1 -0
  52. package/script/methods/itkwasm-node.js +488 -0
  53. package/script/methods/itkwasm-shared.d.ts +68 -0
  54. package/script/methods/itkwasm-shared.d.ts.map +1 -0
  55. package/script/methods/itkwasm-shared.js +524 -0
  56. package/script/methods/itkwasm.d.ts +11 -3
  57. package/script/methods/itkwasm.d.ts.map +1 -1
  58. package/script/methods/itkwasm.js +14 -835
  59. package/script/schemas/coordinate_systems.d.ts +159 -552
  60. package/script/schemas/coordinate_systems.d.ts.map +1 -1
  61. package/script/schemas/coordinate_systems.js +0 -1
  62. package/script/schemas/ome_zarr.d.ts +105 -69
  63. package/script/schemas/ome_zarr.d.ts.map +1 -1
  64. package/script/schemas/rfc4.d.ts +26 -131
  65. package/script/schemas/rfc4.d.ts.map +1 -1
  66. package/script/schemas/units.d.ts +70 -5
  67. package/script/schemas/units.d.ts.map +1 -1
  68. package/script/schemas/units.js +2 -15
  69. package/script/schemas/zarr_metadata.d.ts +13 -300
  70. package/script/schemas/zarr_metadata.d.ts.map +1 -1
@@ -1,842 +1,21 @@
1
1
  "use strict";
2
2
  // SPDX-FileCopyrightText: Copyright (c) Fideus Labs LLC
3
3
  // SPDX-License-Identifier: MIT
4
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
5
- if (k2 === undefined) k2 = k;
6
- var desc = Object.getOwnPropertyDescriptor(m, k);
7
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
8
- desc = { enumerable: true, get: function() { return m[k]; } };
9
- }
10
- Object.defineProperty(o, k2, desc);
11
- }) : (function(o, m, k, k2) {
12
- if (k2 === undefined) k2 = k;
13
- o[k2] = m[k];
14
- }));
15
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
16
- Object.defineProperty(o, "default", { enumerable: true, value: v });
17
- }) : function(o, v) {
18
- o["default"] = v;
19
- });
20
- var __importStar = (this && this.__importStar) || function (mod) {
21
- if (mod && mod.__esModule) return mod;
22
- var result = {};
23
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
24
- __setModuleDefault(result, mod);
25
- return result;
26
- };
27
4
  Object.defineProperty(exports, "__esModule", { value: true });
28
- exports.downsampleItkWasm = downsampleItkWasm;
29
- const downsample_1 = require("@itk-wasm/downsample");
30
- const zarr = __importStar(require("zarrita"));
31
- const ngff_image_js_1 = require("../types/ngff_image.js");
32
- const SPATIAL_DIMS = ["x", "y", "z"];
5
+ exports.downsampleItkWasm = void 0;
33
6
  /**
34
- * Convert dimension scale factors to ITK-Wasm format
35
- */
36
- function dimScaleFactors(dims, scaleFactor, previousDimFactors) {
37
- const dimFactors = {};
38
- if (typeof scaleFactor === "number") {
39
- for (const dim of dims) {
40
- if (SPATIAL_DIMS.includes(dim)) {
41
- dimFactors[dim] = scaleFactor;
42
- }
43
- else {
44
- dimFactors[dim] = previousDimFactors[dim] || 1;
45
- }
46
- }
47
- }
48
- else {
49
- for (const dim of dims) {
50
- if (dim in scaleFactor) {
51
- dimFactors[dim] = scaleFactor[dim];
52
- }
53
- else {
54
- dimFactors[dim] = previousDimFactors[dim] || 1;
55
- }
56
- }
57
- }
58
- return dimFactors;
59
- }
60
- /**
61
- * Update previous dimension factors
62
- */
63
- function updatePreviousDimFactors(scaleFactor, spatialDims, previousDimFactors) {
64
- const updated = { ...previousDimFactors };
65
- if (typeof scaleFactor === "number") {
66
- for (const dim of spatialDims) {
67
- updated[dim] = scaleFactor;
68
- }
69
- }
70
- else {
71
- for (const dim of spatialDims) {
72
- if (dim in scaleFactor) {
73
- updated[dim] = scaleFactor[dim];
74
- }
75
- }
76
- }
77
- return updated;
78
- }
79
- /**
80
- * Compute next scale metadata
81
- */
82
- function nextScaleMetadata(image, dimFactors, spatialDims) {
83
- const translation = {};
84
- const scale = {};
85
- for (const dim of image.dims) {
86
- if (spatialDims.includes(dim)) {
87
- const factor = dimFactors[dim];
88
- scale[dim] = image.scale[dim] * factor;
89
- translation[dim] = image.translation[dim] +
90
- 0.5 * (factor - 1) * image.scale[dim];
91
- }
92
- else {
93
- // Only copy non-spatial dimensions if they exist in the source
94
- if (dim in image.scale) {
95
- scale[dim] = image.scale[dim];
96
- }
97
- if (dim in image.translation) {
98
- translation[dim] = image.translation[dim];
99
- }
100
- }
101
- }
102
- return [translation, scale];
103
- }
104
- /**
105
- * Compute Gaussian kernel sigma values in pixel units for downsampling.
106
- *
107
- * Formula: sigma = sqrt((k^2 - 1^2)/(2*sqrt(2*ln(2)))^2)
7
+ * ITK-Wasm downsampling support for multiscale generation
108
8
  *
109
- * Reference:
110
- * - https://discourse.itk.org/t/resampling-to-isotropic-signal-processing-theory/1403/16
111
- * - https://doi.org/10.1007/978-3-319-24571-3_81
112
- * - http://discovery.ucl.ac.uk/1469251/1/scale-factor-point-5.pdf
9
+ * This module provides conditional exports for browser and Node environments.
10
+ * The actual implementation is delegated to environment-specific modules:
11
+ * - itkwasm-browser.ts: Uses WebWorker-based functions for browser environments
12
+ * - itkwasm-node.ts: Uses native WASM functions for Node/Deno environments
113
13
  *
114
- * @param shrinkFactors - Shrink ratio along each axis
115
- * @returns Standard deviation of Gaussian kernel along each axis
116
- */
117
- function computeSigma(shrinkFactors) {
118
- const denominator = Math.pow(2 * Math.sqrt(2 * Math.log(2)), 2);
119
- return shrinkFactors.map((factor) => Math.sqrt((factor * factor - 1) / denominator));
120
- }
121
- /**
122
- * Convert zarr array to ITK-Wasm Image format
123
- * If isVector is true, ensures "c" dimension is last by transposing if needed
124
- */
125
- async function zarrToItkImage(array, dims, isVector = false) {
126
- // Read the full array data
127
- const result = await zarr.get(array);
128
- // Ensure we have the data
129
- if (!result.data || result.data.length === 0) {
130
- throw new Error("Zarr array data is empty");
131
- }
132
- let data;
133
- let shape = result.shape;
134
- let _finalDims = dims;
135
- // If vector image, ensure "c" is last dimension
136
- if (isVector) {
137
- const cIndex = dims.indexOf("c");
138
- if (cIndex !== -1 && cIndex !== dims.length - 1) {
139
- // Need to transpose to move "c" to the end
140
- const permutation = dims.map((_, i) => i).filter((i) => i !== cIndex);
141
- permutation.push(cIndex);
142
- // Reorder dims
143
- _finalDims = permutation.map((i) => dims[i]);
144
- // Reorder shape
145
- shape = permutation.map((i) => result.shape[i]);
146
- // Transpose the data
147
- data = transposeArray(result.data, result.shape, permutation, getItkComponentType(result.data));
148
- }
149
- else {
150
- // "c" already at end or not present, just copy data
151
- data = copyTypedArray(result.data);
152
- }
153
- }
154
- else {
155
- // Not a vector image, just copy data
156
- data = copyTypedArray(result.data);
157
- }
158
- // For vector images, the last dimension is the component count, not a spatial dimension
159
- const spatialShape = isVector ? shape.slice(0, -1) : shape;
160
- const components = isVector ? shape[shape.length - 1] : 1;
161
- // Create ITK-Wasm image
162
- const itkImage = {
163
- imageType: {
164
- dimension: spatialShape.length,
165
- componentType: getItkComponentType(data),
166
- pixelType: isVector ? "VariableLengthVector" : "Scalar",
167
- components,
168
- },
169
- name: "image",
170
- origin: spatialShape.map(() => 0),
171
- spacing: spatialShape.map(() => 1),
172
- direction: createIdentityMatrix(spatialShape.length),
173
- size: spatialShape,
174
- data,
175
- metadata: new Map(),
176
- };
177
- return itkImage;
178
- }
179
- /**
180
- * Copy typed array to appropriate type
181
- */
182
- function copyTypedArray(data) {
183
- if (data instanceof Float32Array) {
184
- return new Float32Array(data);
185
- }
186
- else if (data instanceof Float64Array) {
187
- return new Float64Array(data);
188
- }
189
- else if (data instanceof Uint8Array) {
190
- return new Uint8Array(data);
191
- }
192
- else if (data instanceof Int8Array) {
193
- return new Int8Array(data);
194
- }
195
- else if (data instanceof Uint16Array) {
196
- return new Uint16Array(data);
197
- }
198
- else if (data instanceof Int16Array) {
199
- return new Int16Array(data);
200
- }
201
- else if (data instanceof Uint32Array) {
202
- return new Uint32Array(data);
203
- }
204
- else if (data instanceof Int32Array) {
205
- return new Int32Array(data);
206
- }
207
- else if (data instanceof BigInt64Array) {
208
- return new BigInt64Array(data);
209
- }
210
- else if (data instanceof BigUint64Array) {
211
- return new BigUint64Array(data);
212
- }
213
- else {
214
- // Convert to Float32Array as fallback
215
- return new Float32Array(data);
216
- }
217
- }
218
- /**
219
- * Transpose array data according to permutation
220
- */
221
- function transposeArray(data, shape, permutation, componentType) {
222
- const typedData = data;
223
- // Create output array of same type
224
- let output;
225
- const totalSize = typedData.length;
226
- switch (componentType) {
227
- case "uint8":
228
- output = new Uint8Array(totalSize);
229
- break;
230
- case "int8":
231
- output = new Int8Array(totalSize);
232
- break;
233
- case "int16":
234
- output = new Int16Array(totalSize);
235
- break;
236
- case "uint16":
237
- output = new Uint16Array(totalSize);
238
- break;
239
- case "int32":
240
- output = new Int32Array(totalSize);
241
- break;
242
- case "uint32":
243
- output = new Uint32Array(totalSize);
244
- break;
245
- case "int64":
246
- output = new BigInt64Array(totalSize);
247
- break;
248
- case "uint64":
249
- output = new BigUint64Array(totalSize);
250
- break;
251
- case "float64":
252
- output = new Float64Array(totalSize);
253
- break;
254
- case "float32":
255
- default:
256
- output = new Float32Array(totalSize);
257
- break;
258
- }
259
- // Calculate strides for source
260
- const sourceStride = calculateStride(shape);
261
- // Calculate new shape after permutation
262
- const newShape = permutation.map((i) => shape[i]);
263
- const targetStride = calculateStride(newShape);
264
- // Perform transpose
265
- const indices = new Array(shape.length).fill(0);
266
- for (let i = 0; i < totalSize; i++) {
267
- // Calculate source index from multi-dimensional indices
268
- let sourceIdx = 0;
269
- for (let j = 0; j < shape.length; j++) {
270
- sourceIdx += indices[j] * sourceStride[j];
271
- }
272
- // Calculate target index with permuted dimensions
273
- let targetIdx = 0;
274
- for (let j = 0; j < permutation.length; j++) {
275
- targetIdx += indices[permutation[j]] * targetStride[j];
276
- }
277
- output[targetIdx] = typedData[sourceIdx];
278
- // Increment indices
279
- for (let j = shape.length - 1; j >= 0; j--) {
280
- indices[j]++;
281
- if (indices[j] < shape[j])
282
- break;
283
- indices[j] = 0;
284
- }
285
- }
286
- return output;
287
- }
288
- /**
289
- * Get ITK component type from typed array
290
- */
291
- function getItkComponentType(data) {
292
- if (data instanceof Uint8Array)
293
- return "uint8";
294
- if (data instanceof Int8Array)
295
- return "int8";
296
- if (data instanceof Uint16Array)
297
- return "uint16";
298
- if (data instanceof Int16Array)
299
- return "int16";
300
- if (data instanceof Uint32Array)
301
- return "uint32";
302
- if (data instanceof Int32Array)
303
- return "int32";
304
- if (data instanceof BigUint64Array)
305
- return "uint64";
306
- if (data instanceof BigInt64Array)
307
- return "int64";
308
- if (data instanceof Float64Array)
309
- return "float64";
310
- return "float32";
311
- }
312
- /**
313
- * Create identity matrix for ITK direction
314
- */
315
- function createIdentityMatrix(dimension) {
316
- const matrix = new Float64Array(dimension * dimension);
317
- for (let i = 0; i < dimension * dimension; i++) {
318
- matrix[i] = i % (dimension + 1) === 0 ? 1 : 0;
319
- }
320
- return matrix;
321
- }
322
- /**
323
- * Convert ITK-Wasm Image back to zarr array
324
- */
325
- async function itkImageToZarr(itkImage, path, chunkShape) {
326
- // Use in-memory store
327
- const store = new Map();
328
- const root = zarr.root(store);
329
- // Determine data type
330
- let dataType;
331
- if (itkImage.data instanceof Uint8Array) {
332
- dataType = "uint8";
333
- }
334
- else if (itkImage.data instanceof Int8Array) {
335
- dataType = "int8";
336
- }
337
- else if (itkImage.data instanceof Int16Array) {
338
- dataType = "int16";
339
- }
340
- else if (itkImage.data instanceof Uint16Array) {
341
- dataType = "uint16";
342
- }
343
- else if (itkImage.data instanceof Int32Array) {
344
- dataType = "int32";
345
- }
346
- else if (itkImage.data instanceof Uint32Array) {
347
- dataType = "uint32";
348
- }
349
- else if (itkImage.data instanceof BigInt64Array) {
350
- dataType = "int64";
351
- }
352
- else if (itkImage.data instanceof BigUint64Array) {
353
- dataType = "uint64";
354
- }
355
- else if (itkImage.data instanceof Float64Array) {
356
- dataType = "float64";
357
- }
358
- else if (itkImage.data instanceof Float32Array) {
359
- dataType = "float32";
360
- }
361
- else {
362
- dataType = "float32";
363
- }
364
- const array = await zarr.create(root.resolve(path), {
365
- shape: itkImage.size,
366
- chunk_shape: chunkShape,
367
- data_type: dataType,
368
- fill_value: 0,
369
- });
370
- // Write data
371
- await zarr.set(array, [], {
372
- data: itkImage.data,
373
- shape: itkImage.size,
374
- stride: calculateStride(itkImage.size),
375
- });
376
- return array;
377
- }
378
- /**
379
- * Calculate stride for array
380
- */
381
- function calculateStride(shape) {
382
- const stride = new Array(shape.length);
383
- stride[shape.length - 1] = 1;
384
- for (let i = shape.length - 2; i >= 0; i--) {
385
- stride[i] = stride[i + 1] * shape[i + 1];
386
- }
387
- return stride;
388
- }
389
- /**
390
- * Process channel-first data by downsampling each channel separately
391
- */
392
- async function downsampleChannelFirst(image, dimFactors, spatialDims, smoothing) {
393
- // Get the channel index and count
394
- const cIndex = image.dims.indexOf("c");
395
- const result = await zarr.get(image.data);
396
- const channelCount = result.shape[cIndex];
397
- // Process each channel separately
398
- const downsampledChannels = [];
399
- for (let channelIdx = 0; channelIdx < channelCount; channelIdx++) {
400
- // Extract single channel data
401
- const channelSlice = extractChannel(result, cIndex, channelIdx);
402
- // Create temporary zarr array for this channel
403
- const store = new Map();
404
- const root = zarr.root(store);
405
- const channelDims = image.dims.filter((d) => d !== "c");
406
- const channelShape = result.shape.filter((_, i) => i !== cIndex);
407
- const chunkShape = channelShape.map((s) => Math.min(s, 256));
408
- const channelArray = await zarr.create(root.resolve("channel"), {
409
- shape: channelShape,
410
- chunk_shape: chunkShape,
411
- data_type: getItkComponentType(result.data),
412
- fill_value: 0,
413
- });
414
- await zarr.set(channelArray, [], {
415
- data: channelSlice,
416
- shape: channelShape,
417
- stride: calculateStride(channelShape),
418
- });
419
- // Create NgffImage for this channel (unused but kept for potential future use)
420
- // const _channelImage = new NgffImage({
421
- // data: channelArray,
422
- // dims: channelDims,
423
- // scale: Object.fromEntries(
424
- // Object.entries(image.scale).filter(([k]) => k !== "c")
425
- // ),
426
- // translation: Object.fromEntries(
427
- // Object.entries(image.translation).filter(([k]) => k !== "c")
428
- // ),
429
- // name: image.name,
430
- // axesUnits: image.axesUnits,
431
- // computedCallbacks: image.computedCallbacks,
432
- // });
433
- // Downsample this channel
434
- const itkImage = await zarrToItkImage(channelArray, channelDims, false);
435
- const shrinkFactors = [];
436
- for (let i = 0; i < channelDims.length; i++) {
437
- const dim = channelDims[i];
438
- if (SPATIAL_DIMS.includes(dim)) {
439
- shrinkFactors.push(dimFactors[dim] || 1);
440
- }
441
- else {
442
- shrinkFactors.push(1); // Non-spatial dimensions don't shrink
443
- }
444
- }
445
- let downsampled;
446
- if (smoothing === "gaussian") {
447
- const blockSize = itkImage.size.slice().reverse();
448
- const sigma = computeSigma(shrinkFactors);
449
- const { radius: _radius } = await (0, downsample_1.gaussianKernelRadiusNode)({
450
- size: blockSize,
451
- sigma,
452
- });
453
- const result = await (0, downsample_1.downsampleNode)(itkImage, {
454
- shrinkFactors,
455
- cropRadius: shrinkFactors.map(() => 0),
456
- });
457
- downsampled = result.downsampled;
458
- }
459
- else if (smoothing === "bin_shrink") {
460
- const result = await (0, downsample_1.downsampleBinShrinkNode)(itkImage, {
461
- shrinkFactors,
462
- });
463
- downsampled = result.downsampled;
464
- }
465
- else if (smoothing === "label_image") {
466
- const blockSize = itkImage.size.slice().reverse();
467
- const sigma = computeSigma(shrinkFactors);
468
- const { radius: _radius } = await (0, downsample_1.gaussianKernelRadiusNode)({
469
- size: blockSize,
470
- sigma,
471
- });
472
- const result = await (0, downsample_1.downsampleLabelImageNode)(itkImage, {
473
- shrinkFactors,
474
- cropRadius: shrinkFactors.map(() => 0),
475
- });
476
- downsampled = result.downsampled;
477
- }
478
- else {
479
- throw new Error(`Unknown smoothing method: ${smoothing}`);
480
- }
481
- // Convert back to zarr array
482
- const downsampledChunkShape = downsampled.size.map((s) => Math.min(s, 256));
483
- const downsampledArray = await itkImageToZarr(downsampled, "downsampled_channel", downsampledChunkShape);
484
- downsampledChannels.push(downsampledArray);
485
- }
486
- // Combine all channels back together
487
- const combinedArray = await combineChannels(downsampledChannels, cIndex, image.dims);
488
- // Compute new metadata
489
- const [translation, scale] = nextScaleMetadata(image, dimFactors, spatialDims);
490
- return new ngff_image_js_1.NgffImage({
491
- data: combinedArray,
492
- dims: image.dims,
493
- scale,
494
- translation,
495
- name: image.name,
496
- axesUnits: image.axesUnits,
497
- computedCallbacks: image.computedCallbacks,
498
- });
499
- }
500
- /**
501
- * Extract a single channel from the data
502
- */
503
- function extractChannel(result, cIndex, channelIdx) {
504
- const typedData = result.data;
505
- const shape = result.shape;
506
- // Calculate output size (all dims except channel)
507
- const outputSize = shape.reduce((acc, s, i) => (i === cIndex ? acc : acc * s), 1);
508
- let output;
509
- if (typedData instanceof Uint8Array) {
510
- output = new Uint8Array(outputSize);
511
- }
512
- else if (typedData instanceof Int8Array) {
513
- output = new Int8Array(outputSize);
514
- }
515
- else if (typedData instanceof Int16Array) {
516
- output = new Int16Array(outputSize);
517
- }
518
- else if (typedData instanceof Uint16Array) {
519
- output = new Uint16Array(outputSize);
520
- }
521
- else if (typedData instanceof Int32Array) {
522
- output = new Int32Array(outputSize);
523
- }
524
- else if (typedData instanceof Uint32Array) {
525
- output = new Uint32Array(outputSize);
526
- }
527
- else if (typedData instanceof BigInt64Array) {
528
- output = new BigInt64Array(outputSize);
529
- }
530
- else if (typedData instanceof BigUint64Array) {
531
- output = new BigUint64Array(outputSize);
532
- }
533
- else if (typedData instanceof Float64Array) {
534
- output = new Float64Array(outputSize);
535
- }
536
- else {
537
- output = new Float32Array(outputSize);
538
- }
539
- // Calculate strides
540
- const stride = calculateStride(shape);
541
- const outputShape = shape.filter((_, i) => i !== cIndex);
542
- const _outputStride = calculateStride(outputShape);
543
- // Extract channel
544
- const indices = new Array(shape.length).fill(0);
545
- let outputIdx = 0;
546
- for (let i = 0; i < outputSize; i++) {
547
- // Set channel index
548
- indices[cIndex] = channelIdx;
549
- // Calculate source index
550
- let sourceIdx = 0;
551
- for (let j = 0; j < shape.length; j++) {
552
- sourceIdx += indices[j] * stride[j];
553
- }
554
- output[outputIdx++] = typedData[sourceIdx];
555
- // Increment indices (skip channel dimension)
556
- for (let j = shape.length - 1; j >= 0; j--) {
557
- if (j === cIndex)
558
- continue;
559
- indices[j]++;
560
- if (indices[j] < shape[j])
561
- break;
562
- indices[j] = 0;
563
- }
564
- }
565
- return output;
566
- }
567
- /**
568
- * Combine multiple channel arrays back into a single multi-channel array
569
- */
570
- async function combineChannels(channels, cIndex, _originalDims) {
571
- // Read all channel data
572
- const channelData = await Promise.all(channels.map((c) => zarr.get(c)));
573
- // Determine combined shape
574
- const firstChannel = channelData[0];
575
- const channelShape = firstChannel.shape;
576
- const combinedShape = [...channelShape];
577
- combinedShape.splice(cIndex, 0, channels.length);
578
- // Create combined array
579
- const store = new Map();
580
- const root = zarr.root(store);
581
- const chunkShape = combinedShape.map((s) => Math.min(s, 256));
582
- const dataType = getItkComponentType(firstChannel.data);
583
- const combinedArray = await zarr.create(root.resolve("combined"), {
584
- shape: combinedShape,
585
- chunk_shape: chunkShape,
586
- data_type: dataType,
587
- fill_value: 0,
588
- });
589
- // Combine all channels
590
- const totalSize = combinedShape.reduce((acc, s) => acc * s, 1);
591
- let combined;
592
- if (dataType === "uint8") {
593
- combined = new Uint8Array(totalSize);
594
- }
595
- else if (dataType === "int8") {
596
- combined = new Int8Array(totalSize);
597
- }
598
- else if (dataType === "int16") {
599
- combined = new Int16Array(totalSize);
600
- }
601
- else if (dataType === "uint16") {
602
- combined = new Uint16Array(totalSize);
603
- }
604
- else if (dataType === "int32") {
605
- combined = new Int32Array(totalSize);
606
- }
607
- else if (dataType === "uint32") {
608
- combined = new Uint32Array(totalSize);
609
- }
610
- else if (dataType === "int64") {
611
- combined = new BigInt64Array(totalSize);
612
- }
613
- else if (dataType === "uint64") {
614
- combined = new BigUint64Array(totalSize);
615
- }
616
- else if (dataType === "float64") {
617
- combined = new Float64Array(totalSize);
618
- }
619
- else {
620
- combined = new Float32Array(totalSize);
621
- }
622
- const stride = calculateStride(combinedShape);
623
- const _channelStride = calculateStride(channelShape);
624
- // Copy each channel's data
625
- for (let c = 0; c < channels.length; c++) {
626
- const channelTypedData = channelData[c].data;
627
- const indices = new Array(combinedShape.length).fill(0);
628
- for (let i = 0; i < channelTypedData.length; i++) {
629
- // Set channel index
630
- indices[cIndex] = c;
631
- // Calculate target index in combined array
632
- let targetIdx = 0;
633
- for (let j = 0; j < combinedShape.length; j++) {
634
- targetIdx += indices[j] * stride[j];
635
- }
636
- combined[targetIdx] = channelTypedData[i];
637
- // Increment indices (skip channel dimension)
638
- for (let j = combinedShape.length - 1; j >= 0; j--) {
639
- if (j === cIndex)
640
- continue;
641
- indices[j]++;
642
- if (indices[j] < combinedShape[j])
643
- break;
644
- indices[j] = 0;
645
- }
646
- }
647
- }
648
- // Write combined data
649
- await zarr.set(combinedArray, [], {
650
- data: combined,
651
- shape: combinedShape,
652
- stride,
653
- });
654
- return combinedArray;
655
- }
656
- /**
657
- * Perform Gaussian downsampling using ITK-Wasm
658
- */
659
- async function downsampleGaussian(image, dimFactors, spatialDims) {
660
- const cIndex = image.dims.indexOf("c");
661
- const isVector = cIndex === image.dims.length - 1;
662
- const isChannelFirst = cIndex !== -1 && cIndex < image.dims.length - 1 &&
663
- !isVector;
664
- // If channel is first (before spatial dims), process each channel separately
665
- if (isChannelFirst) {
666
- return await downsampleChannelFirst(image, dimFactors, spatialDims, "gaussian");
667
- }
668
- // Convert to ITK-Wasm format
669
- const itkImage = await zarrToItkImage(image.data, image.dims, isVector);
670
- // Prepare shrink factors - need to be for spatial dimensions only
671
- // For vector images, the last dimension (c) is NOT a spatial dimension in the ITK image
672
- const shrinkFactors = [];
673
- const effectiveDims = isVector ? image.dims.slice(0, -1) : image.dims;
674
- for (let i = 0; i < effectiveDims.length; i++) {
675
- const dim = effectiveDims[i];
676
- if (SPATIAL_DIMS.includes(dim)) {
677
- shrinkFactors.push(dimFactors[dim] || 1);
678
- }
679
- else {
680
- shrinkFactors.push(1); // Non-spatial dimensions don't shrink
681
- }
682
- }
683
- // Compute kernel radius - sigma should also be for ALL dimensions
684
- const blockSize = itkImage.size.slice().reverse();
685
- const sigma = computeSigma(shrinkFactors);
686
- const { radius: _radius } = await (0, downsample_1.gaussianKernelRadiusNode)({
687
- size: blockSize,
688
- sigma,
689
- });
690
- // Perform downsampling
691
- const { downsampled } = await (0, downsample_1.downsampleNode)(itkImage, {
692
- shrinkFactors,
693
- cropRadius: shrinkFactors.map(() => 0),
694
- });
695
- // Compute new metadata
696
- const [translation, scale] = nextScaleMetadata(image, dimFactors, spatialDims);
697
- // Convert back to zarr array
698
- const chunkShape = downsampled.size.map((s) => Math.min(s, 256));
699
- const array = await itkImageToZarr(downsampled, "downsampled", chunkShape);
700
- return new ngff_image_js_1.NgffImage({
701
- data: array,
702
- dims: image.dims,
703
- scale,
704
- translation,
705
- name: image.name,
706
- axesUnits: image.axesUnits,
707
- computedCallbacks: image.computedCallbacks,
708
- });
709
- }
710
- /**
711
- * Perform bin shrink downsampling using ITK-Wasm
712
- */
713
- async function downsampleBinShrinkImpl(image, dimFactors, spatialDims) {
714
- const cIndex = image.dims.indexOf("c");
715
- const isVector = cIndex === image.dims.length - 1;
716
- const isChannelFirst = cIndex !== -1 && cIndex < image.dims.length - 1 &&
717
- !isVector;
718
- // If channel is first (before spatial dims), process each channel separately
719
- if (isChannelFirst) {
720
- return await downsampleChannelFirst(image, dimFactors, spatialDims, "bin_shrink");
721
- }
722
- // Convert to ITK-Wasm format
723
- const itkImage = await zarrToItkImage(image.data, image.dims, isVector);
724
- // Prepare shrink factors - need to be for spatial dimensions only
725
- // For vector images, the last dimension (c) is NOT a spatial dimension in the ITK image
726
- const shrinkFactors = [];
727
- const effectiveDims = isVector ? image.dims.slice(0, -1) : image.dims;
728
- for (let i = 0; i < effectiveDims.length; i++) {
729
- const dim = effectiveDims[i];
730
- if (SPATIAL_DIMS.includes(dim)) {
731
- shrinkFactors.push(dimFactors[dim] || 1);
732
- }
733
- else {
734
- shrinkFactors.push(1); // Non-spatial dimensions don't shrink
735
- }
736
- }
737
- // Perform downsampling
738
- const { downsampled } = await (0, downsample_1.downsampleBinShrinkNode)(itkImage, {
739
- shrinkFactors,
740
- });
741
- // Compute new metadata
742
- const [translation, scale] = nextScaleMetadata(image, dimFactors, spatialDims);
743
- // Convert back to zarr array
744
- const chunkShape = downsampled.size.map((s) => Math.min(s, 256));
745
- const array = await itkImageToZarr(downsampled, "downsampled", chunkShape);
746
- return new ngff_image_js_1.NgffImage({
747
- data: array,
748
- dims: image.dims,
749
- scale,
750
- translation,
751
- name: image.name,
752
- axesUnits: image.axesUnits,
753
- computedCallbacks: image.computedCallbacks,
754
- });
755
- }
756
- /**
757
- * Perform label image downsampling using ITK-Wasm
758
- */
759
- async function downsampleLabelImageImpl(image, dimFactors, spatialDims) {
760
- const cIndex = image.dims.indexOf("c");
761
- const isVector = cIndex === image.dims.length - 1;
762
- const isChannelFirst = cIndex !== -1 && cIndex < image.dims.length - 1 &&
763
- !isVector;
764
- // If channel is first (before spatial dims), process each channel separately
765
- if (isChannelFirst) {
766
- return await downsampleChannelFirst(image, dimFactors, spatialDims, "label_image");
767
- }
768
- // Convert to ITK-Wasm format
769
- const itkImage = await zarrToItkImage(image.data, image.dims, isVector);
770
- // Prepare shrink factors - need to be for spatial dimensions only
771
- // For vector images, the last dimension (c) is NOT a spatial dimension in the ITK image
772
- const shrinkFactors = [];
773
- const effectiveDims = isVector ? image.dims.slice(0, -1) : image.dims;
774
- for (let i = 0; i < effectiveDims.length; i++) {
775
- const dim = effectiveDims[i];
776
- if (SPATIAL_DIMS.includes(dim)) {
777
- shrinkFactors.push(dimFactors[dim] || 1);
778
- }
779
- else {
780
- shrinkFactors.push(1); // Non-spatial dimensions don't shrink
781
- }
782
- }
783
- // Compute kernel radius
784
- const blockSize = itkImage.size.slice().reverse();
785
- const sigma = computeSigma(shrinkFactors);
786
- const { radius: _radius } = await (0, downsample_1.gaussianKernelRadiusNode)({
787
- size: blockSize,
788
- sigma,
789
- });
790
- // Perform downsampling
791
- const { downsampled } = await (0, downsample_1.downsampleLabelImageNode)(itkImage, {
792
- shrinkFactors,
793
- cropRadius: shrinkFactors.map(() => 0),
794
- });
795
- // Compute new metadata
796
- const [translation, scale] = nextScaleMetadata(image, dimFactors, spatialDims);
797
- // Convert back to zarr array
798
- const chunkShape = downsampled.size.map((s) => Math.min(s, 256));
799
- const array = await itkImageToZarr(downsampled, "downsampled", chunkShape);
800
- return new ngff_image_js_1.NgffImage({
801
- data: array,
802
- dims: image.dims,
803
- scale,
804
- translation,
805
- name: image.name,
806
- axesUnits: image.axesUnits,
807
- computedCallbacks: image.computedCallbacks,
808
- });
809
- }
810
- /**
811
- * Main downsampling function for ITK-Wasm
812
- */
813
- async function downsampleItkWasm(ngffImage, scaleFactors, smoothing) {
814
- const multiscales = [ngffImage];
815
- let previousImage = ngffImage;
816
- const dims = ngffImage.dims;
817
- let previousDimFactors = {};
818
- for (const dim of dims) {
819
- previousDimFactors[dim] = 1;
820
- }
821
- const spatialDims = dims.filter((dim) => SPATIAL_DIMS.includes(dim));
822
- for (const scaleFactor of scaleFactors) {
823
- const dimFactors = dimScaleFactors(dims, scaleFactor, previousDimFactors);
824
- previousDimFactors = updatePreviousDimFactors(scaleFactor, spatialDims, previousDimFactors);
825
- let downsampled;
826
- if (smoothing === "gaussian") {
827
- downsampled = await downsampleGaussian(previousImage, dimFactors, spatialDims);
828
- }
829
- else if (smoothing === "bin_shrink") {
830
- downsampled = await downsampleBinShrinkImpl(previousImage, dimFactors, spatialDims);
831
- }
832
- else if (smoothing === "label_image") {
833
- downsampled = await downsampleLabelImageImpl(previousImage, dimFactors, spatialDims);
834
- }
835
- else {
836
- throw new Error(`Unknown smoothing method: ${smoothing}`);
837
- }
838
- multiscales.push(downsampled);
839
- previousImage = downsampled;
840
- }
841
- return multiscales;
842
- }
14
+ * For Deno runtime, we default to the node implementation.
15
+ * For browser bundlers, they should use conditional exports in package.json
16
+ * to resolve to the browser implementation.
17
+ */
18
+ // Default to Node implementation for Deno and Node.js environments
19
+ // Browser bundlers should use conditional exports to get itkwasm-browser.ts
20
+ var itkwasm_node_js_1 = require("./itkwasm-node.js");
21
+ Object.defineProperty(exports, "downsampleItkWasm", { enumerable: true, get: function () { return itkwasm_node_js_1.downsampleItkWasm; } });