1- import * as zarr from "zarrita" ;
21import { NgffImage } from "../types/ngff_image.ts" ;
32import { Multiscales } from "../types/multiscales.ts" ;
43import { Methods } from "../types/methods.ts" ;
5- import type { MemoryStore } from "../io/from_ngff_zarr.ts" ;
64import {
75 createAxis ,
86 createDataset ,
97 createMetadata ,
108 createMultiscales ,
119} from "../utils/factory.ts" ;
1210import { getMethodMetadata } from "../utils/method_metadata.ts" ;
11+ import { downsampleItkWasm } from "../methods/itkwasm.ts" ;
1312
14- export interface ToNgffImageOptions {
15- dims ?: string [ ] ;
16- scale ?: Record < string , number > ;
17- translation ?: Record < string , number > ;
18- name ?: string ;
19- }
20-
21- /**
22- * Convert array data to NgffImage
23- *
24- * @param data - Input data as typed array or regular array
25- * @param options - Configuration options for NgffImage creation
26- * @returns NgffImage instance
27- */
28- export async function toNgffImage (
29- data : ArrayLike < number > | number [ ] [ ] | number [ ] [ ] [ ] ,
30- options : ToNgffImageOptions = { } ,
31- ) : Promise < NgffImage > {
32- const {
33- dims = [ "y" , "x" ] ,
34- scale = { } ,
35- translation = { } ,
36- name = "image" ,
37- } = options ;
38-
39- // Determine data shape and create typed array
40- let typedData : Float32Array ;
41- let shape : number [ ] ;
42-
43- if ( Array . isArray ( data ) ) {
44- // Handle multi-dimensional arrays
45- if ( Array . isArray ( data [ 0 ] ) ) {
46- if ( Array . isArray ( ( data [ 0 ] as unknown [ ] ) [ 0 ] ) ) {
47- // 3D array
48- const d3 = data as number [ ] [ ] [ ] ;
49- shape = [ d3 . length , d3 [ 0 ] . length , d3 [ 0 ] [ 0 ] . length ] ;
50- typedData = new Float32Array ( shape [ 0 ] * shape [ 1 ] * shape [ 2 ] ) ;
51-
52- let idx = 0 ;
53- for ( let i = 0 ; i < shape [ 0 ] ; i ++ ) {
54- for ( let j = 0 ; j < shape [ 1 ] ; j ++ ) {
55- for ( let k = 0 ; k < shape [ 2 ] ; k ++ ) {
56- typedData [ idx ++ ] = d3 [ i ] [ j ] [ k ] ;
57- }
58- }
59- }
60- } else {
61- // 2D array
62- const d2 = data as number [ ] [ ] ;
63- shape = [ d2 . length , d2 [ 0 ] . length ] ;
64- typedData = new Float32Array ( shape [ 0 ] * shape [ 1 ] ) ;
65-
66- let idx = 0 ;
67- for ( let i = 0 ; i < shape [ 0 ] ; i ++ ) {
68- for ( let j = 0 ; j < shape [ 1 ] ; j ++ ) {
69- typedData [ idx ++ ] = d2 [ i ] [ j ] ;
70- }
71- }
72- }
73- } else {
74- // 1D array
75- const d1 = data as unknown as number [ ] ;
76- shape = [ d1 . length ] ;
77- typedData = new Float32Array ( d1 ) ;
78- }
79- } else {
80- // ArrayLike (already a typed array)
81- typedData = new Float32Array ( data as ArrayLike < number > ) ;
82- shape = [ typedData . length ] ;
83- }
84-
85- // Adjust shape to match dims length
86- while ( shape . length < dims . length ) {
87- shape . unshift ( 1 ) ;
88- }
89-
90- if ( shape . length > dims . length ) {
91- throw new Error (
92- `Data dimensionality (${ shape . length } ) exceeds dims length (${ dims . length } )` ,
93- ) ;
94- }
95-
96- // Create in-memory zarr store and array
97- const store : MemoryStore = new Map ( ) ;
98- const root = zarr . root ( store ) ;
99-
100- // Calculate appropriate chunk size
101- const chunkShape = shape . map ( ( dim ) => Math . min ( dim , 256 ) ) ;
102-
103- const zarrArray = await zarr . create ( root . resolve ( "data" ) , {
104- shape,
105- chunk_shape : chunkShape ,
106- data_type : "float32" ,
107- fill_value : 0 ,
108- } ) ;
109-
110- // Write data to zarr array
111- await zarr . set ( zarrArray , [ ] , {
112- data : typedData ,
113- shape,
114- stride : calculateStride ( shape ) ,
115- } ) ;
116-
117- // Create scale and translation records with defaults
118- const fullScale : Record < string , number > = { } ;
119- const fullTranslation : Record < string , number > = { } ;
120-
121- for ( const dim of dims ) {
122- fullScale [ dim ] = scale [ dim ] ?? 1.0 ;
123- fullTranslation [ dim ] = translation [ dim ] ?? 0.0 ;
124- }
125-
126- return new NgffImage ( {
127- data : zarrArray ,
128- dims,
129- scale : fullScale ,
130- translation : fullTranslation ,
131- name,
132- axesUnits : undefined ,
133- computedCallbacks : undefined ,
134- } ) ;
135- }
13+ // Re-export for convenience
14+ export { toNgffImage , type ToNgffImageOptions } from "./to_ngff_image.ts" ;
13615
13716export interface ToMultiscalesOptions {
13817 scaleFactors ?: ( Record < string , number > | number ) [ ] ;
@@ -147,19 +26,40 @@ export interface ToMultiscalesOptions {
14726 * @param options - Configuration options
14827 * @returns Multiscales object
14928 */
150- export function toMultiscales (
29+ export async function toMultiscales (
15130 image : NgffImage ,
15231 options : ToMultiscalesOptions = { } ,
153- ) : Multiscales {
32+ ) : Promise < Multiscales > {
15433 const {
15534 scaleFactors = [ 2 , 4 ] ,
15635 method = Methods . ITKWASM_GAUSSIAN ,
15736 chunks : _chunks ,
15837 } = options ;
15938
160- // For now, create only the base image (no actual downsampling)
161- // This is a simplified implementation for testing metadata functionality
162- const images = [ image ] ;
39+ let images : NgffImage [ ] ;
40+
41+ // Check if we should perform actual downsampling
42+ if (
43+ method === Methods . ITKWASM_GAUSSIAN ||
44+ method === Methods . ITKWASM_BIN_SHRINK ||
45+ method === Methods . ITKWASM_LABEL_IMAGE
46+ ) {
47+ // Perform actual downsampling using ITK-Wasm
48+ const smoothing = method === Methods . ITKWASM_GAUSSIAN
49+ ? "gaussian"
50+ : method === Methods . ITKWASM_BIN_SHRINK
51+ ? "bin_shrink"
52+ : "label_image" ;
53+
54+ images = await downsampleItkWasm (
55+ image ,
56+ scaleFactors as ( Record < string , number > | number ) [ ] ,
57+ smoothing ,
58+ ) ;
59+ } else {
60+ // Fallback: create only the base image (no actual downsampling)
61+ images = [ image ] ;
62+ }
16363
16464 // Create axes from image dimensions
16565 const axes = image . dims . map ( ( dim ) => {
@@ -178,14 +78,14 @@ export function toMultiscales(
17878 }
17979 } ) ;
18080
181- // Create datasets
182- const datasets = [
183- createDataset (
184- "0" ,
185- image . dims . map ( ( dim ) => image . scale [ dim ] ) ,
186- image . dims . map ( ( dim ) => image . translation [ dim ] ) ,
187- ) ,
188- ] ;
81+ // Create datasets for all images
82+ const datasets = images . map ( ( img , index ) => {
83+ return createDataset (
84+ ` ${ index } ` ,
85+ img . dims . map ( ( dim ) => img . scale [ dim ] ) ,
86+ img . dims . map ( ( dim ) => img . translation [ dim ] ) ,
87+ ) ;
88+ } ) ;
18989
19090 // Create metadata with method information
19191 const methodMetadata = getMethodMetadata ( method ) ;
@@ -199,12 +99,3 @@ export function toMultiscales(
19999
200100 return createMultiscales ( images , metadata , scaleFactors , method ) ;
201101}
202-
203- function calculateStride ( shape : number [ ] ) : number [ ] {
204- const stride = new Array ( shape . length ) ;
205- stride [ shape . length - 1 ] = 1 ;
206- for ( let i = shape . length - 2 ; i >= 0 ; i -- ) {
207- stride [ i ] = stride [ i + 1 ] * shape [ i + 1 ] ;
208- }
209- return stride ;
210- }
0 commit comments