ngio 0.2.0a2__py3-none-any.whl → 0.2.0b1__py3-none-any.whl

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 (50) hide show
  1. ngio/__init__.py +4 -4
  2. ngio/common/__init__.py +12 -2
  3. ngio/common/_array_pipe.py +106 -0
  4. ngio/common/_axes_transforms.py +3 -2
  5. ngio/common/_dimensions.py +7 -0
  6. ngio/common/_masking_roi.py +158 -0
  7. ngio/common/_pyramid.py +16 -11
  8. ngio/common/_roi.py +74 -0
  9. ngio/common/_slicer.py +1 -2
  10. ngio/common/_zoom.py +5 -3
  11. ngio/hcs/__init__.py +2 -57
  12. ngio/hcs/plate.py +399 -0
  13. ngio/images/abstract_image.py +97 -28
  14. ngio/images/create.py +48 -29
  15. ngio/images/image.py +121 -57
  16. ngio/images/label.py +131 -86
  17. ngio/images/masked_image.py +259 -0
  18. ngio/images/omezarr_container.py +250 -77
  19. ngio/ome_zarr_meta/__init__.py +25 -13
  20. ngio/ome_zarr_meta/_meta_handlers.py +718 -69
  21. ngio/ome_zarr_meta/ngio_specs/__init__.py +8 -0
  22. ngio/ome_zarr_meta/ngio_specs/_channels.py +11 -0
  23. ngio/ome_zarr_meta/ngio_specs/_ngio_hcs.py +374 -2
  24. ngio/ome_zarr_meta/ngio_specs/_ngio_image.py +174 -113
  25. ngio/ome_zarr_meta/ngio_specs/_pixel_size.py +35 -3
  26. ngio/ome_zarr_meta/v04/__init__.py +17 -5
  27. ngio/ome_zarr_meta/v04/_v04_spec_utils.py +85 -12
  28. ngio/tables/__init__.py +2 -0
  29. ngio/tables/_validators.py +2 -4
  30. ngio/tables/backends/_anndata_utils.py +2 -1
  31. ngio/tables/backends/_anndata_v1.py +2 -1
  32. ngio/tables/backends/_json_v1.py +1 -1
  33. ngio/tables/tables_container.py +12 -2
  34. ngio/tables/v1/__init__.py +1 -2
  35. ngio/tables/v1/_feature_table.py +7 -5
  36. ngio/tables/v1/_generic_table.py +65 -11
  37. ngio/tables/v1/_roi_table.py +145 -27
  38. ngio/utils/__init__.py +3 -0
  39. ngio/utils/_datasets.py +4 -2
  40. ngio/utils/_fractal_fsspec_store.py +13 -0
  41. ngio/utils/_logger.py +3 -1
  42. ngio/utils/_zarr_utils.py +25 -2
  43. {ngio-0.2.0a2.dist-info → ngio-0.2.0b1.dist-info}/METADATA +4 -1
  44. ngio-0.2.0b1.dist-info/RECORD +54 -0
  45. ngio/ome_zarr_meta/_generic_handlers.py +0 -320
  46. ngio/ome_zarr_meta/v04/_meta_handlers.py +0 -54
  47. ngio/tables/v1/_masking_roi_table.py +0 -175
  48. ngio-0.2.0a2.dist-info/RECORD +0 -53
  49. {ngio-0.2.0a2.dist-info → ngio-0.2.0b1.dist-info}/WHEEL +0 -0
  50. {ngio-0.2.0a2.dist-info → ngio-0.2.0b1.dist-info}/licenses/LICENSE +0 -0
ngio/__init__.py CHANGED
@@ -10,7 +10,7 @@ __author__ = "Lorenzo Cerrone"
10
10
  __email__ = "lorenzo.cerrone@uzh.ch"
11
11
 
12
12
  from ngio.common import ArrayLike, Dimensions
13
- from ngio.hcs import OmeZarrPlate, OmeZarrWell, open_omezarr_plate, open_omezarr_well
13
+ from ngio.hcs import OmeZarrPlate, create_empty_plate, open_omezarr_plate
14
14
  from ngio.images import (
15
15
  Image,
16
16
  Label,
@@ -20,22 +20,22 @@ from ngio.images import (
20
20
  open_image,
21
21
  open_omezarr_container,
22
22
  )
23
- from ngio.ome_zarr_meta.ngio_specs import AxesSetup, PixelSize
23
+ from ngio.ome_zarr_meta.ngio_specs import AxesSetup, ImageInWellPath, PixelSize
24
24
 
25
25
  __all__ = [
26
26
  "ArrayLike",
27
27
  "AxesSetup",
28
28
  "Dimensions",
29
29
  "Image",
30
+ "ImageInWellPath",
30
31
  "Label",
31
32
  "OmeZarrContainer",
32
33
  "OmeZarrPlate",
33
- "OmeZarrWell",
34
34
  "PixelSize",
35
35
  "create_empty_omezarr",
36
+ "create_empty_plate",
36
37
  "create_omezarr_from_array",
37
38
  "open_image",
38
39
  "open_omezarr_container",
39
40
  "open_omezarr_plate",
40
- "open_omezarr_well",
41
41
  ]
ngio/common/__init__.py CHANGED
@@ -1,6 +1,11 @@
1
1
  """Common classes and functions that are used across the package."""
2
2
 
3
- from ngio.common._array_pipe import get_pipe, set_pipe
3
+ from ngio.common._array_pipe import (
4
+ get_masked_pipe,
5
+ get_pipe,
6
+ set_masked_pipe,
7
+ set_pipe,
8
+ )
4
9
  from ngio.common._axes_transforms import (
5
10
  transform_dask_array,
6
11
  transform_list,
@@ -8,8 +13,9 @@ from ngio.common._axes_transforms import (
8
13
  )
9
14
  from ngio.common._common_types import ArrayLike
10
15
  from ngio.common._dimensions import Dimensions
16
+ from ngio.common._masking_roi import compute_masking_roi
11
17
  from ngio.common._pyramid import consolidate_pyramid, init_empty_pyramid, on_disk_zoom
12
- from ngio.common._roi import RasterCooROI, WorldCooROI
18
+ from ngio.common._roi import RasterCooROI, WorldCooROI, roi_to_slice_kwargs
13
19
  from ngio.common._slicer import (
14
20
  SliceTransform,
15
21
  compute_and_slices,
@@ -27,16 +33,20 @@ __all__ = [
27
33
  "SliceTransform",
28
34
  "WorldCooROI",
29
35
  "compute_and_slices",
36
+ "compute_masking_roi",
30
37
  "consolidate_pyramid",
31
38
  "dask_get_slice",
32
39
  "dask_set_slice",
33
40
  "dask_zoom",
41
+ "get_masked_pipe",
34
42
  "get_pipe",
35
43
  "init_empty_pyramid",
36
44
  "numpy_get_slice",
37
45
  "numpy_set_slice",
38
46
  "numpy_zoom",
39
47
  "on_disk_zoom",
48
+ "roi_to_slice_kwargs",
49
+ "set_masked_pipe",
40
50
  "set_pipe",
41
51
  "transform_dask_array",
42
52
  "transform_list",
@@ -158,3 +158,109 @@ def set_pipe(
158
158
  )
159
159
  else:
160
160
  raise NgioValueError("Unknown patch type, expected numpy, dask or delayed.")
161
+
162
+
163
+ def _mask_pipe_common(
164
+ array: zarr.Array,
165
+ label_array: zarr.Array,
166
+ label: int,
167
+ *,
168
+ dimensions_array: Dimensions,
169
+ dimensions_label: Dimensions,
170
+ axes_order: Collection[str] | None = None,
171
+ mode: Literal["numpy", "dask", "delayed"] = "numpy",
172
+ **slice_kwargs: slice | int | Iterable[int],
173
+ ):
174
+ array_patch = get_pipe(
175
+ array,
176
+ dimensions=dimensions_array,
177
+ axes_order=axes_order,
178
+ mode=mode,
179
+ **slice_kwargs,
180
+ )
181
+
182
+ if "c" in slice_kwargs.keys():
183
+ # This makes the strong assumption that the
184
+ # user is passing the channel axis as "c"
185
+ # This will fail if the channel axis is queried
186
+ # with a different on-disk name
187
+ slice_kwargs.pop("c")
188
+
189
+ label_patch = get_pipe(
190
+ label_array,
191
+ dimensions=dimensions_label,
192
+ axes_order=axes_order,
193
+ mode=mode,
194
+ **slice_kwargs,
195
+ )
196
+
197
+ if isinstance(array_patch, np.ndarray):
198
+ label_patch = np.broadcast_to(label_patch, array_patch.shape)
199
+ elif isinstance(array_patch, dask.array.Array):
200
+ label_patch = dask.array.broadcast_to(label_patch, array_patch.shape)
201
+ else:
202
+ raise NgioValueError(f"Mode {mode} not yet supported for masked array.")
203
+
204
+ mask = label_patch == label
205
+ return array_patch, mask
206
+
207
+
208
+ def get_masked_pipe(
209
+ array: zarr.Array,
210
+ label_array: zarr.Array,
211
+ label: int,
212
+ *,
213
+ dimensions_array: Dimensions,
214
+ dimensions_label: Dimensions,
215
+ axes_order: Collection[str] | None = None,
216
+ mode: Literal["numpy", "dask", "delayed"] = "numpy",
217
+ **slice_kwargs: slice | int | Iterable[int],
218
+ ):
219
+ array_patch, mask = _mask_pipe_common(
220
+ array=array,
221
+ label_array=label_array,
222
+ label=label,
223
+ dimensions_array=dimensions_array,
224
+ dimensions_label=dimensions_label,
225
+ axes_order=axes_order,
226
+ mode=mode,
227
+ **slice_kwargs,
228
+ )
229
+ array_patch[~mask] = 0
230
+ return array_patch
231
+
232
+
233
+ def set_masked_pipe(
234
+ array: zarr.Array,
235
+ label_array: zarr.Array,
236
+ label: int,
237
+ patch: ArrayLike,
238
+ *,
239
+ dimensions_array: Dimensions,
240
+ dimensions_label: Dimensions,
241
+ axes_order: Collection[str] | None = None,
242
+ **slice_kwargs: slice | int | Iterable[int],
243
+ ):
244
+ if isinstance(patch, dask.array.Array):
245
+ mode = "dask"
246
+ elif isinstance(patch, np.ndarray):
247
+ mode = "numpy"
248
+ else:
249
+ raise NgioValueError(
250
+ "Mode not yet supported for masked array. Expected a numpy or dask array."
251
+ )
252
+
253
+ array_patch, mask = _mask_pipe_common(
254
+ array=array,
255
+ label_array=label_array,
256
+ label=label,
257
+ dimensions_array=dimensions_array,
258
+ dimensions_label=dimensions_label,
259
+ axes_order=axes_order,
260
+ mode=mode,
261
+ **slice_kwargs,
262
+ )
263
+ patch = np.where(mask, patch, array_patch)
264
+ set_pipe(
265
+ array, patch, dimensions=dimensions_array, axes_order=axes_order, **slice_kwargs
266
+ )
@@ -9,6 +9,7 @@ from ngio.ome_zarr_meta.ngio_specs._axes import (
9
9
  AxesTransformation,
10
10
  AxesTranspose,
11
11
  )
12
+ from ngio.utils import NgioValueError
12
13
 
13
14
  T = TypeVar("T")
14
15
 
@@ -44,7 +45,7 @@ def transform_numpy_array(
44
45
  elif isinstance(operation, AxesSqueeze):
45
46
  array = np.squeeze(array, axis=operation.axes)
46
47
  else:
47
- raise ValueError(f"Unknown operation {operation}")
48
+ raise NgioValueError(f"Unknown operation {operation}")
48
49
  return array
49
50
 
50
51
 
@@ -59,5 +60,5 @@ def transform_dask_array(
59
60
  elif isinstance(operation, AxesSqueeze):
60
61
  array = da.squeeze(array, axis=operation.axes)
61
62
  else:
62
- raise ValueError(f"Unknown operation {operation}")
63
+ raise NgioValueError(f"Unknown operation {operation}")
63
64
  return array
@@ -57,6 +57,13 @@ class Dimensions:
57
57
  return 1
58
58
  return self._shape[index]
59
59
 
60
+ def has_axis(self, axis_name: str) -> bool:
61
+ """Return whether the axis exists."""
62
+ index = self._axes_mapper.get_axis(axis_name)
63
+ if index is None:
64
+ return False
65
+ return True
66
+
60
67
  def get_shape(self, axes_order: Collection[str]) -> tuple[int, ...]:
61
68
  """Return the shape in the given axes order."""
62
69
  transforms = self._axes_mapper.to_order(axes_order)
@@ -0,0 +1,158 @@
1
+ """Utilities to build masking regions of interest (ROIs)."""
2
+
3
+ import itertools
4
+
5
+ import dask
6
+ import dask.array as da
7
+ import dask.delayed
8
+ import numpy as np
9
+ import scipy.ndimage as ndi
10
+
11
+ from ngio.common._roi import RasterCooROI, WorldCooROI
12
+ from ngio.ome_zarr_meta import PixelSize
13
+ from ngio.utils import NgioValueError
14
+
15
+
16
+ def _compute_offsets(chunks):
17
+ """Given a chunks tuple, compute cumulative offsets for each axis.
18
+
19
+ Returns a list where each element is a list of offsets for that dimension.
20
+ """
21
+ offsets = []
22
+ for dim_chunks in chunks:
23
+ dim_offsets = [0]
24
+ for size in dim_chunks:
25
+ dim_offsets.append(dim_offsets[-1] + size)
26
+ offsets.append(dim_offsets)
27
+ return offsets
28
+
29
+
30
+ def _adjust_slices(slices, offset):
31
+ """Adjust slices to global coordinates using the provided offset."""
32
+ adjusted_slices = {}
33
+ for label, s in slices.items():
34
+ adjusted = tuple(
35
+ slice(s_dim.start + off, s_dim.stop + off)
36
+ for s_dim, off in zip(s, offset, strict=True)
37
+ )
38
+ adjusted_slices[label] = adjusted
39
+ return adjusted_slices
40
+
41
+
42
+ @dask.delayed
43
+ def _process_chunk(chunk, offset):
44
+ """Process a single chunk.
45
+
46
+ run ndi.find_objects and adjust the slices
47
+ to global coordinates using the provided offset.
48
+ """
49
+ local_slices = compute_slices(chunk)
50
+ local_slices = _adjust_slices(local_slices, offset)
51
+ return local_slices
52
+
53
+
54
+ def _merge_slices(
55
+ slice1: tuple[slice, ...], slice2: tuple[slice, ...]
56
+ ) -> tuple[slice, ...]:
57
+ """Merge two slices."""
58
+ merged = []
59
+ for s1, s2 in zip(slice1, slice2, strict=True):
60
+ start = min(s1.start, s2.start)
61
+ stop = max(s1.stop, s2.stop)
62
+ merged.append(slice(start, stop))
63
+ return tuple(merged)
64
+
65
+
66
+ @dask.delayed
67
+ def _collect_slices(
68
+ local_slices: list[dict[int, tuple[slice, ...]]],
69
+ ) -> dict[int, tuple[slice]]:
70
+ """Collect the slices from the delayed results."""
71
+ global_slices = {}
72
+ for result in local_slices:
73
+ for label, s in result.items():
74
+ if label in global_slices:
75
+ global_slices[label] = _merge_slices(global_slices[label], s)
76
+ else:
77
+ global_slices[label] = s
78
+ return global_slices
79
+
80
+
81
+ def compute_slices(segmentation: np.ndarray) -> dict[int, tuple[slice, ...]]:
82
+ """Compute slices for each label in a segmentation.
83
+
84
+ Args:
85
+ segmentation (ndarray): The segmentation array.
86
+
87
+ Returns:
88
+ dict[int, tuple[slice]]: A dictionary with the label as key
89
+ and the slice as value.
90
+ """
91
+ slices = ndi.find_objects(segmentation)
92
+ slices_dict = {}
93
+ for label, s in enumerate(slices, start=1):
94
+ if s is None:
95
+ continue
96
+ else:
97
+ slices_dict[label] = s
98
+ return slices_dict
99
+
100
+
101
+ def lazy_compute_slices(segmentation: da.Array) -> dict[int, tuple[slice, ...]]:
102
+ """Compute slices for each label in a segmentation."""
103
+ global_offsets = _compute_offsets(segmentation.chunks)
104
+ delayed_chunks = segmentation.to_delayed()
105
+
106
+ grid_shape = tuple(len(c) for c in segmentation.chunks)
107
+
108
+ grid_indices = list(itertools.product(*[range(n) for n in grid_shape]))
109
+ delayed_results = []
110
+ for idx, chunk in zip(grid_indices, np.ravel(delayed_chunks), strict=True):
111
+ offset = tuple(global_offsets[dim][idx[dim]] for dim in range(len(idx)))
112
+ delayed_result = _process_chunk(chunk, offset)
113
+ delayed_results.append(delayed_result)
114
+
115
+ return _collect_slices(delayed_results).compute()
116
+
117
+
118
+ def compute_masking_roi(
119
+ segmentation: np.ndarray | da.Array, pixel_size: PixelSize
120
+ ) -> list[WorldCooROI]:
121
+ """Compute a ROIs for each label in a segmentation.
122
+
123
+ This function expects a 2D or 3D segmentation array.
124
+ And this function expects the axes order to be 'zyx' or 'yx'.
125
+ Other axes orders are not supported.
126
+
127
+ """
128
+ if segmentation.ndim not in [2, 3]:
129
+ raise NgioValueError("Only 2D and 3D segmentations are supported.")
130
+
131
+ if isinstance(segmentation, da.Array):
132
+ slices = lazy_compute_slices(segmentation)
133
+ else:
134
+ slices = compute_slices(segmentation)
135
+
136
+ rois = []
137
+ for label, slice_ in slices.items():
138
+ if len(slice_) == 2:
139
+ min_z, min_y, min_x = 0, slice_[0].start, slice_[1].start
140
+ max_z, max_y, max_x = 1, slice_[0].stop, slice_[1].stop
141
+ elif len(slice_) == 3:
142
+ min_z, min_y, min_x = slice_[0].start, slice_[1].start, slice_[2].start
143
+ max_z, max_y, max_x = slice_[0].stop, slice_[1].stop, slice_[2].stop
144
+ else:
145
+ raise ValueError("Invalid slice length.")
146
+ roi = RasterCooROI(
147
+ name=str(label),
148
+ x_length=max_x - min_x,
149
+ y_length=max_y - min_y,
150
+ z_length=max_z - min_z,
151
+ x=min_x,
152
+ y=min_y,
153
+ z=min_z,
154
+ )
155
+
156
+ roi = roi.to_world_coo_roi(pixel_size)
157
+ rois.append(roi)
158
+ return rois
ngio/common/_pyramid.py CHANGED
@@ -7,7 +7,12 @@ import numpy as np
7
7
  import zarr
8
8
 
9
9
  from ngio.common._zoom import _zoom_inputs_check, dask_zoom, numpy_zoom
10
- from ngio.utils import AccessModeLiteral, StoreOrGroup, open_group_wrapper
10
+ from ngio.utils import (
11
+ AccessModeLiteral,
12
+ NgioValueError,
13
+ StoreOrGroup,
14
+ open_group_wrapper,
15
+ )
11
16
 
12
17
 
13
18
  def _on_disk_numpy_zoom(
@@ -64,7 +69,7 @@ def _on_disk_coarsen(
64
69
  elif _order == 0:
65
70
  aggregation_function = np.max
66
71
  else:
67
- raise ValueError(
72
+ raise NgioValueError(
68
73
  f"Aggregation function must be provided for order {_order}"
69
74
  )
70
75
 
@@ -77,7 +82,7 @@ def _on_disk_coarsen(
77
82
  if factor.is_integer():
78
83
  coarsening_setup[i] = int(factor)
79
84
  else:
80
- raise ValueError(
85
+ raise NgioValueError(
81
86
  f"Coarsening factor must be an integer, got {factor} on axis {i}"
82
87
  )
83
88
 
@@ -103,13 +108,13 @@ def on_disk_zoom(
103
108
  mode (Literal["dask", "numpy", "coarsen"]): The mode to use. Defaults to "dask".
104
109
  """
105
110
  if not isinstance(source, zarr.Array):
106
- raise ValueError("source must be a zarr array")
111
+ raise NgioValueError("source must be a zarr array")
107
112
 
108
113
  if not isinstance(target, zarr.Array):
109
- raise ValueError("target must be a zarr array")
114
+ raise NgioValueError("target must be a zarr array")
110
115
 
111
116
  if source.dtype != target.dtype:
112
- raise ValueError("source and target must have the same dtype")
117
+ raise NgioValueError("source and target must have the same dtype")
113
118
 
114
119
  match mode:
115
120
  case "numpy":
@@ -122,7 +127,7 @@ def on_disk_zoom(
122
127
  target,
123
128
  )
124
129
  case _:
125
- raise ValueError("mode must be either 'dask', 'numpy' or 'coarsen'")
130
+ raise NgioValueError("mode must be either 'dask', 'numpy' or 'coarsen'")
126
131
 
127
132
 
128
133
  def _find_closest_arrays(
@@ -181,19 +186,19 @@ def init_empty_pyramid(
181
186
  ) -> None:
182
187
  # Return the an Image object
183
188
  if chunks is not None and len(chunks) != len(ref_shape):
184
- raise ValueError(
189
+ raise NgioValueError(
185
190
  "The shape and chunks must have the same number of dimensions."
186
191
  )
187
192
 
188
193
  if len(ref_shape) != len(scaling_factors):
189
- raise ValueError(
194
+ raise NgioValueError(
190
195
  "The shape and scaling factor must have the same number of dimensions."
191
196
  )
192
197
 
193
198
  root_group, _ = open_group_wrapper(store, mode=mode)
194
199
  for path in paths:
195
200
  if any(s < 1 for s in ref_shape):
196
- raise ValueError(
201
+ raise NgioValueError(
197
202
  "Level shape must be at least 1 on all dimensions. "
198
203
  f"Calculated shape: {ref_shape} at level {path}."
199
204
  )
@@ -218,6 +223,6 @@ def init_empty_pyramid(
218
223
  if chunks is None:
219
224
  chunks = new_arr.chunks
220
225
  if chunks is None:
221
- raise ValueError("Something went wrong with the chunks")
226
+ raise NgioValueError("Something went wrong with the chunks")
222
227
  chunks = [min(c, s) for c, s in zip(chunks, ref_shape, strict=True)]
223
228
  return None
ngio/common/_roi.py CHANGED
@@ -4,11 +4,14 @@ These are the interfaces bwteen the ROI tables / masking ROI tables and
4
4
  the ImageLikeHandler.
5
5
  """
6
6
 
7
+ from collections.abc import Iterable
8
+
7
9
  import numpy as np
8
10
  from pydantic import BaseModel, ConfigDict, Field
9
11
 
10
12
  from ngio.common._dimensions import Dimensions
11
13
  from ngio.ome_zarr_meta.ngio_specs import PixelSize, SpaceUnits
14
+ from ngio.utils import NgioValueError
12
15
 
13
16
 
14
17
  def _to_raster(value: float, pixel_size: float, max_shape: int) -> int:
@@ -56,6 +59,17 @@ class WorldCooROI(BaseModel):
56
59
  z_length=_to_raster(self.z_length, pixel_size.z, dim_z),
57
60
  )
58
61
 
62
+ def zoom(self, zoom_factor: float = 1) -> "WorldCooROI":
63
+ """Zoom the ROI by a factor.
64
+
65
+ Args:
66
+ zoom_factor: The zoom factor. If the zoom factor
67
+ is less than 1 the ROI will be zoomed in.
68
+ If the zoom factor is greater than 1 the ROI will be zoomed out.
69
+ If the zoom factor is 1 the ROI will not be changed.
70
+ """
71
+ return zoom_roi(self, zoom_factor)
72
+
59
73
 
60
74
  class RasterCooROI(BaseModel):
61
75
  """Region of interest (ROI) metadata."""
@@ -89,3 +103,63 @@ class RasterCooROI(BaseModel):
89
103
  "y": slice(self.y, self.y + self.y_length),
90
104
  "z": slice(self.z, self.z + self.z_length),
91
105
  }
106
+
107
+
108
+ def zoom_roi(roi: WorldCooROI, zoom_factor: float = 1) -> WorldCooROI:
109
+ """Zoom the ROI by a factor.
110
+
111
+ Args:
112
+ roi: The ROI to zoom.
113
+ zoom_factor: The zoom factor. If the zoom factor
114
+ is less than 1 the ROI will be zoomed in.
115
+ If the zoom factor is greater than 1 the ROI will be zoomed out.
116
+ If the zoom factor is 1 the ROI will not be changed.
117
+ """
118
+ if zoom_factor <= 0:
119
+ raise ValueError("Zoom factor must be greater than 0.")
120
+
121
+ # the zoom factor needs to be rescaled
122
+ # from the range [-1, inf) to [0, inf)
123
+ zoom_factor -= 1
124
+ diff_x = roi.x_length * zoom_factor
125
+ diff_y = roi.y_length * zoom_factor
126
+
127
+ new_x = max(roi.x - diff_x / 2, 0)
128
+ new_y = max(roi.y - diff_y / 2, 0)
129
+
130
+ new_roi = WorldCooROI(
131
+ name=roi.name,
132
+ x=new_x,
133
+ y=new_y,
134
+ z=roi.z,
135
+ x_length=roi.x_length + diff_x,
136
+ y_length=roi.y_length + diff_y,
137
+ z_length=roi.z_length,
138
+ unit=roi.unit,
139
+ )
140
+
141
+ return new_roi
142
+
143
+
144
+ def roi_to_slice_kwargs(
145
+ roi: WorldCooROI,
146
+ pixel_size: PixelSize,
147
+ dimensions: Dimensions,
148
+ **slice_kwargs: slice | int | Iterable[int],
149
+ ) -> dict[str, slice | int | Iterable[int]]:
150
+ """Convert a WorldCooROI to slice_kwargs."""
151
+ raster_roi = roi.to_raster_coo(
152
+ pixel_size=pixel_size, dimensions=dimensions
153
+ ).to_slices()
154
+
155
+ if not dimensions.has_axis(axis_name="z"):
156
+ raster_roi.pop("z")
157
+
158
+ for key in slice_kwargs.keys():
159
+ if key in raster_roi:
160
+ raise NgioValueError(
161
+ f"Key {key} is already in the slice_kwargs. "
162
+ "Ambiguous which one to use: "
163
+ f"{key}={slice_kwargs[key]} or roi_{key}={raster_roi[key]}"
164
+ )
165
+ return {**raster_roi, **slice_kwargs}
ngio/common/_slicer.py CHANGED
@@ -1,4 +1,3 @@
1
- # %%
2
1
  from collections.abc import Iterable
3
2
 
4
3
  import dask.array as da
@@ -69,7 +68,7 @@ def compute_and_slices(
69
68
  slice_ = _validate_slice(slice_, shape)
70
69
 
71
70
  elif not isinstance(slice_, slice):
72
- raise ValueError(
71
+ raise NgioValueError(
73
72
  f"Invalid slice definition {slice_} of type {type(slice_)}"
74
73
  )
75
74
  _slices[axis.on_disk_name] = slice_
ngio/common/_zoom.py CHANGED
@@ -5,6 +5,8 @@ import dask.array as da
5
5
  import numpy as np
6
6
  from scipy.ndimage import zoom as scipy_zoom
7
7
 
8
+ from ngio.utils import NgioValueError
9
+
8
10
 
9
11
  def _stacked_zoom(x, zoom_y, zoom_x, order=1, mode="grid-constant", grid_mode=True):
10
12
  *rest, yshape, xshape = x.shape
@@ -53,15 +55,15 @@ def _zoom_inputs_check(
53
55
  target_shape: tuple[int, ...] | None = None,
54
56
  ) -> tuple[np.ndarray, tuple[int, ...]]:
55
57
  if scale is None and target_shape is None:
56
- raise ValueError("Either scale or target_shape must be provided")
58
+ raise NgioValueError("Either scale or target_shape must be provided")
57
59
 
58
60
  if scale is not None and target_shape is not None:
59
- raise ValueError("Only one of scale or target_shape must be provided")
61
+ raise NgioValueError("Only one of scale or target_shape must be provided")
60
62
 
61
63
  if scale is None:
62
64
  assert target_shape is not None, "Target shape must be provided"
63
65
  if len(target_shape) != source_array.ndim:
64
- raise ValueError(
66
+ raise NgioValueError(
65
67
  "Target shape must have the "
66
68
  "same number of dimensions as "
67
69
  "the source array"
ngio/hcs/__init__.py CHANGED
@@ -1,60 +1,5 @@
1
1
  """OME-Zarr HCS objects models."""
2
2
 
3
- from typing import TYPE_CHECKING
3
+ from ngio.hcs.plate import OmeZarrPlate, create_empty_plate, open_omezarr_plate
4
4
 
5
- if TYPE_CHECKING:
6
- from ngio.images import OmeZarrContainer
7
-
8
-
9
- class OmeZarrPlate:
10
- """Placeholder for the OME-Zarr image object."""
11
-
12
- def __init__(self, *args, **kwargs):
13
- """Initialize the OME-Zarr plate."""
14
- raise NotImplementedError
15
-
16
- def wells(self) -> list[str]:
17
- """Return the wells."""
18
- raise NotImplementedError
19
-
20
- def columns(self) -> list[str]:
21
- """Return the number of columns."""
22
- raise NotImplementedError
23
-
24
- def rows(self) -> list[str]:
25
- """Return the number of rows."""
26
- raise NotImplementedError
27
-
28
- def get_omezarr_well(self, *args, **kwargs) -> "OmeZarrWell":
29
- """Return the OME-Zarr well."""
30
- raise NotImplementedError
31
-
32
- def get_omezarr_image(self, *args, **kwargs) -> "OmeZarrContainer":
33
- """Return the OME-Zarr image."""
34
- raise NotImplementedError
35
-
36
-
37
- class OmeZarrWell:
38
- """Placeholder for the Image object."""
39
-
40
- def __init__(self, *args, **kwargs):
41
- """Initialize the OME-Zarr well."""
42
- raise NotImplementedError
43
-
44
- def acquisitions(self) -> list[str]:
45
- """Return the acquisition."""
46
- raise NotImplementedError
47
-
48
- def get_ome_zarr_image(self, *args, **kwargs) -> "OmeZarrContainer":
49
- """Return the OME-Zarr image."""
50
- raise NotImplementedError
51
-
52
-
53
- def open_omezarr_plate(*args, **kwargs):
54
- """Open an OME-Zarr plate."""
55
- return OmeZarrPlate(*args, **kwargs)
56
-
57
-
58
- def open_omezarr_well(*args, **kwargs):
59
- """Open an OME-Zarr well."""
60
- return OmeZarrWell(*args, **kwargs)
5
+ __all__ = ["OmeZarrPlate", "create_empty_plate", "open_omezarr_plate"]