@fideus-labs/ngff-zarr 0.2.0 → 0.2.2
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/esm/browser-mod.d.ts +14 -0
- package/esm/browser-mod.d.ts.map +1 -0
- package/esm/browser-mod.js +23 -0
- package/esm/methods/itkwasm-browser.d.ts +6 -0
- package/esm/methods/itkwasm-browser.d.ts.map +1 -0
- package/esm/methods/itkwasm-browser.js +462 -0
- package/esm/methods/itkwasm-node.d.ts +6 -0
- package/esm/methods/itkwasm-node.d.ts.map +1 -0
- package/esm/methods/itkwasm-node.js +462 -0
- package/esm/methods/itkwasm-shared.d.ts +68 -0
- package/esm/methods/itkwasm-shared.d.ts.map +1 -0
- package/esm/methods/itkwasm-shared.js +489 -0
- package/esm/methods/itkwasm.d.ts +11 -3
- package/esm/methods/itkwasm.d.ts.map +1 -1
- package/esm/methods/itkwasm.js +11 -952
- package/package.json +28 -3
- package/script/browser-mod.d.ts +14 -0
- package/script/browser-mod.d.ts.map +1 -0
- package/script/browser-mod.js +48 -0
- package/script/methods/itkwasm-browser.d.ts +6 -0
- package/script/methods/itkwasm-browser.d.ts.map +1 -0
- package/script/methods/itkwasm-browser.js +488 -0
- package/script/methods/itkwasm-node.d.ts +6 -0
- package/script/methods/itkwasm-node.d.ts.map +1 -0
- package/script/methods/itkwasm-node.js +488 -0
- package/script/methods/itkwasm-shared.d.ts +68 -0
- package/script/methods/itkwasm-shared.d.ts.map +1 -0
- package/script/methods/itkwasm-shared.js +524 -0
- package/script/methods/itkwasm.d.ts +11 -3
- package/script/methods/itkwasm.d.ts.map +1 -1
- package/script/methods/itkwasm.js +14 -977
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export * from "./types/units.js";
|
|
2
|
+
export * from "./types/methods.js";
|
|
3
|
+
export * from "./types/array_interface.js";
|
|
4
|
+
export * from "./types/zarr_metadata.js";
|
|
5
|
+
export * from "./types/ngff_image.js";
|
|
6
|
+
export * from "./types/multiscales.js";
|
|
7
|
+
export * from "./schemas/units.js";
|
|
8
|
+
export * from "./schemas/methods.js";
|
|
9
|
+
export * from "./schemas/zarr_metadata.js";
|
|
10
|
+
export * from "./schemas/ngff_image.js";
|
|
11
|
+
export * from "./schemas/multiscales.js";
|
|
12
|
+
export { isValidDimension, isValidUnit, validateMetadata, } from "./utils/validation.js";
|
|
13
|
+
export { createAxis, createDataset, createMetadata, createMultiscales, createNgffImage, } from "./utils/factory.js";
|
|
14
|
+
//# sourceMappingURL=browser-mod.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser-mod.d.ts","sourceRoot":"","sources":["../src/browser-mod.ts"],"names":[],"mappings":"AASA,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC;AACnC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,0BAA0B,CAAC;AACzC,cAAc,uBAAuB,CAAC;AACtC,cAAc,wBAAwB,CAAC;AAEvC,cAAc,oBAAoB,CAAC;AACnC,cAAc,sBAAsB,CAAC;AACrC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,yBAAyB,CAAC;AACxC,cAAc,0BAA0B,CAAC;AAEzC,OAAO,EACL,gBAAgB,EAChB,WAAW,EACX,gBAAgB,GACjB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,UAAU,EACV,aAAa,EACb,cAAc,EACd,iBAAiB,EACjB,eAAe,GAChB,MAAM,oBAAoB,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: Copyright (c) Fideus Labs LLC
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// Browser-compatible module exports
|
|
4
|
+
// This module excludes I/O functionality (from_ngff_zarr, to_ngff_zarr)
|
|
5
|
+
// because those modules depend on Node.js/Deno-specific filesystem APIs
|
|
6
|
+
// that are not available in browser environments.
|
|
7
|
+
//
|
|
8
|
+
// For browser use cases, the schemas, types, and validation utilities
|
|
9
|
+
// are the most commonly needed functionality.
|
|
10
|
+
export * from "./types/units.js";
|
|
11
|
+
export * from "./types/methods.js";
|
|
12
|
+
export * from "./types/array_interface.js";
|
|
13
|
+
export * from "./types/zarr_metadata.js";
|
|
14
|
+
export * from "./types/ngff_image.js";
|
|
15
|
+
export * from "./types/multiscales.js";
|
|
16
|
+
export * from "./schemas/units.js";
|
|
17
|
+
export * from "./schemas/methods.js";
|
|
18
|
+
export * from "./schemas/zarr_metadata.js";
|
|
19
|
+
export * from "./schemas/ngff_image.js";
|
|
20
|
+
export * from "./schemas/multiscales.js";
|
|
21
|
+
export { isValidDimension, isValidUnit, validateMetadata, } from "./utils/validation.js";
|
|
22
|
+
export { createAxis, createDataset, createMetadata, createMultiscales, createNgffImage, } from "./utils/factory.js";
|
|
23
|
+
// Note: Excluding I/O modules for browser compatibility
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { NgffImage } from "../types/ngff_image.js";
|
|
2
|
+
/**
|
|
3
|
+
* Main downsampling function for ITK-Wasm (browser version)
|
|
4
|
+
*/
|
|
5
|
+
export declare function downsampleItkWasm(ngffImage: NgffImage, scaleFactors: (Record<string, number> | number)[], smoothing: "gaussian" | "bin_shrink" | "label_image"): Promise<NgffImage[]>;
|
|
6
|
+
//# sourceMappingURL=itkwasm-browser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"itkwasm-browser.d.ts","sourceRoot":"","sources":["../../src/methods/itkwasm-browser.ts"],"names":[],"mappings":"AAcA,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AA2fnD;;GAEG;AACH,wBAAsB,iBAAiB,CACrC,SAAS,EAAE,SAAS,EACpB,YAAY,EAAE,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAAC,EAAE,EACjD,SAAS,EAAE,UAAU,GAAG,YAAY,GAAG,aAAa,GACnD,OAAO,CAAC,SAAS,EAAE,CAAC,CAsHtB"}
|
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: Copyright (c) Fideus Labs LLC
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
/**
|
|
4
|
+
* Browser-compatible ITK-Wasm downsampling support
|
|
5
|
+
* Uses WebWorker-based implementations from @itk-wasm/downsample
|
|
6
|
+
*/
|
|
7
|
+
import { downsample, downsampleBinShrink, downsampleLabelImage, } from "@itk-wasm/downsample";
|
|
8
|
+
import * as zarr from "zarrita";
|
|
9
|
+
import { NgffImage } from "../types/ngff_image.js";
|
|
10
|
+
import { dimScaleFactors, itkImageToZarr, nextScaleMetadata, SPATIAL_DIMS, updatePreviousDimFactors, zarrToItkImage, } from "./itkwasm-shared.js";
|
|
11
|
+
/**
|
|
12
|
+
* Perform Gaussian downsampling using ITK-Wasm (browser version)
|
|
13
|
+
*/
|
|
14
|
+
async function downsampleGaussian(image, dimFactors, spatialDims) {
|
|
15
|
+
// Handle time dimension by processing each time slice independently
|
|
16
|
+
if (image.dims.includes("t")) {
|
|
17
|
+
const tDimIndex = image.dims.indexOf("t");
|
|
18
|
+
const tSize = image.data.shape[tDimIndex];
|
|
19
|
+
const newDims = image.dims.filter((dim) => dim !== "t");
|
|
20
|
+
// Downsample each time slice
|
|
21
|
+
const downsampledSlices = [];
|
|
22
|
+
for (let t = 0; t < tSize; t++) {
|
|
23
|
+
// Extract time slice
|
|
24
|
+
const selection = new Array(image.data.shape.length).fill(null);
|
|
25
|
+
selection[tDimIndex] = t;
|
|
26
|
+
const sliceData = await zarr.get(image.data, selection);
|
|
27
|
+
// Create temporary zarr array for this slice
|
|
28
|
+
const sliceStore = new Map();
|
|
29
|
+
const sliceRoot = zarr.root(sliceStore);
|
|
30
|
+
const sliceShape = image.data.shape.filter((_, i) => i !== tDimIndex);
|
|
31
|
+
const sliceChunkShape = sliceShape.map((s) => Math.min(s, 256));
|
|
32
|
+
const sliceArray = await zarr.create(sliceRoot.resolve("slice"), {
|
|
33
|
+
shape: sliceShape,
|
|
34
|
+
chunk_shape: sliceChunkShape,
|
|
35
|
+
data_type: image.data.dtype,
|
|
36
|
+
fill_value: 0,
|
|
37
|
+
});
|
|
38
|
+
const fullSelection = new Array(sliceShape.length).fill(null);
|
|
39
|
+
await zarr.set(sliceArray, fullSelection, sliceData);
|
|
40
|
+
// Create NgffImage for this slice (without 't' dimension)
|
|
41
|
+
const sliceImage = new NgffImage({
|
|
42
|
+
data: sliceArray,
|
|
43
|
+
dims: newDims,
|
|
44
|
+
scale: Object.fromEntries(Object.entries(image.scale).filter(([dim]) => dim !== "t")),
|
|
45
|
+
translation: Object.fromEntries(Object.entries(image.translation).filter(([dim]) => dim !== "t")),
|
|
46
|
+
name: image.name,
|
|
47
|
+
axesUnits: image.axesUnits
|
|
48
|
+
? Object.fromEntries(Object.entries(image.axesUnits).filter(([dim]) => dim !== "t"))
|
|
49
|
+
: undefined,
|
|
50
|
+
computedCallbacks: image.computedCallbacks,
|
|
51
|
+
});
|
|
52
|
+
// Recursively downsample this slice (without 't', so no infinite loop)
|
|
53
|
+
const downsampledSlice = await downsampleGaussian(sliceImage, dimFactors, spatialDims);
|
|
54
|
+
downsampledSlices.push(downsampledSlice.data);
|
|
55
|
+
}
|
|
56
|
+
// Combine downsampled slices back into a single array with 't' dimension
|
|
57
|
+
const firstSlice = downsampledSlices[0];
|
|
58
|
+
const combinedShape = [...image.data.shape];
|
|
59
|
+
combinedShape[tDimIndex] = tSize;
|
|
60
|
+
// Update spatial dimensions based on downsampled size
|
|
61
|
+
for (let i = 0; i < image.dims.length; i++) {
|
|
62
|
+
if (i !== tDimIndex) {
|
|
63
|
+
const sliceIndex = i < tDimIndex ? i : i - 1;
|
|
64
|
+
combinedShape[i] = firstSlice.shape[sliceIndex];
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
// Create combined array
|
|
68
|
+
const combinedStore = new Map();
|
|
69
|
+
const combinedRoot = zarr.root(combinedStore);
|
|
70
|
+
const combinedArray = await zarr.create(combinedRoot.resolve("combined"), {
|
|
71
|
+
shape: combinedShape,
|
|
72
|
+
chunk_shape: combinedShape.map((s) => Math.min(s, 256)),
|
|
73
|
+
data_type: image.data.dtype,
|
|
74
|
+
fill_value: 0,
|
|
75
|
+
});
|
|
76
|
+
// Copy each downsampled slice into the combined array
|
|
77
|
+
for (let t = 0; t < tSize; t++) {
|
|
78
|
+
const sliceData = await zarr.get(downsampledSlices[t]);
|
|
79
|
+
const targetSelection = new Array(combinedShape.length).fill(null);
|
|
80
|
+
targetSelection[tDimIndex] = t;
|
|
81
|
+
await zarr.set(combinedArray, targetSelection, sliceData);
|
|
82
|
+
}
|
|
83
|
+
// Compute new metadata (time dimension unchanged, spatial dimensions downsampled)
|
|
84
|
+
const [translation, scale] = nextScaleMetadata(image, dimFactors, spatialDims);
|
|
85
|
+
return new NgffImage({
|
|
86
|
+
data: combinedArray,
|
|
87
|
+
dims: image.dims,
|
|
88
|
+
scale: { ...image.scale, ...scale },
|
|
89
|
+
translation: { ...image.translation, ...translation },
|
|
90
|
+
name: image.name,
|
|
91
|
+
axesUnits: image.axesUnits,
|
|
92
|
+
computedCallbacks: image.computedCallbacks,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
const isVector = image.dims.includes("c");
|
|
96
|
+
// Convert to ITK-Wasm format
|
|
97
|
+
const itkImage = await zarrToItkImage(image.data, image.dims, isVector);
|
|
98
|
+
// Prepare shrink factors - need to be for ALL dimensions in ITK order (reversed)
|
|
99
|
+
const shrinkFactors = [];
|
|
100
|
+
for (let i = image.dims.length - 1; i >= 0; i--) {
|
|
101
|
+
const dim = image.dims[i];
|
|
102
|
+
if (SPATIAL_DIMS.includes(dim)) {
|
|
103
|
+
shrinkFactors.push(dimFactors[dim] || 1);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
// Use all zeros for cropRadius
|
|
107
|
+
const cropRadius = new Array(shrinkFactors.length).fill(0);
|
|
108
|
+
// Perform downsampling using browser-compatible function
|
|
109
|
+
const { downsampled } = await downsample(itkImage, {
|
|
110
|
+
shrinkFactors,
|
|
111
|
+
cropRadius: cropRadius,
|
|
112
|
+
});
|
|
113
|
+
// Compute new metadata
|
|
114
|
+
const [translation, scale] = nextScaleMetadata(image, dimFactors, spatialDims);
|
|
115
|
+
// Convert back to zarr array in a new in-memory store
|
|
116
|
+
const store = new Map();
|
|
117
|
+
const chunkShape = downsampled.size.map((s) => Math.min(s, 256)).reverse();
|
|
118
|
+
const array = await itkImageToZarr(downsampled, store, "image", chunkShape, image.dims);
|
|
119
|
+
return new NgffImage({
|
|
120
|
+
data: array,
|
|
121
|
+
dims: image.dims,
|
|
122
|
+
scale,
|
|
123
|
+
translation,
|
|
124
|
+
name: image.name,
|
|
125
|
+
axesUnits: image.axesUnits,
|
|
126
|
+
computedCallbacks: image.computedCallbacks,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Perform bin shrink downsampling using ITK-Wasm (browser version)
|
|
131
|
+
*/
|
|
132
|
+
async function downsampleBinShrinkImpl(image, dimFactors, spatialDims) {
|
|
133
|
+
// Handle time dimension by processing each time slice independently
|
|
134
|
+
if (image.dims.includes("t")) {
|
|
135
|
+
const tDimIndex = image.dims.indexOf("t");
|
|
136
|
+
const tSize = image.data.shape[tDimIndex];
|
|
137
|
+
const newDims = image.dims.filter((dim) => dim !== "t");
|
|
138
|
+
// Downsample each time slice
|
|
139
|
+
const downsampledSlices = [];
|
|
140
|
+
for (let t = 0; t < tSize; t++) {
|
|
141
|
+
// Extract time slice
|
|
142
|
+
const selection = new Array(image.data.shape.length).fill(null);
|
|
143
|
+
selection[tDimIndex] = t;
|
|
144
|
+
const sliceData = await zarr.get(image.data, selection);
|
|
145
|
+
// Create temporary zarr array for this slice
|
|
146
|
+
const sliceStore = new Map();
|
|
147
|
+
const sliceRoot = zarr.root(sliceStore);
|
|
148
|
+
const sliceShape = image.data.shape.filter((_, i) => i !== tDimIndex);
|
|
149
|
+
const sliceChunkShape = sliceShape.map((s) => Math.min(s, 256));
|
|
150
|
+
const sliceArray = await zarr.create(sliceRoot.resolve("slice"), {
|
|
151
|
+
shape: sliceShape,
|
|
152
|
+
chunk_shape: sliceChunkShape,
|
|
153
|
+
data_type: image.data.dtype,
|
|
154
|
+
fill_value: 0,
|
|
155
|
+
});
|
|
156
|
+
const fullSelection = new Array(sliceShape.length).fill(null);
|
|
157
|
+
await zarr.set(sliceArray, fullSelection, sliceData);
|
|
158
|
+
// Create NgffImage for this slice (without 't' dimension)
|
|
159
|
+
const sliceImage = new NgffImage({
|
|
160
|
+
data: sliceArray,
|
|
161
|
+
dims: newDims,
|
|
162
|
+
scale: Object.fromEntries(Object.entries(image.scale).filter(([dim]) => dim !== "t")),
|
|
163
|
+
translation: Object.fromEntries(Object.entries(image.translation).filter(([dim]) => dim !== "t")),
|
|
164
|
+
name: image.name,
|
|
165
|
+
axesUnits: image.axesUnits
|
|
166
|
+
? Object.fromEntries(Object.entries(image.axesUnits).filter(([dim]) => dim !== "t"))
|
|
167
|
+
: undefined,
|
|
168
|
+
computedCallbacks: image.computedCallbacks,
|
|
169
|
+
});
|
|
170
|
+
// Recursively downsample this slice
|
|
171
|
+
const downsampledSlice = await downsampleBinShrinkImpl(sliceImage, dimFactors, spatialDims);
|
|
172
|
+
downsampledSlices.push(downsampledSlice.data);
|
|
173
|
+
}
|
|
174
|
+
// Combine downsampled slices back into a single array with 't' dimension
|
|
175
|
+
const firstSlice = downsampledSlices[0];
|
|
176
|
+
const combinedShape = [...image.data.shape];
|
|
177
|
+
combinedShape[tDimIndex] = tSize;
|
|
178
|
+
// Update spatial dimensions based on downsampled size
|
|
179
|
+
for (let i = 0; i < image.dims.length; i++) {
|
|
180
|
+
if (i !== tDimIndex) {
|
|
181
|
+
const sliceIndex = i < tDimIndex ? i : i - 1;
|
|
182
|
+
combinedShape[i] = firstSlice.shape[sliceIndex];
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
// Create combined array
|
|
186
|
+
const combinedStore = new Map();
|
|
187
|
+
const combinedRoot = zarr.root(combinedStore);
|
|
188
|
+
const combinedArray = await zarr.create(combinedRoot.resolve("combined"), {
|
|
189
|
+
shape: combinedShape,
|
|
190
|
+
chunk_shape: combinedShape.map((s) => Math.min(s, 256)),
|
|
191
|
+
data_type: image.data.dtype,
|
|
192
|
+
fill_value: 0,
|
|
193
|
+
});
|
|
194
|
+
// Copy each downsampled slice into the combined array
|
|
195
|
+
for (let t = 0; t < tSize; t++) {
|
|
196
|
+
const sliceData = await zarr.get(downsampledSlices[t]);
|
|
197
|
+
const targetSelection = new Array(combinedShape.length).fill(null);
|
|
198
|
+
targetSelection[tDimIndex] = t;
|
|
199
|
+
await zarr.set(combinedArray, targetSelection, sliceData);
|
|
200
|
+
}
|
|
201
|
+
// Compute new metadata
|
|
202
|
+
const [translation, scale] = nextScaleMetadata(image, dimFactors, spatialDims);
|
|
203
|
+
return new NgffImage({
|
|
204
|
+
data: combinedArray,
|
|
205
|
+
dims: image.dims,
|
|
206
|
+
scale: { ...image.scale, ...scale },
|
|
207
|
+
translation: { ...image.translation, ...translation },
|
|
208
|
+
name: image.name,
|
|
209
|
+
axesUnits: image.axesUnits,
|
|
210
|
+
computedCallbacks: image.computedCallbacks,
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
const isVector = image.dims.includes("c");
|
|
214
|
+
// Convert to ITK-Wasm format
|
|
215
|
+
const itkImage = await zarrToItkImage(image.data, image.dims, isVector);
|
|
216
|
+
// Prepare shrink factors - only for spatial dimensions in ITK order (reversed)
|
|
217
|
+
const shrinkFactors = [];
|
|
218
|
+
for (let i = image.dims.length - 1; i >= 0; i--) {
|
|
219
|
+
const dim = image.dims[i];
|
|
220
|
+
if (SPATIAL_DIMS.includes(dim)) {
|
|
221
|
+
shrinkFactors.push(dimFactors[dim] || 1);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
// Perform downsampling using browser-compatible function
|
|
225
|
+
const { downsampled } = await downsampleBinShrink(itkImage, {
|
|
226
|
+
shrinkFactors,
|
|
227
|
+
});
|
|
228
|
+
// Compute new metadata
|
|
229
|
+
const [translation, scale] = nextScaleMetadata(image, dimFactors, spatialDims);
|
|
230
|
+
// Convert back to zarr array in a new in-memory store
|
|
231
|
+
const store = new Map();
|
|
232
|
+
const chunkShape = downsampled.size.map((s) => Math.min(s, 256)).reverse();
|
|
233
|
+
const array = await itkImageToZarr(downsampled, store, "image", chunkShape, image.dims);
|
|
234
|
+
return new NgffImage({
|
|
235
|
+
data: array,
|
|
236
|
+
dims: image.dims,
|
|
237
|
+
scale,
|
|
238
|
+
translation,
|
|
239
|
+
name: image.name,
|
|
240
|
+
axesUnits: image.axesUnits,
|
|
241
|
+
computedCallbacks: image.computedCallbacks,
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Perform label image downsampling using ITK-Wasm (browser version)
|
|
246
|
+
*/
|
|
247
|
+
async function downsampleLabelImageImpl(image, dimFactors, spatialDims) {
|
|
248
|
+
// Handle time dimension by processing each time slice independently
|
|
249
|
+
if (image.dims.includes("t")) {
|
|
250
|
+
const tDimIndex = image.dims.indexOf("t");
|
|
251
|
+
const tSize = image.data.shape[tDimIndex];
|
|
252
|
+
const newDims = image.dims.filter((dim) => dim !== "t");
|
|
253
|
+
// Downsample each time slice
|
|
254
|
+
const downsampledSlices = [];
|
|
255
|
+
for (let t = 0; t < tSize; t++) {
|
|
256
|
+
// Extract time slice
|
|
257
|
+
const selection = new Array(image.data.shape.length).fill(null);
|
|
258
|
+
selection[tDimIndex] = t;
|
|
259
|
+
const sliceData = await zarr.get(image.data, selection);
|
|
260
|
+
// Create temporary zarr array for this slice
|
|
261
|
+
const sliceStore = new Map();
|
|
262
|
+
const sliceRoot = zarr.root(sliceStore);
|
|
263
|
+
const sliceShape = image.data.shape.filter((_, i) => i !== tDimIndex);
|
|
264
|
+
const sliceChunkShape = sliceShape.map((s) => Math.min(s, 256));
|
|
265
|
+
const sliceArray = await zarr.create(sliceRoot.resolve("slice"), {
|
|
266
|
+
shape: sliceShape,
|
|
267
|
+
chunk_shape: sliceChunkShape,
|
|
268
|
+
data_type: image.data.dtype,
|
|
269
|
+
fill_value: 0,
|
|
270
|
+
});
|
|
271
|
+
const fullSelection = new Array(sliceShape.length).fill(null);
|
|
272
|
+
await zarr.set(sliceArray, fullSelection, sliceData);
|
|
273
|
+
// Create NgffImage for this slice (without 't' dimension)
|
|
274
|
+
const sliceImage = new NgffImage({
|
|
275
|
+
data: sliceArray,
|
|
276
|
+
dims: newDims,
|
|
277
|
+
scale: Object.fromEntries(Object.entries(image.scale).filter(([dim]) => dim !== "t")),
|
|
278
|
+
translation: Object.fromEntries(Object.entries(image.translation).filter(([dim]) => dim !== "t")),
|
|
279
|
+
name: image.name,
|
|
280
|
+
axesUnits: image.axesUnits
|
|
281
|
+
? Object.fromEntries(Object.entries(image.axesUnits).filter(([dim]) => dim !== "t"))
|
|
282
|
+
: undefined,
|
|
283
|
+
computedCallbacks: image.computedCallbacks,
|
|
284
|
+
});
|
|
285
|
+
// Recursively downsample this slice
|
|
286
|
+
const downsampledSlice = await downsampleLabelImageImpl(sliceImage, dimFactors, spatialDims);
|
|
287
|
+
downsampledSlices.push(downsampledSlice.data);
|
|
288
|
+
}
|
|
289
|
+
// Combine downsampled slices back into a single array with 't' dimension
|
|
290
|
+
const firstSlice = downsampledSlices[0];
|
|
291
|
+
const combinedShape = [...image.data.shape];
|
|
292
|
+
combinedShape[tDimIndex] = tSize;
|
|
293
|
+
// Update spatial dimensions based on downsampled size
|
|
294
|
+
for (let i = 0; i < image.dims.length; i++) {
|
|
295
|
+
if (i !== tDimIndex) {
|
|
296
|
+
const sliceIndex = i < tDimIndex ? i : i - 1;
|
|
297
|
+
combinedShape[i] = firstSlice.shape[sliceIndex];
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
// Create combined array
|
|
301
|
+
const combinedStore = new Map();
|
|
302
|
+
const combinedRoot = zarr.root(combinedStore);
|
|
303
|
+
const combinedArray = await zarr.create(combinedRoot.resolve("combined"), {
|
|
304
|
+
shape: combinedShape,
|
|
305
|
+
chunk_shape: combinedShape.map((s) => Math.min(s, 256)),
|
|
306
|
+
data_type: image.data.dtype,
|
|
307
|
+
fill_value: 0,
|
|
308
|
+
});
|
|
309
|
+
// Copy each downsampled slice into the combined array
|
|
310
|
+
for (let t = 0; t < tSize; t++) {
|
|
311
|
+
const sliceData = await zarr.get(downsampledSlices[t]);
|
|
312
|
+
const targetSelection = new Array(combinedShape.length).fill(null);
|
|
313
|
+
targetSelection[tDimIndex] = t;
|
|
314
|
+
await zarr.set(combinedArray, targetSelection, sliceData);
|
|
315
|
+
}
|
|
316
|
+
// Compute new metadata
|
|
317
|
+
const [translation, scale] = nextScaleMetadata(image, dimFactors, spatialDims);
|
|
318
|
+
return new NgffImage({
|
|
319
|
+
data: combinedArray,
|
|
320
|
+
dims: image.dims,
|
|
321
|
+
scale: { ...image.scale, ...scale },
|
|
322
|
+
translation: { ...image.translation, ...translation },
|
|
323
|
+
name: image.name,
|
|
324
|
+
axesUnits: image.axesUnits,
|
|
325
|
+
computedCallbacks: image.computedCallbacks,
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
const isVector = image.dims.includes("c");
|
|
329
|
+
// Convert to ITK-Wasm format
|
|
330
|
+
const itkImage = await zarrToItkImage(image.data, image.dims, isVector);
|
|
331
|
+
// Prepare shrink factors - need to be for ALL dimensions in ITK order (reversed)
|
|
332
|
+
const shrinkFactors = [];
|
|
333
|
+
for (let i = image.dims.length - 1; i >= 0; i--) {
|
|
334
|
+
const dim = image.dims[i];
|
|
335
|
+
if (SPATIAL_DIMS.includes(dim)) {
|
|
336
|
+
shrinkFactors.push(dimFactors[dim] || 1);
|
|
337
|
+
}
|
|
338
|
+
else {
|
|
339
|
+
shrinkFactors.push(1); // Non-spatial dimensions don't shrink
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
// Use all zeros for cropRadius
|
|
343
|
+
const cropRadius = new Array(shrinkFactors.length).fill(0);
|
|
344
|
+
// Perform downsampling using browser-compatible function
|
|
345
|
+
const { downsampled } = await downsampleLabelImage(itkImage, {
|
|
346
|
+
shrinkFactors,
|
|
347
|
+
cropRadius: cropRadius,
|
|
348
|
+
});
|
|
349
|
+
// Compute new metadata
|
|
350
|
+
const [translation, scale] = nextScaleMetadata(image, dimFactors, spatialDims);
|
|
351
|
+
// Convert back to zarr array in a new in-memory store
|
|
352
|
+
const store = new Map();
|
|
353
|
+
const chunkShape = downsampled.size.map((s) => Math.min(s, 256)).reverse();
|
|
354
|
+
const array = await itkImageToZarr(downsampled, store, "image", chunkShape, image.dims);
|
|
355
|
+
return new NgffImage({
|
|
356
|
+
data: array,
|
|
357
|
+
dims: image.dims,
|
|
358
|
+
scale,
|
|
359
|
+
translation,
|
|
360
|
+
name: image.name,
|
|
361
|
+
axesUnits: image.axesUnits,
|
|
362
|
+
computedCallbacks: image.computedCallbacks,
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Main downsampling function for ITK-Wasm (browser version)
|
|
367
|
+
*/
|
|
368
|
+
export async function downsampleItkWasm(ngffImage, scaleFactors, smoothing) {
|
|
369
|
+
const multiscales = [ngffImage];
|
|
370
|
+
const dims = ngffImage.dims;
|
|
371
|
+
const spatialDims = dims.filter((dim) => SPATIAL_DIMS.includes(dim));
|
|
372
|
+
let previousImage = ngffImage;
|
|
373
|
+
let previousDimFactors = {};
|
|
374
|
+
for (const dim of dims)
|
|
375
|
+
previousDimFactors[dim] = 1;
|
|
376
|
+
for (let i = 0; i < scaleFactors.length; i++) {
|
|
377
|
+
const scaleFactor = scaleFactors[i];
|
|
378
|
+
let sourceImage;
|
|
379
|
+
let sourceDimFactors;
|
|
380
|
+
if (smoothing === "bin_shrink") {
|
|
381
|
+
// Purely incremental: scaleFactor is the shrink for this step
|
|
382
|
+
sourceImage = previousImage;
|
|
383
|
+
sourceDimFactors = {};
|
|
384
|
+
if (typeof scaleFactor === "number") {
|
|
385
|
+
for (const dim of spatialDims)
|
|
386
|
+
sourceDimFactors[dim] = scaleFactor;
|
|
387
|
+
}
|
|
388
|
+
else {
|
|
389
|
+
for (const dim of spatialDims) {
|
|
390
|
+
sourceDimFactors[dim] = scaleFactor[dim] || 1;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
for (const dim of dims) {
|
|
394
|
+
if (!(dim in sourceDimFactors))
|
|
395
|
+
sourceDimFactors[dim] = 1;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
else {
|
|
399
|
+
// Hybrid absolute strategy
|
|
400
|
+
const dimFactors = dimScaleFactors(dims, scaleFactor, previousDimFactors, ngffImage, previousImage);
|
|
401
|
+
let canDownsampleIncrementally = true;
|
|
402
|
+
for (const dim of Object.keys(dimFactors)) {
|
|
403
|
+
const dimIndex = ngffImage.dims.indexOf(dim);
|
|
404
|
+
if (dimIndex >= 0) {
|
|
405
|
+
const originalSize = ngffImage.data.shape[dimIndex];
|
|
406
|
+
const targetSize = Math.floor(originalSize /
|
|
407
|
+
(typeof scaleFactor === "number"
|
|
408
|
+
? scaleFactor
|
|
409
|
+
: scaleFactor[dim]));
|
|
410
|
+
const prevDimIndex = previousImage.dims.indexOf(dim);
|
|
411
|
+
const previousSize = previousImage.data.shape[prevDimIndex];
|
|
412
|
+
if (Math.floor(previousSize / dimFactors[dim]) !== targetSize) {
|
|
413
|
+
canDownsampleIncrementally = false;
|
|
414
|
+
break;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
if (canDownsampleIncrementally) {
|
|
419
|
+
sourceImage = previousImage;
|
|
420
|
+
sourceDimFactors = dimFactors;
|
|
421
|
+
}
|
|
422
|
+
else {
|
|
423
|
+
sourceImage = ngffImage;
|
|
424
|
+
const originalDimFactors = {};
|
|
425
|
+
for (const dim of dims)
|
|
426
|
+
originalDimFactors[dim] = 1;
|
|
427
|
+
sourceDimFactors = dimScaleFactors(dims, scaleFactor, originalDimFactors);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
let downsampled;
|
|
431
|
+
if (smoothing === "gaussian") {
|
|
432
|
+
downsampled = await downsampleGaussian(sourceImage, sourceDimFactors, spatialDims);
|
|
433
|
+
}
|
|
434
|
+
else if (smoothing === "bin_shrink") {
|
|
435
|
+
downsampled = await downsampleBinShrinkImpl(sourceImage, sourceDimFactors, spatialDims);
|
|
436
|
+
}
|
|
437
|
+
else if (smoothing === "label_image") {
|
|
438
|
+
downsampled = await downsampleLabelImageImpl(sourceImage, sourceDimFactors, spatialDims);
|
|
439
|
+
}
|
|
440
|
+
else {
|
|
441
|
+
throw new Error(`Unknown smoothing method: ${smoothing}`);
|
|
442
|
+
}
|
|
443
|
+
multiscales.push(downsampled);
|
|
444
|
+
previousImage = downsampled;
|
|
445
|
+
if (smoothing === "bin_shrink") {
|
|
446
|
+
if (typeof scaleFactor === "number") {
|
|
447
|
+
for (const dim of spatialDims) {
|
|
448
|
+
previousDimFactors[dim] *= scaleFactor;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
else {
|
|
452
|
+
for (const dim of spatialDims) {
|
|
453
|
+
previousDimFactors[dim] *= scaleFactor[dim] || 1;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
else {
|
|
458
|
+
previousDimFactors = updatePreviousDimFactors(scaleFactor, spatialDims, previousDimFactors);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
return multiscales;
|
|
462
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { NgffImage } from "../types/ngff_image.js";
|
|
2
|
+
/**
|
|
3
|
+
* Main downsampling function for ITK-Wasm (browser version)
|
|
4
|
+
*/
|
|
5
|
+
export declare function downsampleItkWasm(ngffImage: NgffImage, scaleFactors: (Record<string, number> | number)[], smoothing: "gaussian" | "bin_shrink" | "label_image"): Promise<NgffImage[]>;
|
|
6
|
+
//# sourceMappingURL=itkwasm-node.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"itkwasm-node.d.ts","sourceRoot":"","sources":["../../src/methods/itkwasm-node.ts"],"names":[],"mappings":"AAcA,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AA2fnD;;GAEG;AACH,wBAAsB,iBAAiB,CACrC,SAAS,EAAE,SAAS,EACpB,YAAY,EAAE,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAAC,EAAE,EACjD,SAAS,EAAE,UAAU,GAAG,YAAY,GAAG,aAAa,GACnD,OAAO,CAAC,SAAS,EAAE,CAAC,CAsHtB"}
|