careamics 0.0.12__py3-none-any.whl → 0.0.14__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.
Potentially problematic release.
This version of careamics might be problematic. Click here for more details.
- careamics/careamist.py +4 -3
- careamics/cli/utils.py +1 -1
- careamics/config/algorithms/n2v_algorithm_model.py +1 -1
- careamics/config/architectures/unet_model.py +3 -0
- careamics/config/callback_model.py +23 -34
- careamics/config/configuration.py +47 -1
- careamics/config/configuration_factories.py +288 -23
- careamics/config/data/__init__.py +2 -0
- careamics/config/data/data_model.py +3 -3
- careamics/config/data/ng_data_model.py +381 -0
- careamics/config/data/patching_strategies/__init__.py +14 -0
- careamics/config/data/patching_strategies/_overlapping_patched_model.py +103 -0
- careamics/config/data/patching_strategies/_patched_model.py +56 -0
- careamics/config/data/patching_strategies/random_patching_model.py +21 -0
- careamics/config/data/patching_strategies/sequential_patching_model.py +25 -0
- careamics/config/data/patching_strategies/tiled_patching_model.py +40 -0
- careamics/config/data/patching_strategies/whole_patching_model.py +12 -0
- careamics/config/inference_model.py +6 -3
- careamics/config/support/supported_data.py +7 -0
- careamics/config/support/supported_patching_strategies.py +22 -0
- careamics/config/validators/validator_utils.py +4 -3
- careamics/dataset/dataset_utils/iterate_over_files.py +2 -2
- careamics/dataset/in_memory_dataset.py +2 -1
- careamics/dataset/iterable_dataset.py +2 -2
- careamics/dataset/iterable_pred_dataset.py +2 -2
- careamics/dataset/iterable_tiled_pred_dataset.py +2 -2
- careamics/dataset/patching/patching.py +3 -2
- careamics/dataset/tiling/lvae_tiled_patching.py +16 -6
- careamics/dataset/tiling/tiled_patching.py +2 -1
- careamics/dataset_ng/dataset.py +46 -50
- careamics/dataset_ng/demos/bsd68_demo.ipynb +28 -23
- careamics/dataset_ng/demos/care_U2OS_demo.ipynb +1 -1
- careamics/dataset_ng/demos/demo_custom_image_stack.ipynb +1 -1
- careamics/dataset_ng/demos/demo_datamodule.ipynb +50 -46
- careamics/dataset_ng/demos/demo_dataset.ipynb +32 -49
- careamics/dataset_ng/factory.py +58 -15
- careamics/dataset_ng/legacy_interoperability.py +3 -1
- careamics/dataset_ng/patch_extractor/demo_custom_image_stack_loader.py +1 -1
- careamics/dataset_ng/patch_extractor/image_stack/__init__.py +2 -0
- careamics/dataset_ng/patch_extractor/image_stack/czi_image_stack.py +360 -0
- careamics/dataset_ng/patch_extractor/image_stack/in_memory_image_stack.py +1 -1
- careamics/dataset_ng/patch_extractor/patch_extractor_factory.py +43 -1
- careamics/dataset_ng/patching_strategies/random_patching.py +4 -2
- careamics/dataset_ng/patching_strategies/sequential_patching.py +5 -5
- careamics/dataset_ng/patching_strategies/tiling_strategy.py +2 -1
- careamics/file_io/read/get_func.py +2 -1
- careamics/lightning/dataset_ng/__init__.py +1 -0
- careamics/lightning/dataset_ng/data_module.py +218 -28
- careamics/lightning/dataset_ng/lightning_modules/care_module.py +44 -5
- careamics/lightning/dataset_ng/lightning_modules/n2v_module.py +42 -3
- careamics/lightning/dataset_ng/lightning_modules/unet_module.py +73 -4
- careamics/lightning/lightning_module.py +2 -1
- careamics/lightning/predict_data_module.py +2 -1
- careamics/lightning/train_data_module.py +2 -1
- careamics/losses/loss_factory.py +2 -1
- careamics/lvae_training/dataset/multicrop_dset.py +1 -1
- careamics/model_io/bioimage/bioimage_utils.py +1 -1
- careamics/model_io/bioimage/model_description.py +1 -1
- careamics/model_io/bmz_io.py +1 -1
- careamics/model_io/model_io_utils.py +2 -2
- careamics/models/activation.py +2 -1
- careamics/models/unet.py +16 -10
- careamics/prediction_utils/prediction_outputs.py +1 -1
- careamics/prediction_utils/stitch_prediction.py +1 -1
- careamics/transforms/n2v_manipulate_torch.py +15 -9
- careamics/transforms/pixel_manipulation_torch.py +59 -92
- careamics/utils/lightning_utils.py +2 -2
- careamics/utils/metrics.py +2 -1
- careamics/utils/torch_utils.py +23 -0
- {careamics-0.0.12.dist-info → careamics-0.0.14.dist-info}/METADATA +10 -9
- {careamics-0.0.12.dist-info → careamics-0.0.14.dist-info}/RECORD +74 -63
- {careamics-0.0.12.dist-info → careamics-0.0.14.dist-info}/WHEEL +0 -0
- {careamics-0.0.12.dist-info → careamics-0.0.14.dist-info}/entry_points.txt +0 -0
- {careamics-0.0.12.dist-info → careamics-0.0.14.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from collections.abc import Sequence
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import TYPE_CHECKING, Any, Literal
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
from numpy.typing import NDArray
|
|
10
|
+
|
|
11
|
+
try:
|
|
12
|
+
from pylibCZIrw.czi import CziReader, Rectangle, open_czi
|
|
13
|
+
|
|
14
|
+
pyczi_available = True
|
|
15
|
+
except ImportError:
|
|
16
|
+
pyczi_available = False
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
try:
|
|
20
|
+
from pylibCZIrw.czi import CziReader, Rectangle, open_czi
|
|
21
|
+
except ImportError:
|
|
22
|
+
CziReader = Rectangle = open_czi = None # type: ignore
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class CziImageStack:
|
|
26
|
+
"""
|
|
27
|
+
A class for extracting patches from an image stack that is stored as a CZI file.
|
|
28
|
+
|
|
29
|
+
Parameters
|
|
30
|
+
----------
|
|
31
|
+
data_path : str or Path
|
|
32
|
+
Path to the CZI file.
|
|
33
|
+
|
|
34
|
+
scene : int, optional
|
|
35
|
+
Index of the scene to extract.
|
|
36
|
+
|
|
37
|
+
A single CZI file can contain multiple "scenes", which are stored alongside each
|
|
38
|
+
other at different coordinates in the image plane, often separated by empty
|
|
39
|
+
space. Specifying this argument will read only the single scene with that index
|
|
40
|
+
from the file. Think of it as cropping the CZI file to the region where that
|
|
41
|
+
scene is located.
|
|
42
|
+
|
|
43
|
+
If no scene index is specified, the entire image will be read. In case it
|
|
44
|
+
contains multiple scenes, they will all be present in the resulting image.
|
|
45
|
+
This is usually not desirable due to the empty space between them.
|
|
46
|
+
In general, only omit this argument or set it to `None` if you know that
|
|
47
|
+
your CZI file does not contain any scenes.
|
|
48
|
+
|
|
49
|
+
The static function :py:meth:`get_bounding_rectangles` can be used to find out
|
|
50
|
+
how many scenes a given file contains and what their bounding rectangles are.
|
|
51
|
+
|
|
52
|
+
The scene can also be provided as part of `data_path` by appending an `"@"`
|
|
53
|
+
followed by the scene index to the filename.
|
|
54
|
+
|
|
55
|
+
depth_axis : {"none", "Z", "T"}, default: "none"
|
|
56
|
+
Which axis to use as depth-axis for providing 3-D patches.
|
|
57
|
+
|
|
58
|
+
- `"none"`: Only provide 2-D patches. If a Z or T dimension is present in the
|
|
59
|
+
data, they will be combined into the sample dimension `S`.
|
|
60
|
+
- `"Z"`: Use the Z-axis as depth-axis. If a T axis is present as well, it will
|
|
61
|
+
be merged into the sample dimensions `S`.
|
|
62
|
+
- `"T"`: Use the T-axis as depth-axis. If a Z axis is present as well, it will
|
|
63
|
+
be merged into the sample dimensions `S`.
|
|
64
|
+
|
|
65
|
+
Attributes
|
|
66
|
+
----------
|
|
67
|
+
source : Path
|
|
68
|
+
Path to the CZI file, including the scene index if specified.
|
|
69
|
+
data_path : Path
|
|
70
|
+
Path to the CZI file without scene index.
|
|
71
|
+
scene : int or None
|
|
72
|
+
Index of the scene to extract, or None if not specified.
|
|
73
|
+
data_shape : Sequence[int]
|
|
74
|
+
The shape of the data in the order `(SC(Z)YX)`.
|
|
75
|
+
axes : str
|
|
76
|
+
The axes in the CZI file corresponding to the dimensions in `data_shape`.
|
|
77
|
+
The following values can occur:
|
|
78
|
+
|
|
79
|
+
- "SCZYX" for 3-D volumes if `depth_axis` is `"Z"`.
|
|
80
|
+
- "SCTYX" for time-series if `depth_axis` is `"T"`.
|
|
81
|
+
- "SCYX" if `depth_axis` is `"none"`.
|
|
82
|
+
|
|
83
|
+
The axis `S` (sample) is the only one not mapping one-to-one to an axis in the
|
|
84
|
+
CZI file but combines all remaining axes present in the file into one.
|
|
85
|
+
|
|
86
|
+
Examples
|
|
87
|
+
--------
|
|
88
|
+
Create an image stack for the first scene in a CZI file:
|
|
89
|
+
>>> stack = CziImageStack("path/to/file.czi", scene=0) # doctest: +SKIP
|
|
90
|
+
|
|
91
|
+
Alternatively, the scene index can also be provided as part of the filename.
|
|
92
|
+
This is mainly intended for re-creating an image stack from the `source` property:
|
|
93
|
+
>>> stack = CziImageStack("path/to/file.czi@0") # doctest: +SKIP
|
|
94
|
+
>>> stack2 = CziImageStack(stack.source) # doctest: +SKIP
|
|
95
|
+
|
|
96
|
+
If the CZI file contains a third dimension (Z or T) and you want to perform 3-D
|
|
97
|
+
denoising, you need to explicitly set `depth_axis` to `"Z"` or `"T"`:
|
|
98
|
+
>>> stack_2d = CziImageStack("path/to/file.czi", scene=0) # doctest: +SKIP
|
|
99
|
+
>>> stack_2d.axes, stack_2d.data_shape # doctest: +SKIP
|
|
100
|
+
('SCYX', [40, 1, 512, 512])
|
|
101
|
+
>>> stack_3d = CziImageStack( # doctest: +SKIP
|
|
102
|
+
... "path/to/file.czi", scene=0, depth_axis="Z"
|
|
103
|
+
... )
|
|
104
|
+
>>> stack_3d.axes, stack_3d.data_shape # doctest: +SKIP
|
|
105
|
+
('SCZYX', [4, 1, 10, 512, 512])
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
def __init__(
|
|
109
|
+
self,
|
|
110
|
+
data_path: str | Path,
|
|
111
|
+
scene: int | None = None,
|
|
112
|
+
depth_axis: Literal["none", "Z", "T"] = "none",
|
|
113
|
+
) -> None:
|
|
114
|
+
if not pyczi_available:
|
|
115
|
+
raise ImportError(
|
|
116
|
+
"The CZI image stack requires the `pylibCZIrw` package to be installed."
|
|
117
|
+
" Please install it with `pip install careamics[czi]`."
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
_data_path = Path(data_path)
|
|
121
|
+
|
|
122
|
+
# Check for scene encoded in filename.
|
|
123
|
+
# Normally, file path and scene should be provided as separate arguments but
|
|
124
|
+
# we would also like to support using the `source` property to re-create the
|
|
125
|
+
# CZI image stack. In this case, the scene index is encoded in the file path.
|
|
126
|
+
scene_matches = re.match(r"^(.*)@(\d+)$", _data_path.name)
|
|
127
|
+
if scene_matches:
|
|
128
|
+
if scene is not None:
|
|
129
|
+
raise ValueError(
|
|
130
|
+
f"Scene index is specified in the filename ({_data_path.name}) and "
|
|
131
|
+
f"as an argument ({scene}). Please specify only one."
|
|
132
|
+
)
|
|
133
|
+
_data_path = _data_path.parent / scene_matches.group(1)
|
|
134
|
+
scene = int(scene_matches.group(2))
|
|
135
|
+
|
|
136
|
+
# Set variables
|
|
137
|
+
self.data_path = _data_path
|
|
138
|
+
self.scene = scene
|
|
139
|
+
self._depth_axis = depth_axis
|
|
140
|
+
|
|
141
|
+
# Open CZI file
|
|
142
|
+
self._czi = CziReader(str(self.data_path))
|
|
143
|
+
|
|
144
|
+
# Determine metadata
|
|
145
|
+
self.axes, self.data_shape, self._bounding_rectangle, self._sample_axes = (
|
|
146
|
+
self._get_shape()
|
|
147
|
+
)
|
|
148
|
+
self.data_dtype = np.float32
|
|
149
|
+
|
|
150
|
+
def __del__(self):
|
|
151
|
+
if hasattr(self, "_czi"):
|
|
152
|
+
# Close CZI file
|
|
153
|
+
self._czi.close()
|
|
154
|
+
|
|
155
|
+
def __getstate__(self) -> dict[str, Any]:
|
|
156
|
+
# Remove CziReader object from state to avoid pickling issues
|
|
157
|
+
state = self.__dict__.copy()
|
|
158
|
+
del state["_czi"]
|
|
159
|
+
return state
|
|
160
|
+
|
|
161
|
+
def __setstate__(self, state: dict[str, Any]) -> None:
|
|
162
|
+
# Reopen CZI file after unpickling
|
|
163
|
+
self.__dict__.update(state)
|
|
164
|
+
self._czi = CziReader(str(self.data_path))
|
|
165
|
+
|
|
166
|
+
# TODO: we append the scene index to the file name
|
|
167
|
+
# - not sure if this is a good approach
|
|
168
|
+
@property
|
|
169
|
+
def source(self) -> Path:
|
|
170
|
+
filename = self.data_path.name
|
|
171
|
+
if self.scene is not None:
|
|
172
|
+
filename = f"{filename}@{self.scene}"
|
|
173
|
+
return self.data_path.parent / filename
|
|
174
|
+
|
|
175
|
+
def extract_patch(
|
|
176
|
+
self, sample_idx: int, coords: Sequence[int], patch_size: Sequence[int]
|
|
177
|
+
) -> NDArray:
|
|
178
|
+
# Determine 3rd dimension (T, Z or none)
|
|
179
|
+
if len(coords) == 3:
|
|
180
|
+
if len(self.axes) != 5:
|
|
181
|
+
raise ValueError(
|
|
182
|
+
f"Requested a 3D patch from a 2D image stack with axes {self.axes}."
|
|
183
|
+
)
|
|
184
|
+
third_dim = self.axes[2]
|
|
185
|
+
third_dim_offset, third_dim_size = coords[0], patch_size[0]
|
|
186
|
+
else:
|
|
187
|
+
if len(self.axes) != 4:
|
|
188
|
+
raise ValueError(
|
|
189
|
+
f"Requested a 2D patch from a 3D image stack with axes {self.axes}."
|
|
190
|
+
)
|
|
191
|
+
third_dim = None
|
|
192
|
+
third_dim_offset, third_dim_size = 0, 1
|
|
193
|
+
|
|
194
|
+
# Set up ROI to extract from each plane as (x, y, w, h)
|
|
195
|
+
roi = (
|
|
196
|
+
self._bounding_rectangle.x + coords[-1],
|
|
197
|
+
self._bounding_rectangle.y + coords[-2],
|
|
198
|
+
patch_size[-1],
|
|
199
|
+
patch_size[-2],
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
# Create output array of shape (C, Z, Y, X)
|
|
203
|
+
patch = np.empty(
|
|
204
|
+
(self.data_shape[1], third_dim_size, *patch_size[-2:]), dtype=np.float32
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
# Set up plane to index `sample_idx`
|
|
208
|
+
sample_shape = list(self._sample_axes.values())
|
|
209
|
+
sample_indices = np.unravel_index(sample_idx, sample_shape)
|
|
210
|
+
plane = {
|
|
211
|
+
dimension: int(index)
|
|
212
|
+
for dimension, index in zip(
|
|
213
|
+
self._sample_axes.keys(), sample_indices, strict=False
|
|
214
|
+
)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
# Read XY planes sequentially
|
|
218
|
+
for channel in range(self.data_shape[1]):
|
|
219
|
+
for third_dim_index in range(third_dim_size):
|
|
220
|
+
plane["C"] = channel
|
|
221
|
+
if third_dim is not None:
|
|
222
|
+
plane[third_dim] = third_dim_offset + third_dim_index
|
|
223
|
+
extracted_roi = self._czi.read(roi=roi, plane=plane, scene=self.scene)
|
|
224
|
+
if extracted_roi.ndim == 3:
|
|
225
|
+
if extracted_roi.shape[-1] > 1:
|
|
226
|
+
raise ValueError(
|
|
227
|
+
"CZI files with RGB channels are currently not supported."
|
|
228
|
+
)
|
|
229
|
+
extracted_roi = extracted_roi.squeeze(-1)
|
|
230
|
+
patch[channel, third_dim_index] = extracted_roi
|
|
231
|
+
|
|
232
|
+
# Remove dummy 3rd dimension for 2-D data
|
|
233
|
+
if third_dim is None:
|
|
234
|
+
patch = patch.squeeze(1)
|
|
235
|
+
|
|
236
|
+
return patch
|
|
237
|
+
|
|
238
|
+
def _get_shape(self) -> tuple[str, list[int], Rectangle, dict[str, int]]:
|
|
239
|
+
"""Determines the shape of the selected scene.
|
|
240
|
+
|
|
241
|
+
Returns
|
|
242
|
+
-------
|
|
243
|
+
axes : str
|
|
244
|
+
String specifying the axis order. Examples:
|
|
245
|
+
|
|
246
|
+
- "SCZYX" for 3-D volumes if `depth_axis` is `"Z"`.
|
|
247
|
+
- "SCTYX" for time-series if `depth_axis` is `"T"`.
|
|
248
|
+
- "SCYX" if `depth_axis` is `"none"`.
|
|
249
|
+
|
|
250
|
+
The axis `S` is the sample dimension and combines all remaining axes
|
|
251
|
+
present in the data.
|
|
252
|
+
|
|
253
|
+
shape : list[int]
|
|
254
|
+
The size of each axis, in the order listed in `axes`.
|
|
255
|
+
|
|
256
|
+
bounding_rectangle : Rectangle
|
|
257
|
+
The bounding rectangle of the scene in pixels. The rectangle is
|
|
258
|
+
defined by its top-left corner (x, y) and its width and height (w, h).
|
|
259
|
+
|
|
260
|
+
sample_axes : dict[str, int]
|
|
261
|
+
A dictionary with information about the remaining axes used for the
|
|
262
|
+
sample dimension.
|
|
263
|
+
The keys are the axis names (e.g., "T", "Z") and the values are their
|
|
264
|
+
respective sizes.
|
|
265
|
+
"""
|
|
266
|
+
# Get CZI dimensions
|
|
267
|
+
total_bbox = self._czi.total_bounding_box_no_pyramid
|
|
268
|
+
if self.scene is None:
|
|
269
|
+
bounding_rectangle = self._czi.total_bounding_rectangle_no_pyramid
|
|
270
|
+
else:
|
|
271
|
+
bounding_rectangle = self._czi.scenes_bounding_rectangle_no_pyramid[
|
|
272
|
+
self.scene
|
|
273
|
+
]
|
|
274
|
+
|
|
275
|
+
# Determine if T and Z axis are present
|
|
276
|
+
# Note: An axis of size 1 is as good as no axis since we cannot use it for 3-D
|
|
277
|
+
# denoising.
|
|
278
|
+
has_time = "T" in total_bbox and (total_bbox["T"][1] - total_bbox["T"][0]) > 1
|
|
279
|
+
has_depth = "Z" in total_bbox and (total_bbox["Z"][1] - total_bbox["Z"][0]) > 1
|
|
280
|
+
|
|
281
|
+
# Determine axis order depending on `depth_axis`
|
|
282
|
+
if self._depth_axis == "Z":
|
|
283
|
+
axes = "SCZYX"
|
|
284
|
+
if not has_depth:
|
|
285
|
+
raise RuntimeError(
|
|
286
|
+
f"The CZI file {self.data_path} does not contain a Z axis to use "
|
|
287
|
+
'for 3-D denoising. Consider setting `axes="YX"` or '
|
|
288
|
+
'`depth_axis="none"` to perform 2-D denoising instead.'
|
|
289
|
+
)
|
|
290
|
+
elif self._depth_axis == "T":
|
|
291
|
+
axes = "SCTYX"
|
|
292
|
+
if not has_time:
|
|
293
|
+
raise RuntimeError(
|
|
294
|
+
f"The CZI file {self.data_path} does not contain a T axis to use "
|
|
295
|
+
'for 3-D denoising. Consider setting `axes="YX"` or '
|
|
296
|
+
'`depth_axis="none"` to perform 2-D denoising instead.'
|
|
297
|
+
)
|
|
298
|
+
else:
|
|
299
|
+
axes = "SCYX"
|
|
300
|
+
|
|
301
|
+
# Calculcate size of sample dimension S, combining all axes not used elsewhere.
|
|
302
|
+
# This could, for example, be a time axis. If we only perform 2-D denoising, a
|
|
303
|
+
# potentially present Z axis would also be used as sample dimension. If both,
|
|
304
|
+
# T and Z, are present, both need to be combined into the sample dimension.
|
|
305
|
+
# The same needs to be done to any other potentially present axis in the CZI
|
|
306
|
+
# file which is not a spatial or channel axis.
|
|
307
|
+
# The following code calculates the size of the combined sample axis.
|
|
308
|
+
sample_axes = {}
|
|
309
|
+
sample_size = 1
|
|
310
|
+
for dimension, (start, end) in total_bbox.items():
|
|
311
|
+
if dimension not in axes:
|
|
312
|
+
sample_axes[dimension] = end - start
|
|
313
|
+
sample_size *= end - start
|
|
314
|
+
|
|
315
|
+
# Determine data shape
|
|
316
|
+
shape = []
|
|
317
|
+
for dimension in axes:
|
|
318
|
+
if dimension == "S":
|
|
319
|
+
shape.append(sample_size)
|
|
320
|
+
elif dimension == "Y":
|
|
321
|
+
shape.append(bounding_rectangle.h)
|
|
322
|
+
elif dimension == "X":
|
|
323
|
+
shape.append(bounding_rectangle.w)
|
|
324
|
+
elif dimension in total_bbox:
|
|
325
|
+
shape.append(total_bbox[dimension][1] - total_bbox[dimension][0])
|
|
326
|
+
else:
|
|
327
|
+
shape.append(1)
|
|
328
|
+
|
|
329
|
+
return axes, shape, bounding_rectangle, sample_axes
|
|
330
|
+
|
|
331
|
+
@classmethod
|
|
332
|
+
def get_bounding_rectangles(
|
|
333
|
+
cls, czi: Path | str | CziReader
|
|
334
|
+
) -> dict[int | None, Rectangle]:
|
|
335
|
+
"""Gets the bounding rectangles of all scenes in a CZI file.
|
|
336
|
+
|
|
337
|
+
Parameters
|
|
338
|
+
----------
|
|
339
|
+
czi : Path or str or pyczi.CziReader
|
|
340
|
+
Path to the CZI file or an already opened file as CziReader object.
|
|
341
|
+
|
|
342
|
+
Returns
|
|
343
|
+
-------
|
|
344
|
+
dict[int | None, Rectangle]
|
|
345
|
+
A dictionary mapping scene indices to their bounding rectangles in the
|
|
346
|
+
format `(x, y, w, h)`.
|
|
347
|
+
If no scenes are present in the CZI file, the returned dictionary will
|
|
348
|
+
have only one entry with key `None`, whose bounding rectangle covers the
|
|
349
|
+
entire image.
|
|
350
|
+
"""
|
|
351
|
+
if not isinstance(czi, CziReader):
|
|
352
|
+
with open_czi(str(czi)) as czi_reader:
|
|
353
|
+
return cls.get_bounding_rectangles(czi_reader)
|
|
354
|
+
|
|
355
|
+
scenes_bounding_rectangle = czi.scenes_bounding_rectangle_no_pyramid
|
|
356
|
+
if len(scenes_bounding_rectangle) >= 1:
|
|
357
|
+
# Ensure keys are int | None for type compatibility
|
|
358
|
+
return {int(k): v for k, v in scenes_bounding_rectangle.items()}
|
|
359
|
+
else:
|
|
360
|
+
return {None: czi.total_bounding_rectangle_no_pyramid}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from collections.abc import Sequence
|
|
2
2
|
from pathlib import Path
|
|
3
|
-
from typing import Any
|
|
3
|
+
from typing import Any, Literal
|
|
4
4
|
|
|
5
5
|
from numpy.typing import NDArray
|
|
6
6
|
from typing_extensions import ParamSpec
|
|
@@ -9,6 +9,7 @@ from careamics.dataset_ng.patch_extractor import PatchExtractor
|
|
|
9
9
|
from careamics.file_io.read import ReadFunc
|
|
10
10
|
|
|
11
11
|
from .image_stack import (
|
|
12
|
+
CziImageStack,
|
|
12
13
|
GenericImageStack,
|
|
13
14
|
InMemoryImageStack,
|
|
14
15
|
ZarrImageStack,
|
|
@@ -95,6 +96,47 @@ def create_ome_zarr_extractor(
|
|
|
95
96
|
return PatchExtractor(image_stacks)
|
|
96
97
|
|
|
97
98
|
|
|
99
|
+
# CZI case
|
|
100
|
+
def create_czi_extractor(
|
|
101
|
+
source: Sequence[Path],
|
|
102
|
+
axes: str,
|
|
103
|
+
) -> PatchExtractor[CziImageStack]:
|
|
104
|
+
"""
|
|
105
|
+
Create a patch extractor from a sequence of CZI files.
|
|
106
|
+
|
|
107
|
+
If the CZI files contain multiple scenes, one patch extractor will be created for
|
|
108
|
+
each scene.
|
|
109
|
+
|
|
110
|
+
Parameters
|
|
111
|
+
----------
|
|
112
|
+
source: sequence of Path
|
|
113
|
+
The source files for the data.
|
|
114
|
+
axes: str
|
|
115
|
+
Specifies which axes of the data to use and how.
|
|
116
|
+
If this string ends with `"ZYX"` or `"TYX"`, the data will consist of 3-D
|
|
117
|
+
patches, using `Z` or `T` as third dimension, respectively.
|
|
118
|
+
If the string does not end with "ZYX", the data will consist of 2-D patches.
|
|
119
|
+
|
|
120
|
+
Returns
|
|
121
|
+
-------
|
|
122
|
+
PatchExtractor
|
|
123
|
+
"""
|
|
124
|
+
depth_axis: Literal["none", "Z", "T"] = "none"
|
|
125
|
+
if axes.endswith("TYX"):
|
|
126
|
+
depth_axis = "T"
|
|
127
|
+
elif axes.endswith("ZYX"):
|
|
128
|
+
depth_axis = "Z"
|
|
129
|
+
|
|
130
|
+
image_stacks: list[CziImageStack] = []
|
|
131
|
+
for path in source:
|
|
132
|
+
scene_rectangles = CziImageStack.get_bounding_rectangles(path)
|
|
133
|
+
image_stacks.extend(
|
|
134
|
+
CziImageStack(path, scene=scene, depth_axis=depth_axis)
|
|
135
|
+
for scene in scene_rectangles.keys()
|
|
136
|
+
)
|
|
137
|
+
return PatchExtractor(image_stacks)
|
|
138
|
+
|
|
139
|
+
|
|
98
140
|
# Custom file type case (loaded into memory)
|
|
99
141
|
def create_custom_file_extractor(
|
|
100
142
|
source: Sequence[Path],
|
|
@@ -302,7 +302,7 @@ def _generate_random_coords(
|
|
|
302
302
|
rng.integers(
|
|
303
303
|
np.zeros(len(patch_size), dtype=int),
|
|
304
304
|
np.array(spatial_shape) - np.array(patch_size),
|
|
305
|
-
endpoint=
|
|
305
|
+
endpoint=True,
|
|
306
306
|
dtype=int,
|
|
307
307
|
).tolist()
|
|
308
308
|
)
|
|
@@ -335,6 +335,8 @@ def _calc_n_patches(spatial_shape: Sequence[int], patch_size: Sequence[int]) ->
|
|
|
335
335
|
f"spatial dimensions {len(spatial_shape)}, for `patch_size={patch_size}` "
|
|
336
336
|
f"and `spatial_shape={spatial_shape}`."
|
|
337
337
|
)
|
|
338
|
-
patches_per_dim = [
|
|
338
|
+
patches_per_dim = [
|
|
339
|
+
np.ceil(s / p) for s, p in zip(spatial_shape, patch_size, strict=False)
|
|
340
|
+
]
|
|
339
341
|
total_patches = int(np.prod(patches_per_dim))
|
|
340
342
|
return total_patches
|
|
@@ -18,13 +18,13 @@ class SequentialPatchingStrategy:
|
|
|
18
18
|
self,
|
|
19
19
|
data_shapes: Sequence[Sequence[int]],
|
|
20
20
|
patch_size: Sequence[int],
|
|
21
|
-
|
|
21
|
+
overlaps: Optional[Sequence[int]] = None,
|
|
22
22
|
):
|
|
23
23
|
self.data_shapes = data_shapes
|
|
24
24
|
self.patch_size = patch_size
|
|
25
|
-
if
|
|
26
|
-
|
|
27
|
-
self.
|
|
25
|
+
if overlaps is None:
|
|
26
|
+
overlaps = [0] * len(patch_size)
|
|
27
|
+
self.overlaps = np.asarray(overlaps)
|
|
28
28
|
|
|
29
29
|
self.patch_specs: list[PatchSpecs] = self._initialize_patch_specs()
|
|
30
30
|
|
|
@@ -58,7 +58,7 @@ class SequentialPatchingStrategy:
|
|
|
58
58
|
data_spatial_shape = data_shape[-len(self.patch_size) :]
|
|
59
59
|
coords_list = [
|
|
60
60
|
self._compute_coords_1d(
|
|
61
|
-
self.patch_size[i], data_spatial_shape[i], self.
|
|
61
|
+
self.patch_size[i], data_spatial_shape[i], self.overlaps[i]
|
|
62
62
|
)
|
|
63
63
|
for i in range(len(self.patch_size))
|
|
64
64
|
]
|
|
@@ -80,7 +80,7 @@ class TilingStrategy:
|
|
|
80
80
|
|
|
81
81
|
# combine by using zip
|
|
82
82
|
all_coords, all_stitch_coords, all_crop_coords, all_crop_size = zip(
|
|
83
|
-
*axis_specs
|
|
83
|
+
*axis_specs, strict=False
|
|
84
84
|
)
|
|
85
85
|
# patches will be the same for each sample in a stack
|
|
86
86
|
for sample_idx in range(data_shape[0]):
|
|
@@ -90,6 +90,7 @@ class TilingStrategy:
|
|
|
90
90
|
itertools.product(*all_stitch_coords),
|
|
91
91
|
itertools.product(*all_crop_coords),
|
|
92
92
|
itertools.product(*all_crop_size),
|
|
93
|
+
strict=False,
|
|
93
94
|
):
|
|
94
95
|
tile_specs.append(
|
|
95
96
|
{
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Next-Generation DataModules for Careamics."""
|