ngio 0.3.5__py3-none-any.whl → 0.4.0__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.
- ngio/__init__.py +7 -2
- ngio/common/__init__.py +5 -52
- ngio/common/_dimensions.py +270 -55
- ngio/common/_masking_roi.py +38 -10
- ngio/common/_pyramid.py +51 -30
- ngio/common/_roi.py +269 -82
- ngio/common/_synt_images_utils.py +101 -0
- ngio/common/_zoom.py +49 -19
- ngio/experimental/__init__.py +5 -0
- ngio/experimental/iterators/__init__.py +15 -0
- ngio/experimental/iterators/_abstract_iterator.py +390 -0
- ngio/experimental/iterators/_feature.py +189 -0
- ngio/experimental/iterators/_image_processing.py +130 -0
- ngio/experimental/iterators/_mappers.py +48 -0
- ngio/experimental/iterators/_rois_utils.py +127 -0
- ngio/experimental/iterators/_segmentation.py +235 -0
- ngio/hcs/_plate.py +41 -36
- ngio/images/__init__.py +22 -1
- ngio/images/_abstract_image.py +403 -176
- ngio/images/_create.py +31 -15
- ngio/images/_create_synt_container.py +138 -0
- ngio/images/_image.py +452 -63
- ngio/images/_label.py +56 -30
- ngio/images/_masked_image.py +387 -129
- ngio/images/_ome_zarr_container.py +237 -67
- ngio/{common → images}/_table_ops.py +41 -41
- ngio/io_pipes/__init__.py +75 -0
- ngio/io_pipes/_io_pipes.py +361 -0
- ngio/io_pipes/_io_pipes_masked.py +488 -0
- ngio/io_pipes/_io_pipes_roi.py +152 -0
- ngio/io_pipes/_io_pipes_types.py +56 -0
- ngio/io_pipes/_match_shape.py +376 -0
- ngio/io_pipes/_ops_axes.py +344 -0
- ngio/io_pipes/_ops_slices.py +446 -0
- ngio/io_pipes/_ops_slices_utils.py +196 -0
- ngio/io_pipes/_ops_transforms.py +104 -0
- ngio/io_pipes/_zoom_transform.py +175 -0
- ngio/ome_zarr_meta/__init__.py +4 -2
- ngio/ome_zarr_meta/ngio_specs/__init__.py +4 -10
- ngio/ome_zarr_meta/ngio_specs/_axes.py +186 -175
- ngio/ome_zarr_meta/ngio_specs/_channels.py +55 -18
- ngio/ome_zarr_meta/ngio_specs/_dataset.py +48 -122
- ngio/ome_zarr_meta/ngio_specs/_ngio_hcs.py +3 -3
- ngio/ome_zarr_meta/ngio_specs/_ngio_image.py +38 -87
- ngio/ome_zarr_meta/ngio_specs/_pixel_size.py +17 -1
- ngio/ome_zarr_meta/v04/_v04_spec_utils.py +34 -31
- ngio/resources/20200812-CardiomyocyteDifferentiation14-Cycle1_B03/mask.png +0 -0
- ngio/resources/20200812-CardiomyocyteDifferentiation14-Cycle1_B03/nuclei.png +0 -0
- ngio/resources/20200812-CardiomyocyteDifferentiation14-Cycle1_B03/raw.jpg +0 -0
- ngio/resources/__init__.py +55 -0
- ngio/resources/resource_model.py +36 -0
- ngio/tables/backends/_abstract_backend.py +5 -6
- ngio/tables/backends/_anndata.py +1 -1
- ngio/tables/backends/_anndata_utils.py +3 -3
- ngio/tables/backends/_non_zarr_backends.py +1 -1
- ngio/tables/backends/_table_backends.py +0 -1
- ngio/tables/backends/_utils.py +3 -3
- ngio/tables/v1/_roi_table.py +165 -70
- ngio/transforms/__init__.py +5 -0
- ngio/transforms/_zoom.py +19 -0
- ngio/utils/__init__.py +2 -3
- ngio/utils/_datasets.py +5 -0
- ngio/utils/_logger.py +19 -0
- ngio/utils/_zarr_utils.py +6 -6
- {ngio-0.3.5.dist-info → ngio-0.4.0.dist-info}/METADATA +16 -14
- ngio-0.4.0.dist-info/RECORD +85 -0
- ngio/common/_array_pipe.py +0 -288
- ngio/common/_axes_transforms.py +0 -64
- ngio/common/_common_types.py +0 -5
- ngio/common/_slicer.py +0 -96
- ngio-0.3.5.dist-info/RECORD +0 -61
- {ngio-0.3.5.dist-info → ngio-0.4.0.dist-info}/WHEEL +0 -0
- {ngio-0.3.5.dist-info → ngio-0.4.0.dist-info}/licenses/LICENSE +0 -0
ngio/common/_pyramid.py
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
import math
|
|
2
|
-
from collections.abc import
|
|
2
|
+
from collections.abc import Callable, Sequence
|
|
3
3
|
from typing import Literal
|
|
4
4
|
|
|
5
5
|
import dask.array as da
|
|
6
6
|
import numpy as np
|
|
7
7
|
import zarr
|
|
8
|
+
from zarr.types import DIMENSION_SEPARATOR
|
|
8
9
|
|
|
9
|
-
from ngio.common._zoom import
|
|
10
|
+
from ngio.common._zoom import (
|
|
11
|
+
InterpolationOrder,
|
|
12
|
+
_zoom_inputs_check,
|
|
13
|
+
dask_zoom,
|
|
14
|
+
numpy_zoom,
|
|
15
|
+
)
|
|
10
16
|
from ngio.utils import (
|
|
11
17
|
AccessModeLiteral,
|
|
12
18
|
NgioValueError,
|
|
@@ -18,7 +24,7 @@ from ngio.utils import (
|
|
|
18
24
|
def _on_disk_numpy_zoom(
|
|
19
25
|
source: zarr.Array,
|
|
20
26
|
target: zarr.Array,
|
|
21
|
-
order:
|
|
27
|
+
order: InterpolationOrder,
|
|
22
28
|
) -> None:
|
|
23
29
|
target[...] = numpy_zoom(source[...], target_shape=target.shape, order=order)
|
|
24
30
|
|
|
@@ -26,7 +32,7 @@ def _on_disk_numpy_zoom(
|
|
|
26
32
|
def _on_disk_dask_zoom(
|
|
27
33
|
source: zarr.Array,
|
|
28
34
|
target: zarr.Array,
|
|
29
|
-
order:
|
|
35
|
+
order: InterpolationOrder,
|
|
30
36
|
) -> None:
|
|
31
37
|
source_array = da.from_zarr(source)
|
|
32
38
|
target_array = dask_zoom(source_array, target_shape=target.shape, order=order)
|
|
@@ -39,18 +45,18 @@ def _on_disk_dask_zoom(
|
|
|
39
45
|
def _on_disk_coarsen(
|
|
40
46
|
source: zarr.Array,
|
|
41
47
|
target: zarr.Array,
|
|
42
|
-
|
|
43
|
-
aggregation_function:
|
|
48
|
+
order: InterpolationOrder = "linear",
|
|
49
|
+
aggregation_function: Callable | None = None,
|
|
44
50
|
) -> None:
|
|
45
51
|
"""Apply a coarsening operation from a source zarr array to a target zarr array.
|
|
46
52
|
|
|
47
53
|
Args:
|
|
48
54
|
source (zarr.Array): The source array to coarsen.
|
|
49
55
|
target (zarr.Array): The target array to save the coarsened result to.
|
|
50
|
-
|
|
56
|
+
order (InterpolationOrder): The order of interpolation is not really implemented
|
|
51
57
|
for coarsening, but it is kept for compatibility with the zoom function.
|
|
52
|
-
|
|
53
|
-
|
|
58
|
+
order="linear" -> linear interpolation ~ np.mean
|
|
59
|
+
order="nearest" -> nearest interpolation ~ np.max
|
|
54
60
|
aggregation_function (np.ufunc): The aggregation function to use.
|
|
55
61
|
"""
|
|
56
62
|
source_array = da.from_zarr(source)
|
|
@@ -64,13 +70,15 @@ def _on_disk_coarsen(
|
|
|
64
70
|
)
|
|
65
71
|
|
|
66
72
|
if aggregation_function is None:
|
|
67
|
-
if
|
|
73
|
+
if order == "linear":
|
|
68
74
|
aggregation_function = np.mean
|
|
69
|
-
elif
|
|
75
|
+
elif order == "nearest":
|
|
70
76
|
aggregation_function = np.max
|
|
77
|
+
elif order == "cubic":
|
|
78
|
+
raise NgioValueError("Cubic interpolation is not supported for coarsening.")
|
|
71
79
|
else:
|
|
72
80
|
raise NgioValueError(
|
|
73
|
-
f"Aggregation function must be provided for order {
|
|
81
|
+
f"Aggregation function must be provided for order {order}"
|
|
74
82
|
)
|
|
75
83
|
|
|
76
84
|
coarsening_setup = {}
|
|
@@ -96,7 +104,7 @@ def _on_disk_coarsen(
|
|
|
96
104
|
def on_disk_zoom(
|
|
97
105
|
source: zarr.Array,
|
|
98
106
|
target: zarr.Array,
|
|
99
|
-
order:
|
|
107
|
+
order: InterpolationOrder = "linear",
|
|
100
108
|
mode: Literal["dask", "numpy", "coarsen"] = "dask",
|
|
101
109
|
) -> None:
|
|
102
110
|
"""Apply a zoom operation from a source zarr array to a target zarr array.
|
|
@@ -104,7 +112,7 @@ def on_disk_zoom(
|
|
|
104
112
|
Args:
|
|
105
113
|
source (zarr.Array): The source array to zoom.
|
|
106
114
|
target (zarr.Array): The target array to save the zoomed result to.
|
|
107
|
-
order (
|
|
115
|
+
order (InterpolationOrder): The order of interpolation. Defaults to "linear".
|
|
108
116
|
mode (Literal["dask", "numpy", "coarsen"]): The mode to use. Defaults to "dask".
|
|
109
117
|
"""
|
|
110
118
|
if not isinstance(source, zarr.Array):
|
|
@@ -132,7 +140,7 @@ def on_disk_zoom(
|
|
|
132
140
|
|
|
133
141
|
def _find_closest_arrays(
|
|
134
142
|
processed: list[zarr.Array], to_be_processed: list[zarr.Array]
|
|
135
|
-
) -> tuple[
|
|
143
|
+
) -> tuple[np.intp, np.intp]:
|
|
136
144
|
dist_matrix = np.zeros((len(processed), len(to_be_processed)))
|
|
137
145
|
for i, arr_to_proc in enumerate(to_be_processed):
|
|
138
146
|
for j, proc_arr in enumerate(processed):
|
|
@@ -147,13 +155,15 @@ def _find_closest_arrays(
|
|
|
147
155
|
)
|
|
148
156
|
)
|
|
149
157
|
|
|
150
|
-
|
|
158
|
+
indices = np.unravel_index(dist_matrix.argmin(), dist_matrix.shape)
|
|
159
|
+
assert len(indices) == 2, "Indices must be of length 2"
|
|
160
|
+
return indices
|
|
151
161
|
|
|
152
162
|
|
|
153
163
|
def consolidate_pyramid(
|
|
154
164
|
source: zarr.Array,
|
|
155
165
|
targets: list[zarr.Array],
|
|
156
|
-
order:
|
|
166
|
+
order: InterpolationOrder = "linear",
|
|
157
167
|
mode: Literal["dask", "numpy", "coarsen"] = "dask",
|
|
158
168
|
) -> None:
|
|
159
169
|
"""Consolidate the Zarr array."""
|
|
@@ -175,14 +185,25 @@ def consolidate_pyramid(
|
|
|
175
185
|
processed.append(target_image)
|
|
176
186
|
|
|
177
187
|
|
|
188
|
+
def _maybe_int(value: float | int) -> float | int:
|
|
189
|
+
"""Convert a float to an int if it is an integer."""
|
|
190
|
+
if isinstance(value, int):
|
|
191
|
+
return value
|
|
192
|
+
if value.is_integer():
|
|
193
|
+
return int(value)
|
|
194
|
+
return value
|
|
195
|
+
|
|
196
|
+
|
|
178
197
|
def init_empty_pyramid(
|
|
179
198
|
store: StoreOrGroup,
|
|
180
199
|
paths: list[str],
|
|
181
|
-
ref_shape:
|
|
182
|
-
scaling_factors:
|
|
183
|
-
chunks:
|
|
200
|
+
ref_shape: Sequence[int],
|
|
201
|
+
scaling_factors: Sequence[float],
|
|
202
|
+
chunks: Sequence[int] | None = None,
|
|
184
203
|
dtype: str = "uint16",
|
|
185
204
|
mode: AccessModeLiteral = "a",
|
|
205
|
+
dimension_separator: DIMENSION_SEPARATOR = "/",
|
|
206
|
+
compressor="default",
|
|
186
207
|
) -> None:
|
|
187
208
|
# Return the an Image object
|
|
188
209
|
if chunks is not None and len(chunks) != len(ref_shape):
|
|
@@ -198,6 +219,10 @@ def init_empty_pyramid(
|
|
|
198
219
|
"The shape and scaling factor must have the same number of dimensions."
|
|
199
220
|
)
|
|
200
221
|
|
|
222
|
+
# Ensure scaling factors are int if possible
|
|
223
|
+
# To reduce the risk of floating point issues
|
|
224
|
+
scaling_factors = [_maybe_int(s) for s in scaling_factors]
|
|
225
|
+
|
|
201
226
|
root_group = open_group_wrapper(store, mode=mode)
|
|
202
227
|
|
|
203
228
|
for path in paths:
|
|
@@ -211,22 +236,18 @@ def init_empty_pyramid(
|
|
|
211
236
|
shape=ref_shape,
|
|
212
237
|
dtype=dtype,
|
|
213
238
|
chunks=chunks,
|
|
214
|
-
dimension_separator=
|
|
239
|
+
dimension_separator=dimension_separator,
|
|
215
240
|
overwrite=True,
|
|
241
|
+
compressor=compressor,
|
|
216
242
|
)
|
|
217
243
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
if math.floor(s / sc) % 2 == 0:
|
|
222
|
-
_shape.append(math.floor(s / sc))
|
|
223
|
-
else:
|
|
224
|
-
_shape.append(math.ceil(s / sc))
|
|
244
|
+
_shape = [
|
|
245
|
+
math.floor(s / sc) for s, sc in zip(ref_shape, scaling_factors, strict=True)
|
|
246
|
+
]
|
|
225
247
|
ref_shape = _shape
|
|
226
248
|
|
|
227
249
|
if chunks is None:
|
|
228
250
|
chunks = new_arr.chunks
|
|
229
|
-
|
|
230
|
-
raise NgioValueError("Something went wrong with the chunks")
|
|
251
|
+
assert chunks is not None
|
|
231
252
|
chunks = [min(c, s) for c, s in zip(chunks, ref_shape, strict=True)]
|
|
232
253
|
return None
|
ngio/common/_roi.py
CHANGED
|
@@ -4,62 +4,242 @@ These are the interfaces bwteen the ROI tables / masking ROI tables and
|
|
|
4
4
|
the ImageLikeHandler.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
from
|
|
7
|
+
from typing import TypeVar
|
|
8
|
+
from warnings import warn
|
|
8
9
|
|
|
9
|
-
import
|
|
10
|
-
from pydantic import BaseModel, ConfigDict, Field
|
|
10
|
+
from pydantic import BaseModel, ConfigDict
|
|
11
11
|
|
|
12
12
|
from ngio.common._dimensions import Dimensions
|
|
13
13
|
from ngio.ome_zarr_meta.ngio_specs import DefaultSpaceUnit, PixelSize, SpaceUnits
|
|
14
14
|
from ngio.utils import NgioValueError
|
|
15
15
|
|
|
16
16
|
|
|
17
|
-
def _to_raster(value: float,
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
return max(0, min(round_value, max_shape))
|
|
17
|
+
def _to_raster(value: float, length: float, pixel_size: float) -> tuple[float, float]:
|
|
18
|
+
raster_value = value / pixel_size
|
|
19
|
+
raster_length = length / pixel_size
|
|
20
|
+
return raster_value, raster_length
|
|
22
21
|
|
|
23
22
|
|
|
24
|
-
def
|
|
23
|
+
def _to_slice(start: float | None, length: float | None) -> slice:
|
|
24
|
+
if length is not None:
|
|
25
|
+
assert start is not None
|
|
26
|
+
end = start + length
|
|
27
|
+
else:
|
|
28
|
+
end = None
|
|
29
|
+
return slice(start, end)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _to_world(value: int | float, pixel_size: float) -> float:
|
|
25
33
|
"""Convert to world coordinates."""
|
|
26
34
|
return value * pixel_size
|
|
27
35
|
|
|
28
36
|
|
|
29
|
-
|
|
30
|
-
|
|
37
|
+
T = TypeVar("T", int, float)
|
|
38
|
+
|
|
31
39
|
|
|
32
|
-
|
|
40
|
+
class GenericRoi(BaseModel):
|
|
41
|
+
"""A generic Region of Interest (ROI) model."""
|
|
42
|
+
|
|
43
|
+
name: str | None = None
|
|
44
|
+
x: float
|
|
45
|
+
y: float
|
|
46
|
+
z: float | None = None
|
|
47
|
+
t: float | None = None
|
|
33
48
|
x_length: float
|
|
34
49
|
y_length: float
|
|
35
|
-
z_length: float =
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
unit: SpaceUnits | str | None = Field(DefaultSpaceUnit, repr=False)
|
|
50
|
+
z_length: float | None = None
|
|
51
|
+
t_length: float | None = None
|
|
52
|
+
label: int | None = None
|
|
53
|
+
unit: SpaceUnits | str | None = None
|
|
40
54
|
|
|
41
55
|
model_config = ConfigDict(extra="allow")
|
|
42
56
|
|
|
43
|
-
def
|
|
44
|
-
|
|
45
|
-
|
|
57
|
+
def intersection(self, other: "GenericRoi") -> "GenericRoi | None":
|
|
58
|
+
"""Calculate the intersection of this ROI with another ROI."""
|
|
59
|
+
return roi_intersection(self, other)
|
|
60
|
+
|
|
61
|
+
def _nice_str(self) -> str:
|
|
62
|
+
if self.t is not None:
|
|
63
|
+
t_str = f"t={self.t}->{self.t_length}"
|
|
64
|
+
else:
|
|
65
|
+
t_str = "t=None"
|
|
66
|
+
if self.z is not None:
|
|
67
|
+
z_str = f"z={self.z}->{self.z_length}"
|
|
68
|
+
else:
|
|
69
|
+
z_str = "z=None"
|
|
70
|
+
|
|
71
|
+
y_str = f"y={self.y}->{self.y_length}"
|
|
72
|
+
x_str = f"x={self.x}->{self.x_length}"
|
|
73
|
+
|
|
74
|
+
if self.label is not None:
|
|
75
|
+
label_str = f", label={self.label}"
|
|
76
|
+
else:
|
|
77
|
+
label_str = ""
|
|
78
|
+
cls_name = self.__class__.__name__
|
|
79
|
+
return f"{cls_name}({t_str}, {z_str}, {y_str}, {x_str}{label_str})"
|
|
80
|
+
|
|
81
|
+
def get_name(self) -> str:
|
|
82
|
+
"""Get the name of the ROI, or a default if not set."""
|
|
83
|
+
if self.name is not None:
|
|
84
|
+
return self.name
|
|
85
|
+
return self._nice_str()
|
|
86
|
+
|
|
87
|
+
def __repr__(self) -> str:
|
|
88
|
+
return self._nice_str()
|
|
89
|
+
|
|
90
|
+
def __str__(self) -> str:
|
|
91
|
+
return self._nice_str()
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _1d_intersection(
|
|
95
|
+
a: T | None, a_length: T | None, b: T | None, b_length: T | None
|
|
96
|
+
) -> tuple[T | None, T | None]:
|
|
97
|
+
"""Calculate the intersection of two 1D intervals."""
|
|
98
|
+
if a is None:
|
|
99
|
+
if b is not None and b_length is not None:
|
|
100
|
+
return b, b_length
|
|
101
|
+
return None, None
|
|
102
|
+
if b is None:
|
|
103
|
+
if a is not None and a_length is not None:
|
|
104
|
+
return a, a_length
|
|
105
|
+
return None, None
|
|
106
|
+
|
|
107
|
+
assert (
|
|
108
|
+
a is not None
|
|
109
|
+
and a_length is not None
|
|
110
|
+
and b is not None
|
|
111
|
+
and b_length is not None
|
|
112
|
+
)
|
|
113
|
+
start = max(a, b)
|
|
114
|
+
end = min(a + a_length, b + b_length)
|
|
115
|
+
length = end - start
|
|
116
|
+
|
|
117
|
+
if length <= 0:
|
|
118
|
+
return None, None
|
|
119
|
+
|
|
120
|
+
return start, length
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def roi_intersection(ref_roi: GenericRoi, other_roi: GenericRoi) -> GenericRoi | None:
|
|
124
|
+
"""Calculate the intersection of two ROIs."""
|
|
125
|
+
if (
|
|
126
|
+
ref_roi.unit is not None
|
|
127
|
+
and other_roi.unit is not None
|
|
128
|
+
and ref_roi.unit != other_roi.unit
|
|
129
|
+
):
|
|
130
|
+
raise NgioValueError(
|
|
131
|
+
"Cannot calculate intersection of ROIs with different units."
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
x, x_length = _1d_intersection(
|
|
135
|
+
ref_roi.x, ref_roi.x_length, other_roi.x, other_roi.x_length
|
|
136
|
+
)
|
|
137
|
+
if x is None and x_length is None:
|
|
138
|
+
# No intersection
|
|
139
|
+
return None
|
|
140
|
+
assert x is not None and x_length is not None
|
|
141
|
+
|
|
142
|
+
y, y_length = _1d_intersection(
|
|
143
|
+
ref_roi.y, ref_roi.y_length, other_roi.y, other_roi.y_length
|
|
144
|
+
)
|
|
145
|
+
if y is None and y_length is None:
|
|
146
|
+
# No intersection
|
|
147
|
+
return None
|
|
148
|
+
assert y is not None and y_length is not None
|
|
149
|
+
|
|
150
|
+
z, z_length = _1d_intersection(
|
|
151
|
+
ref_roi.z, ref_roi.z_length, other_roi.z, other_roi.z_length
|
|
152
|
+
)
|
|
153
|
+
t, t_length = _1d_intersection(
|
|
154
|
+
ref_roi.t, ref_roi.t_length, other_roi.t, other_roi.t_length
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
if (z_length is not None and z_length <= 0) or (
|
|
158
|
+
t_length is not None and t_length <= 0
|
|
159
|
+
):
|
|
160
|
+
# No intersection
|
|
161
|
+
return None
|
|
162
|
+
|
|
163
|
+
# Find label
|
|
164
|
+
if ref_roi.label is not None and other_roi.label is not None:
|
|
165
|
+
if ref_roi.label != other_roi.label:
|
|
166
|
+
raise NgioValueError(
|
|
167
|
+
"Cannot calculate intersection of ROIs with different labels."
|
|
168
|
+
)
|
|
169
|
+
label = ref_roi.label or other_roi.label
|
|
170
|
+
|
|
171
|
+
if ref_roi.name is not None and other_roi.name is not None:
|
|
172
|
+
name = f"{ref_roi.name}:{other_roi.name}"
|
|
173
|
+
else:
|
|
174
|
+
name = ref_roi.name or other_roi.name
|
|
175
|
+
|
|
176
|
+
cls_ref = ref_roi.__class__
|
|
177
|
+
return cls_ref(
|
|
178
|
+
name=name,
|
|
179
|
+
x=x,
|
|
180
|
+
y=y,
|
|
181
|
+
z=z,
|
|
182
|
+
t=t,
|
|
183
|
+
x_length=x_length,
|
|
184
|
+
y_length=y_length,
|
|
185
|
+
z_length=z_length,
|
|
186
|
+
t_length=t_length,
|
|
187
|
+
unit=ref_roi.unit,
|
|
188
|
+
label=label,
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
class Roi(GenericRoi):
|
|
193
|
+
x: float = 0.0
|
|
194
|
+
y: float = 0.0
|
|
195
|
+
unit: SpaceUnits | str | None = DefaultSpaceUnit
|
|
196
|
+
|
|
197
|
+
def to_roi_pixels(self, pixel_size: PixelSize) -> "RoiPixels":
|
|
46
198
|
"""Convert to raster coordinates."""
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
199
|
+
x, x_length = _to_raster(self.x, self.x_length, pixel_size.x)
|
|
200
|
+
y, y_length = _to_raster(self.y, self.y_length, pixel_size.y)
|
|
201
|
+
|
|
202
|
+
if self.z is None:
|
|
203
|
+
z, z_length = None, None
|
|
204
|
+
else:
|
|
205
|
+
assert self.z_length is not None
|
|
206
|
+
z, z_length = _to_raster(self.z, self.z_length, pixel_size.z)
|
|
207
|
+
|
|
208
|
+
if self.t is None:
|
|
209
|
+
t, t_length = None, None
|
|
210
|
+
else:
|
|
211
|
+
assert self.t_length is not None
|
|
212
|
+
t, t_length = _to_raster(self.t, self.t_length, pixel_size.t)
|
|
213
|
+
extra_dict = self.model_extra if self.model_extra else {}
|
|
51
214
|
|
|
52
215
|
return RoiPixels(
|
|
53
216
|
name=self.name,
|
|
54
|
-
x=
|
|
55
|
-
y=
|
|
56
|
-
z=
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
217
|
+
x=x,
|
|
218
|
+
y=y,
|
|
219
|
+
z=z,
|
|
220
|
+
t=t,
|
|
221
|
+
x_length=x_length,
|
|
222
|
+
y_length=y_length,
|
|
223
|
+
z_length=z_length,
|
|
224
|
+
t_length=t_length,
|
|
225
|
+
label=self.label,
|
|
226
|
+
unit=self.unit,
|
|
227
|
+
**extra_dict,
|
|
61
228
|
)
|
|
62
229
|
|
|
230
|
+
def to_pixel_roi(
|
|
231
|
+
self, pixel_size: PixelSize, dimensions: Dimensions | None = None
|
|
232
|
+
) -> "RoiPixels":
|
|
233
|
+
"""Convert to raster coordinates."""
|
|
234
|
+
warn(
|
|
235
|
+
"to_pixel_roi is deprecated and will be removed in a future release. "
|
|
236
|
+
"Use to_roi_pixels instead.",
|
|
237
|
+
DeprecationWarning,
|
|
238
|
+
stacklevel=2,
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
return self.to_roi_pixels(pixel_size=pixel_size)
|
|
242
|
+
|
|
63
243
|
def zoom(self, zoom_factor: float = 1) -> "Roi":
|
|
64
244
|
"""Zoom the ROI by a factor.
|
|
65
245
|
|
|
@@ -72,38 +252,67 @@ class Roi(BaseModel):
|
|
|
72
252
|
return zoom_roi(self, zoom_factor)
|
|
73
253
|
|
|
74
254
|
|
|
75
|
-
class RoiPixels(
|
|
76
|
-
"""Region of interest (ROI)
|
|
255
|
+
class RoiPixels(GenericRoi):
|
|
256
|
+
"""Region of interest (ROI) in pixel coordinates."""
|
|
77
257
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
z: int
|
|
82
|
-
x_length: int
|
|
83
|
-
y_length: int
|
|
84
|
-
z_length: int
|
|
85
|
-
model_config = ConfigDict(extra="allow")
|
|
258
|
+
x: float = 0
|
|
259
|
+
y: float = 0
|
|
260
|
+
unit: SpaceUnits | str | None = None
|
|
86
261
|
|
|
87
|
-
def to_roi(self, pixel_size: PixelSize) -> Roi:
|
|
88
|
-
"""Convert to
|
|
262
|
+
def to_roi(self, pixel_size: PixelSize) -> "Roi":
|
|
263
|
+
"""Convert to raster coordinates."""
|
|
264
|
+
x = _to_world(self.x, pixel_size.x)
|
|
265
|
+
x_length = _to_world(self.x_length, pixel_size.x)
|
|
266
|
+
y = _to_world(self.y, pixel_size.y)
|
|
267
|
+
y_length = _to_world(self.y_length, pixel_size.y)
|
|
268
|
+
|
|
269
|
+
if self.z is None:
|
|
270
|
+
z = None
|
|
271
|
+
else:
|
|
272
|
+
z = _to_world(self.z, pixel_size.z)
|
|
273
|
+
|
|
274
|
+
if self.z_length is None:
|
|
275
|
+
z_length = None
|
|
276
|
+
else:
|
|
277
|
+
z_length = _to_world(self.z_length, pixel_size.z)
|
|
278
|
+
|
|
279
|
+
if self.t is None:
|
|
280
|
+
t = None
|
|
281
|
+
else:
|
|
282
|
+
t = _to_world(self.t, pixel_size.t)
|
|
283
|
+
|
|
284
|
+
if self.t_length is None:
|
|
285
|
+
t_length = None
|
|
286
|
+
else:
|
|
287
|
+
t_length = _to_world(self.t_length, pixel_size.t)
|
|
288
|
+
|
|
289
|
+
extra_dict = self.model_extra if self.model_extra else {}
|
|
89
290
|
return Roi(
|
|
90
291
|
name=self.name,
|
|
91
|
-
x=
|
|
92
|
-
y=
|
|
93
|
-
z=
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
292
|
+
x=x,
|
|
293
|
+
y=y,
|
|
294
|
+
z=z,
|
|
295
|
+
t=t,
|
|
296
|
+
x_length=x_length,
|
|
297
|
+
y_length=y_length,
|
|
298
|
+
z_length=z_length,
|
|
299
|
+
t_length=t_length,
|
|
300
|
+
label=self.label,
|
|
301
|
+
unit=self.unit,
|
|
302
|
+
**extra_dict,
|
|
99
303
|
)
|
|
100
304
|
|
|
101
|
-
def
|
|
102
|
-
"""
|
|
305
|
+
def to_slicing_dict(self) -> dict[str, slice]:
|
|
306
|
+
"""Convert to a slicing dictionary."""
|
|
307
|
+
x_slice = _to_slice(self.x, self.x_length)
|
|
308
|
+
y_slice = _to_slice(self.y, self.y_length)
|
|
309
|
+
z_slice = _to_slice(self.z, self.z_length)
|
|
310
|
+
t_slice = _to_slice(self.t, self.t_length)
|
|
103
311
|
return {
|
|
104
|
-
"x":
|
|
105
|
-
"y":
|
|
106
|
-
"z":
|
|
312
|
+
"x": x_slice,
|
|
313
|
+
"y": y_slice,
|
|
314
|
+
"z": z_slice,
|
|
315
|
+
"t": t_slice,
|
|
107
316
|
}
|
|
108
317
|
|
|
109
318
|
|
|
@@ -118,7 +327,7 @@ def zoom_roi(roi: Roi, zoom_factor: float = 1) -> Roi:
|
|
|
118
327
|
If the zoom factor is 1 the ROI will not be changed.
|
|
119
328
|
"""
|
|
120
329
|
if zoom_factor <= 0:
|
|
121
|
-
raise
|
|
330
|
+
raise NgioValueError("Zoom factor must be greater than 0.")
|
|
122
331
|
|
|
123
332
|
# the zoom factor needs to be rescaled
|
|
124
333
|
# from the range [-1, inf) to [0, inf)
|
|
@@ -134,34 +343,12 @@ def zoom_roi(roi: Roi, zoom_factor: float = 1) -> Roi:
|
|
|
134
343
|
x=new_x,
|
|
135
344
|
y=new_y,
|
|
136
345
|
z=roi.z,
|
|
346
|
+
t=roi.t,
|
|
137
347
|
x_length=roi.x_length + diff_x,
|
|
138
348
|
y_length=roi.y_length + diff_y,
|
|
139
349
|
z_length=roi.z_length,
|
|
350
|
+
t_length=roi.t_length,
|
|
351
|
+
label=roi.label,
|
|
140
352
|
unit=roi.unit,
|
|
141
353
|
)
|
|
142
|
-
|
|
143
354
|
return new_roi
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
def roi_to_slice_kwargs(
|
|
147
|
-
roi: Roi,
|
|
148
|
-
pixel_size: PixelSize,
|
|
149
|
-
dimensions: Dimensions,
|
|
150
|
-
**slice_kwargs: slice | int | Iterable[int],
|
|
151
|
-
) -> dict[str, slice | int | Iterable[int]]:
|
|
152
|
-
"""Convert a WorldCooROI to slice_kwargs."""
|
|
153
|
-
raster_roi = roi.to_pixel_roi(
|
|
154
|
-
pixel_size=pixel_size, dimensions=dimensions
|
|
155
|
-
).to_slices()
|
|
156
|
-
|
|
157
|
-
if not dimensions.has_axis(axis_name="z"):
|
|
158
|
-
raster_roi.pop("z")
|
|
159
|
-
|
|
160
|
-
for key in slice_kwargs.keys():
|
|
161
|
-
if key in raster_roi:
|
|
162
|
-
raise NgioValueError(
|
|
163
|
-
f"Key {key} is already in the slice_kwargs. "
|
|
164
|
-
"Ambiguous which one to use: "
|
|
165
|
-
f"{key}={slice_kwargs[key]} or roi_{key}={raster_roi[key]}"
|
|
166
|
-
)
|
|
167
|
-
return {**raster_roi, **slice_kwargs}
|