Skip to content

Commit 0d81ef3

Browse files
committed
add ome_zarr prepare/open methods
1 parent 7b3c728 commit 0d81ef3

File tree

4 files changed

+444
-98
lines changed

4 files changed

+444
-98
lines changed

funlib/persistence/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from .arrays import Array, open_ds, prepare_ds # noqa
1+
from .arrays import Array, open_ds, prepare_ds, open_ome_ds, prepare_ome_ds # noqa
22

33
__version__ = "0.5.3"
44
__version_info__ = tuple(int(i) for i in __version__.split("."))

funlib/persistence/arrays/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
from .array import Array # noqa
22
from .datasets import prepare_ds, open_ds # noqa
3+
from .ome_datasets import prepare_ome_ds, open_ome_ds # noqa

funlib/persistence/arrays/metadata.py

Lines changed: 215 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -8,103 +8,6 @@
88
from funlib.geometry import Coordinate
99

1010

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-
10811
class MetaData:
10912
def __init__(
11013
self,
@@ -125,6 +28,35 @@ def __init__(
12528

12629
self.validate(strict)
12730

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+
12860
@property
12961
def offset(self) -> Coordinate:
13062
return (
@@ -224,6 +156,192 @@ def validate(self, strict: bool):
224156
assert self.dims == self.physical_dims + self.channel_dims
225157

226158

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+
227345
DEFAULT_METADATA_FORMAT = MetaDataFormat()
228346
LOCAL_PATHS = [Path("pyproject.toml"), Path("funlib_persistence.toml")]
229347
USER_PATHS = [

0 commit comments

Comments
 (0)