8
8
from funlib .geometry import Coordinate
9
9
10
10
11
- class MetaDataFormat (BaseModel ):
12
- offset_attr : str = "offset"
13
- voxel_size_attr : str = "voxel_size"
14
- axis_names_attr : str = "axis_names"
15
- units_attr : str = "units"
16
- types_attr : str = "types"
17
-
18
- class Config :
19
- extra = "forbid"
20
-
21
- def fetch (self , data : dict [str | int , Any ], keys : Sequence [str ]):
22
- current_key : str | int
23
- current_key , * keys = keys
24
- try :
25
- current_key = int (current_key )
26
- except ValueError :
27
- pass
28
- if isinstance (current_key , int ):
29
- return self .fetch (data [current_key ], keys )
30
- if len (keys ) == 0 :
31
- return data .get (current_key , None )
32
- elif isinstance (data , list ):
33
- assert current_key == "{dim}" , current_key
34
- values = []
35
- for sub_data in data :
36
- try :
37
- values .append (self .fetch (sub_data , keys ))
38
- except KeyError :
39
- values .append (None )
40
- return values
41
- else :
42
- return self .fetch (data [current_key ], keys )
43
-
44
- def strip_channels (self , types : list [str ], to_strip : list [Sequence ]) -> None :
45
- to_delete = [i for i , t in enumerate (types ) if t not in ["space" , "time" ]][::- 1 ]
46
- for ll in to_strip :
47
- if ll is not None and len (ll ) == len (types ):
48
- for i in to_delete :
49
- del ll [i ]
50
-
51
- def parse (
52
- self ,
53
- shape ,
54
- data : dict [str | int , Any ],
55
- offset = None ,
56
- voxel_size = None ,
57
- axis_names = None ,
58
- units = None ,
59
- types = None ,
60
- strict = False ,
61
- ):
62
- offset = (
63
- offset
64
- if offset is not None
65
- else self .fetch (data , self .offset_attr .split ("/" ))
66
- )
67
- voxel_size = (
68
- voxel_size
69
- if voxel_size is not None
70
- else self .fetch (data , self .voxel_size_attr .split ("/" ))
71
- )
72
- axis_names = (
73
- axis_names
74
- if axis_names is not None
75
- else self .fetch (data , self .axis_names_attr .split ("/" ))
76
- )
77
- units = (
78
- units if units is not None else self .fetch (data , self .units_attr .split ("/" ))
79
- )
80
- types = (
81
- types if types is not None else self .fetch (data , self .types_attr .split ("/" ))
82
- )
83
-
84
- # we expect offset, voxel_size, and units to only apply to time and space dimensions
85
- # so here we strip off values that are not space or time
86
- if types is not None :
87
- self .strip_channels (types , [offset , voxel_size , units ])
88
-
89
- offset = Coordinate (offset ) if offset is not None else None
90
- voxel_size = Coordinate (voxel_size ) if voxel_size is not None else None
91
- axis_names = list (axis_names ) if axis_names is not None else None
92
- units = list (units ) if units is not None else None
93
- types = list (types ) if types is not None else None
94
-
95
- metadata = MetaData (
96
- shape = shape ,
97
- offset = offset ,
98
- voxel_size = voxel_size ,
99
- axis_names = axis_names ,
100
- units = units ,
101
- types = types ,
102
- strict = strict ,
103
- )
104
-
105
- return metadata
106
-
107
-
108
11
class MetaData :
109
12
def __init__ (
110
13
self ,
@@ -125,6 +28,35 @@ def __init__(
125
28
126
29
self .validate (strict )
127
30
31
+ def interleave_physical (
32
+ self , physical : Sequence [int | str ], non_physical : int | str | None
33
+ ) -> Sequence [int | str | None ]:
34
+ interleaved = []
35
+ physical_ind = 0
36
+ for i , type in enumerate (self .types ):
37
+ if type in ["space" , "time" ]:
38
+ interleaved .append (physical [physical_ind ])
39
+ physical_ind += 1
40
+ else :
41
+ interleaved .append (non_physical )
42
+ return interleaved
43
+
44
+ @property
45
+ def ome_scale (self ) -> Sequence [int ]:
46
+ return self .interleave_physical (self .voxel_size , 1 )
47
+
48
+ @property
49
+ def ome_translate (self ) -> Sequence [int ]:
50
+ assert self .offset % self .voxel_size == self .voxel_size * 0 , (
51
+ "funlib.persistence only supports ome-zarr with integer multiples of voxel_size as an offset."
52
+ f"offset: { self .offset } , voxel_size:{ self .voxel_size } , offset % voxel_size: { self .offset % self .voxel_size } "
53
+ )
54
+ return self .interleave_physical (self .offset / self .voxel_size , 0 )
55
+
56
+ @property
57
+ def ome_units (self ) -> list [str | None ]:
58
+ return self .interleave_physical (self .units , None )
59
+
128
60
@property
129
61
def offset (self ) -> Coordinate :
130
62
return (
@@ -224,6 +156,192 @@ def validate(self, strict: bool):
224
156
assert self .dims == self .physical_dims + self .channel_dims
225
157
226
158
159
+ class OME_MetaDataFormat (BaseModel ):
160
+ class Config :
161
+ extra = "forbid"
162
+
163
+ def fetch (self , data : dict [str | int , Any ], keys : Sequence [str ]):
164
+ current_key : str | int
165
+ current_key , * keys = keys
166
+ try :
167
+ current_key = int (current_key )
168
+ except ValueError :
169
+ pass
170
+ if isinstance (current_key , int ):
171
+ return self .fetch (data [current_key ], keys )
172
+ if len (keys ) == 0 :
173
+ return data .get (current_key , None )
174
+ elif isinstance (data , list ):
175
+ assert current_key == "{dim}" , current_key
176
+ values = []
177
+ for sub_data in data :
178
+ try :
179
+ values .append (self .fetch (sub_data , keys ))
180
+ except KeyError :
181
+ values .append (None )
182
+ return values
183
+ else :
184
+ return self .fetch (data [current_key ], keys )
185
+
186
+ def strip_channels (self , types : list [str ], to_strip : list [Sequence ]) -> None :
187
+ to_delete = [i for i , t in enumerate (types ) if t not in ["space" , "time" ]][::- 1 ]
188
+ for ll in to_strip :
189
+ if ll is not None and len (ll ) == len (types ):
190
+ for i in to_delete :
191
+ del ll [i ]
192
+
193
+ def parse (
194
+ self ,
195
+ shape ,
196
+ data : dict [str | int , Any ],
197
+ offset = None ,
198
+ voxel_size = None ,
199
+ axis_names = None ,
200
+ units = None ,
201
+ types = None ,
202
+ strict = False ,
203
+ ) -> MetaData :
204
+ offset = (
205
+ offset
206
+ if offset is not None
207
+ else self .fetch (data , self .offset_attr .split ("/" ))
208
+ )
209
+ voxel_size = (
210
+ voxel_size
211
+ if voxel_size is not None
212
+ else self .fetch (data , self .voxel_size_attr .split ("/" ))
213
+ )
214
+ axis_names = (
215
+ axis_names
216
+ if axis_names is not None
217
+ else self .fetch (data , self .axis_names_attr .split ("/" ))
218
+ )
219
+ units = (
220
+ units if units is not None else self .fetch (data , self .units_attr .split ("/" ))
221
+ )
222
+ types = (
223
+ types if types is not None else self .fetch (data , self .types_attr .split ("/" ))
224
+ )
225
+
226
+ if types is not None :
227
+ self .strip_channels (types , [offset , voxel_size , units ])
228
+
229
+ offset = Coordinate (offset ) if offset is not None else None
230
+ voxel_size = Coordinate (voxel_size ) if voxel_size is not None else None
231
+ axis_names = list (axis_names ) if axis_names is not None else None
232
+ units = list (units ) if units is not None else None
233
+ types = list (types ) if types is not None else None
234
+
235
+ metadata = MetaData (
236
+ shape = shape ,
237
+ offset = offset ,
238
+ voxel_size = voxel_size ,
239
+ axis_names = axis_names ,
240
+ units = units ,
241
+ types = types ,
242
+ strict = strict ,
243
+ )
244
+
245
+ return metadata
246
+
247
+
248
+ class MetaDataFormat (BaseModel ):
249
+ offset_attr : str = "offset"
250
+ voxel_size_attr : str = "voxel_size"
251
+ axis_names_attr : str = "axis_names"
252
+ units_attr : str = "units"
253
+ types_attr : str = "types"
254
+
255
+ class Config :
256
+ extra = "forbid"
257
+
258
+ def fetch (self , data : dict [str | int , Any ], keys : Sequence [str ]):
259
+ current_key : str | int
260
+ current_key , * keys = keys
261
+ try :
262
+ current_key = int (current_key )
263
+ except ValueError :
264
+ pass
265
+ if isinstance (current_key , int ):
266
+ return self .fetch (data [current_key ], keys )
267
+ if len (keys ) == 0 :
268
+ return data .get (current_key , None )
269
+ elif isinstance (data , list ):
270
+ assert current_key == "{dim}" , current_key
271
+ values = []
272
+ for sub_data in data :
273
+ try :
274
+ values .append (self .fetch (sub_data , keys ))
275
+ except KeyError :
276
+ values .append (None )
277
+ return values
278
+ else :
279
+ return self .fetch (data [current_key ], keys )
280
+
281
+ def strip_channels (self , types : list [str ], to_strip : list [Sequence ]) -> None :
282
+ to_delete = [i for i , t in enumerate (types ) if t not in ["space" , "time" ]][::- 1 ]
283
+ for ll in to_strip :
284
+ if ll is not None and len (ll ) == len (types ):
285
+ for i in to_delete :
286
+ del ll [i ]
287
+
288
+ def parse (
289
+ self ,
290
+ shape ,
291
+ data : dict [str | int , Any ],
292
+ offset = None ,
293
+ voxel_size = None ,
294
+ axis_names = None ,
295
+ units = None ,
296
+ types = None ,
297
+ strict = False ,
298
+ ) -> MetaData :
299
+ offset = (
300
+ offset
301
+ if offset is not None
302
+ else self .fetch (data , self .offset_attr .split ("/" ))
303
+ )
304
+ voxel_size = (
305
+ voxel_size
306
+ if voxel_size is not None
307
+ else self .fetch (data , self .voxel_size_attr .split ("/" ))
308
+ )
309
+ axis_names = (
310
+ axis_names
311
+ if axis_names is not None
312
+ else self .fetch (data , self .axis_names_attr .split ("/" ))
313
+ )
314
+ units = (
315
+ units if units is not None else self .fetch (data , self .units_attr .split ("/" ))
316
+ )
317
+ types = (
318
+ types if types is not None else self .fetch (data , self .types_attr .split ("/" ))
319
+ )
320
+
321
+ # we expect offset, voxel_size, and units to only apply to time and space dimensions
322
+ # so here we strip off values that are not space or time
323
+ if types is not None :
324
+ self .strip_channels (types , [offset , voxel_size , units ])
325
+
326
+ offset = Coordinate (offset ) if offset is not None else None
327
+ voxel_size = Coordinate (voxel_size ) if voxel_size is not None else None
328
+ axis_names = list (axis_names ) if axis_names is not None else None
329
+ units = list (units ) if units is not None else None
330
+ types = list (types ) if types is not None else None
331
+
332
+ metadata = MetaData (
333
+ shape = shape ,
334
+ offset = offset ,
335
+ voxel_size = voxel_size ,
336
+ axis_names = axis_names ,
337
+ units = units ,
338
+ types = types ,
339
+ strict = strict ,
340
+ )
341
+
342
+ return metadata
343
+
344
+
227
345
DEFAULT_METADATA_FORMAT = MetaDataFormat ()
228
346
LOCAL_PATHS = [Path ("pyproject.toml" ), Path ("funlib_persistence.toml" )]
229
347
USER_PATHS = [
0 commit comments