careamics 0.0.11__py3-none-any.whl → 0.0.13__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 +24 -7
- 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 +55 -4
- careamics/config/configuration_factories.py +288 -23
- careamics/config/data/__init__.py +2 -0
- careamics/config/data/data_model.py +41 -4
- 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/optimizer_models.py +1 -3
- careamics/config/support/supported_data.py +7 -0
- careamics/config/support/supported_patching_strategies.py +22 -0
- careamics/config/training_model.py +0 -2
- 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/README.md +212 -0
- careamics/dataset_ng/dataset.py +229 -0
- careamics/dataset_ng/demos/bsd68_demo.ipynb +361 -0
- careamics/dataset_ng/demos/care_U2OS_demo.ipynb +330 -0
- careamics/dataset_ng/demos/demo_custom_image_stack.ipynb +734 -0
- careamics/dataset_ng/demos/demo_datamodule.ipynb +447 -0
- careamics/dataset_ng/{demo_dataset.ipynb → demos/demo_dataset.ipynb} +60 -53
- careamics/dataset_ng/{demo_patch_extractor.py → demos/demo_patch_extractor.py} +7 -9
- careamics/dataset_ng/demos/mouse_nuclei_demo.ipynb +292 -0
- careamics/dataset_ng/factory.py +451 -0
- careamics/dataset_ng/legacy_interoperability.py +170 -0
- careamics/dataset_ng/patch_extractor/__init__.py +3 -8
- careamics/dataset_ng/patch_extractor/demo_custom_image_stack_loader.py +7 -5
- careamics/dataset_ng/patch_extractor/image_stack/__init__.py +4 -1
- careamics/dataset_ng/patch_extractor/image_stack/czi_image_stack.py +360 -0
- careamics/dataset_ng/patch_extractor/image_stack/image_stack_protocol.py +5 -1
- careamics/dataset_ng/patch_extractor/image_stack/in_memory_image_stack.py +1 -1
- careamics/dataset_ng/patch_extractor/image_stack_loader.py +5 -75
- careamics/dataset_ng/patch_extractor/patch_extractor.py +5 -4
- careamics/dataset_ng/patch_extractor/patch_extractor_factory.py +114 -105
- careamics/dataset_ng/patching_strategies/__init__.py +6 -1
- careamics/dataset_ng/patching_strategies/patching_strategy_protocol.py +31 -0
- careamics/dataset_ng/patching_strategies/random_patching.py +5 -1
- careamics/dataset_ng/patching_strategies/sequential_patching.py +5 -5
- careamics/dataset_ng/patching_strategies/tiling_strategy.py +172 -0
- careamics/dataset_ng/patching_strategies/whole_sample.py +36 -0
- careamics/file_io/read/get_func.py +2 -1
- careamics/lightning/dataset_ng/__init__.py +1 -0
- careamics/lightning/dataset_ng/data_module.py +678 -0
- careamics/lightning/dataset_ng/lightning_modules/__init__.py +9 -0
- careamics/lightning/dataset_ng/lightning_modules/care_module.py +97 -0
- careamics/lightning/dataset_ng/lightning_modules/n2v_module.py +106 -0
- careamics/lightning/dataset_ng/lightning_modules/unet_module.py +212 -0
- careamics/lightning/lightning_module.py +5 -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/__init__.py +8 -3
- careamics/lvae_training/dataset/config.py +3 -3
- careamics/lvae_training/dataset/ms_dataset_ref.py +1067 -0
- careamics/lvae_training/dataset/multich_dataset.py +46 -17
- careamics/lvae_training/dataset/multicrop_dset.py +196 -0
- careamics/lvae_training/dataset/types.py +3 -3
- careamics/lvae_training/dataset/utils/index_manager.py +259 -0
- careamics/lvae_training/eval_utils.py +93 -3
- 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/prediction_utils/prediction_outputs.py +1 -1
- careamics/prediction_utils/stitch_prediction.py +1 -1
- careamics/transforms/compose.py +1 -0
- careamics/transforms/n2v_manipulate_torch.py +15 -9
- careamics/transforms/normalize.py +18 -7
- careamics/transforms/pixel_manipulation_torch.py +59 -92
- careamics/utils/lightning_utils.py +25 -11
- careamics/utils/metrics.py +2 -1
- careamics/utils/torch_utils.py +23 -0
- {careamics-0.0.11.dist-info → careamics-0.0.13.dist-info}/METADATA +12 -11
- {careamics-0.0.11.dist-info → careamics-0.0.13.dist-info}/RECORD +95 -69
- careamics/dataset_ng/dataset/__init__.py +0 -3
- careamics/dataset_ng/dataset/dataset.py +0 -184
- careamics/dataset_ng/demo_patch_extractor_factory.py +0 -37
- {careamics-0.0.11.dist-info → careamics-0.0.13.dist-info}/WHEEL +0 -0
- {careamics-0.0.11.dist-info → careamics-0.0.13.dist-info}/entry_points.txt +0 -0
- {careamics-0.0.11.dist-info → careamics-0.0.13.dist-info}/licenses/LICENSE +0 -0
|
@@ -16,12 +16,15 @@ class SupportedData(str, BaseEnum):
|
|
|
16
16
|
Array data.
|
|
17
17
|
TIFF : str
|
|
18
18
|
TIFF image data.
|
|
19
|
+
CZI : str
|
|
20
|
+
CZI image data.
|
|
19
21
|
CUSTOM : str
|
|
20
22
|
Custom data.
|
|
21
23
|
"""
|
|
22
24
|
|
|
23
25
|
ARRAY = "array"
|
|
24
26
|
TIFF = "tiff"
|
|
27
|
+
CZI = "czi"
|
|
25
28
|
CUSTOM = "custom"
|
|
26
29
|
# ZARR = "zarr"
|
|
27
30
|
|
|
@@ -78,6 +81,8 @@ class SupportedData(str, BaseEnum):
|
|
|
78
81
|
raise NotImplementedError(f"Data '{data_type}' is not loaded from a file.")
|
|
79
82
|
elif data_type == cls.TIFF:
|
|
80
83
|
return "*.tif*"
|
|
84
|
+
elif data_type == cls.CZI:
|
|
85
|
+
return "*.czi"
|
|
81
86
|
elif data_type == cls.CUSTOM:
|
|
82
87
|
return "*.*"
|
|
83
88
|
else:
|
|
@@ -102,6 +107,8 @@ class SupportedData(str, BaseEnum):
|
|
|
102
107
|
raise NotImplementedError(f"Data '{data_type}' is not loaded from a file.")
|
|
103
108
|
elif data_type == cls.TIFF:
|
|
104
109
|
return ".tiff"
|
|
110
|
+
elif data_type == cls.CZI:
|
|
111
|
+
return ".czi"
|
|
105
112
|
elif data_type == cls.CUSTOM:
|
|
106
113
|
# TODO: improve this message
|
|
107
114
|
raise NotImplementedError("Custom extensions have to be passed elsewhere.")
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""Patching strategies supported by Careamics."""
|
|
2
|
+
|
|
3
|
+
from careamics.utils import BaseEnum
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class SupportedPatchingStrategy(str, BaseEnum):
|
|
7
|
+
"""Patching strategies supported by Careamics."""
|
|
8
|
+
|
|
9
|
+
FIXED_RANDOM = "fixed_random"
|
|
10
|
+
"""Fixed random patching strategy, used during training."""
|
|
11
|
+
|
|
12
|
+
RANDOM = "random"
|
|
13
|
+
"""Random patching strategy, used during training."""
|
|
14
|
+
|
|
15
|
+
# SEQUENTIAL = "sequential"
|
|
16
|
+
# """Sequential patching strategy, used during training."""
|
|
17
|
+
|
|
18
|
+
TILED = "tiled"
|
|
19
|
+
"""Tiled patching strategy, used during prediction."""
|
|
20
|
+
|
|
21
|
+
WHOLE = "whole"
|
|
22
|
+
"""Whole image patching strategy, used during prediction."""
|
|
@@ -39,8 +39,6 @@ class TrainingConfig(BaseModel):
|
|
|
39
39
|
"""Maximum number of steps to train for. -1 means no limit."""
|
|
40
40
|
check_val_every_n_epoch: int = Field(default=1, ge=1)
|
|
41
41
|
"""Validation step frequency."""
|
|
42
|
-
enable_progress_bar: bool = Field(default=True)
|
|
43
|
-
"""Whether to enable the progress bar."""
|
|
44
42
|
accumulate_grad_batches: int = Field(default=1, ge=1)
|
|
45
43
|
"""Number of batches to accumulate gradients over before stepping the optimizer."""
|
|
46
44
|
gradient_clip_val: Optional[Union[int, float]] = None
|
|
@@ -4,7 +4,8 @@ Validator functions.
|
|
|
4
4
|
These functions are used to validate dimensions and axes of inputs.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
from
|
|
7
|
+
from collections.abc import Sequence
|
|
8
|
+
from typing import Optional
|
|
8
9
|
|
|
9
10
|
_AXES = "STCZYX"
|
|
10
11
|
|
|
@@ -79,14 +80,14 @@ def value_ge_than_8_power_of_2(
|
|
|
79
80
|
|
|
80
81
|
|
|
81
82
|
def patch_size_ge_than_8_power_of_2(
|
|
82
|
-
patch_list: Optional[
|
|
83
|
+
patch_list: Optional[Sequence[int]],
|
|
83
84
|
) -> None:
|
|
84
85
|
"""
|
|
85
86
|
Validate that each entry is greater or equal than 8 and a power of 2.
|
|
86
87
|
|
|
87
88
|
Parameters
|
|
88
89
|
----------
|
|
89
|
-
patch_list :
|
|
90
|
+
patch_list : Sequence of int, or None
|
|
90
91
|
Patch size.
|
|
91
92
|
|
|
92
93
|
Raises
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
from collections.abc import Generator
|
|
5
|
+
from collections.abc import Callable, Generator
|
|
6
6
|
from pathlib import Path
|
|
7
|
-
from typing import
|
|
7
|
+
from typing import Optional, Union
|
|
8
8
|
|
|
9
9
|
from numpy.typing import NDArray
|
|
10
10
|
from torch.utils.data import get_worker_info
|
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import copy
|
|
6
|
+
from collections.abc import Callable
|
|
6
7
|
from pathlib import Path
|
|
7
|
-
from typing import Any,
|
|
8
|
+
from typing import Any, Optional, Union
|
|
8
9
|
|
|
9
10
|
import numpy as np
|
|
10
11
|
from torch.utils.data import Dataset
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import copy
|
|
6
|
-
from collections.abc import Generator
|
|
6
|
+
from collections.abc import Callable, Generator
|
|
7
7
|
from pathlib import Path
|
|
8
|
-
from typing import
|
|
8
|
+
from typing import Optional
|
|
9
9
|
|
|
10
10
|
import numpy as np
|
|
11
11
|
from torch.utils.data import IterableDataset
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
from collections.abc import Generator
|
|
5
|
+
from collections.abc import Callable, Generator
|
|
6
6
|
from pathlib import Path
|
|
7
|
-
from typing import Any
|
|
7
|
+
from typing import Any
|
|
8
8
|
|
|
9
9
|
from numpy.typing import NDArray
|
|
10
10
|
from torch.utils.data import IterableDataset
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
from collections.abc import Generator
|
|
5
|
+
from collections.abc import Callable, Generator
|
|
6
6
|
from pathlib import Path
|
|
7
|
-
from typing import Any
|
|
7
|
+
from typing import Any
|
|
8
8
|
|
|
9
9
|
from numpy.typing import NDArray
|
|
10
10
|
from torch.utils.data import IterableDataset
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"""Patching functions."""
|
|
2
2
|
|
|
3
|
+
from collections.abc import Callable
|
|
3
4
|
from dataclasses import dataclass
|
|
4
5
|
from pathlib import Path
|
|
5
|
-
from typing import
|
|
6
|
+
from typing import Union
|
|
6
7
|
|
|
7
8
|
import numpy as np
|
|
8
9
|
from numpy.typing import NDArray
|
|
@@ -89,7 +90,7 @@ def prepare_patches_supervised(
|
|
|
89
90
|
"""
|
|
90
91
|
means, stds, num_samples = 0, 0, 0
|
|
91
92
|
all_patches, all_targets = [], []
|
|
92
|
-
for train_filename, target_filename in zip(train_files, target_files):
|
|
93
|
+
for train_filename, target_filename in zip(train_files, target_files, strict=False):
|
|
93
94
|
try:
|
|
94
95
|
sample: np.ndarray = read_source_func(train_filename, axes)
|
|
95
96
|
target: np.ndarray = read_source_func(target_filename, axes)
|
|
@@ -78,7 +78,9 @@ def extract_tiles(
|
|
|
78
78
|
...,
|
|
79
79
|
*[
|
|
80
80
|
slice(coords, coords + extent)
|
|
81
|
-
for coords, extent in zip(
|
|
81
|
+
for coords, extent in zip(
|
|
82
|
+
crop_coords_start, tile_size, strict=False
|
|
83
|
+
)
|
|
82
84
|
],
|
|
83
85
|
)
|
|
84
86
|
tile = sample[crop_slices]
|
|
@@ -159,11 +161,14 @@ def compute_tile_info_legacy(
|
|
|
159
161
|
|
|
160
162
|
# --- combine start and end
|
|
161
163
|
stitch_coords = tuple(
|
|
162
|
-
(start, end)
|
|
164
|
+
(start, end)
|
|
165
|
+
for start, end in zip(stitch_coords_start, stitch_coords_end, strict=False)
|
|
163
166
|
)
|
|
164
167
|
overlap_crop_coords = tuple(
|
|
165
168
|
(start, end)
|
|
166
|
-
for start, end in zip(
|
|
169
|
+
for start, end in zip(
|
|
170
|
+
overlap_crop_coords_start, overlap_crop_coords_end, strict=False
|
|
171
|
+
)
|
|
167
172
|
)
|
|
168
173
|
|
|
169
174
|
tile_info = TileInformation(
|
|
@@ -229,11 +234,14 @@ def compute_tile_info(
|
|
|
229
234
|
|
|
230
235
|
# --- combine start and end
|
|
231
236
|
stitch_coords = tuple(
|
|
232
|
-
(start, end)
|
|
237
|
+
(start, end)
|
|
238
|
+
for start, end in zip(stitch_coords_start, stitch_coords_end, strict=False)
|
|
233
239
|
)
|
|
234
240
|
overlap_crop_coords = tuple(
|
|
235
241
|
(start, end)
|
|
236
|
-
for start, end in zip(
|
|
242
|
+
for start, end in zip(
|
|
243
|
+
overlap_crop_coords_start, overlap_crop_coords_end, strict=False
|
|
244
|
+
)
|
|
237
245
|
)
|
|
238
246
|
|
|
239
247
|
# --- Check if last tile
|
|
@@ -284,7 +292,9 @@ def compute_padding(
|
|
|
284
292
|
pad_before = overlaps // 2
|
|
285
293
|
pad_after = covered_shape - data_shape[-len(tile_size) :] - pad_before
|
|
286
294
|
|
|
287
|
-
return tuple(
|
|
295
|
+
return tuple(
|
|
296
|
+
(before, after) for before, after in zip(pad_before, pad_after, strict=False)
|
|
297
|
+
)
|
|
288
298
|
|
|
289
299
|
|
|
290
300
|
def n_tiles_1d(axis_size: int, tile_size: int, overlap: int) -> int:
|
|
@@ -127,7 +127,7 @@ def extract_tiles(
|
|
|
127
127
|
# Rearrange crop coordinates from a list of coordinate pairs per axis to a list
|
|
128
128
|
# grouped by type.
|
|
129
129
|
all_crop_coords, all_stitch_coords, all_overlap_crop_coords = zip(
|
|
130
|
-
*crop_and_stitch_coords_list
|
|
130
|
+
*crop_and_stitch_coords_list, strict=False
|
|
131
131
|
)
|
|
132
132
|
|
|
133
133
|
# Maximum tile index
|
|
@@ -139,6 +139,7 @@ def extract_tiles(
|
|
|
139
139
|
itertools.product(*all_crop_coords),
|
|
140
140
|
itertools.product(*all_stitch_coords),
|
|
141
141
|
itertools.product(*all_overlap_crop_coords),
|
|
142
|
+
strict=False,
|
|
142
143
|
)
|
|
143
144
|
):
|
|
144
145
|
# Extract tile from the sample
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
# The CAREamics Dataset
|
|
2
|
+
|
|
3
|
+
Welcome to the CAREamics dataset!
|
|
4
|
+
|
|
5
|
+
A PyTorch based dataset, designed to be used with microscopy data. It is universal for the training, validation and prediction stages of a machine learning pipeline.
|
|
6
|
+
|
|
7
|
+
The key ethos is to create a modular and maintainable dataset comprised of swappable components that interact through interfaces. This should facilitate a smooth development process when extending the dataset's function to new features, and also enable advanced users to easily customize the dataset to their needs, by writing custom components. This is achieved by following a few key software engineering principles, detailed at the end of this README file.
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
## Dataset Component overview
|
|
11
|
+
|
|
12
|
+
```mermaid
|
|
13
|
+
---
|
|
14
|
+
title: CAREamicsDataset
|
|
15
|
+
---
|
|
16
|
+
classDiagram
|
|
17
|
+
class CAREamicsDataset{
|
|
18
|
+
+PatchExtractor input_extractor
|
|
19
|
+
+Optional[PatchExtractor] target_extractor
|
|
20
|
+
+PatchingStrategy patching_strategy
|
|
21
|
+
+list~Transform~ transforms
|
|
22
|
+
+\_\_getitem\_\_(int index) NDArray
|
|
23
|
+
}
|
|
24
|
+
class PatchingStrategy{
|
|
25
|
+
<<interface>>
|
|
26
|
+
+n_patches int
|
|
27
|
+
+get_patch_spec(index: int) PatchSpecs
|
|
28
|
+
}
|
|
29
|
+
class RandomPatchingStrategy{
|
|
30
|
+
}
|
|
31
|
+
class FixedRandomPatchingStrategy{
|
|
32
|
+
}
|
|
33
|
+
class SequentialPatchingStrategy{
|
|
34
|
+
}
|
|
35
|
+
class TilingStrategy{
|
|
36
|
+
+get_patch_spec(index: int) TileSpecs
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
class PatchExtractor{
|
|
40
|
+
+list~ImageStack~ image_stacks
|
|
41
|
+
+extract_patch(PatchSpecs) NDArray
|
|
42
|
+
}
|
|
43
|
+
class PatchSpecs {
|
|
44
|
+
<<TypedDict>>
|
|
45
|
+
+int data_idx
|
|
46
|
+
+int sample_idx
|
|
47
|
+
+Sequence~int~ coords
|
|
48
|
+
+Sequence~int~ patch_size
|
|
49
|
+
}
|
|
50
|
+
class TileSpecs {
|
|
51
|
+
<<TypedDict>>
|
|
52
|
+
+Sequence~int~ crop_coords
|
|
53
|
+
+Sequence~int~ crop_size
|
|
54
|
+
+Sequence~int~ stitch_coords
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
class ImageStack{
|
|
58
|
+
<<interface>>
|
|
59
|
+
+Union[Path, Literal["array"]] source
|
|
60
|
+
+Sequence~int~ data_shape
|
|
61
|
+
+DTypeLike data_type
|
|
62
|
+
+extract_patch(sample_idx, coords, patch_size) NDArray
|
|
63
|
+
}
|
|
64
|
+
class InMemoryImageStack {
|
|
65
|
+
}
|
|
66
|
+
class ZarrImageStack {
|
|
67
|
+
+Path source
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
CAREamicsDataset --* PatchExtractor: Is composed of
|
|
71
|
+
CAREamicsDataset --* PatchingStrategy: Is composed of
|
|
72
|
+
PatchExtractor --o ImageStack: Aggregates
|
|
73
|
+
ImageStack <|-- InMemoryImageStack: Implements
|
|
74
|
+
ImageStack <|-- ZarrImageStack: Implements
|
|
75
|
+
PatchingStrategy <|-- RandomPatchingStrategy: Implements
|
|
76
|
+
PatchingStrategy <|-- FixedRandomPatchingStrategy: Implements
|
|
77
|
+
PatchingStrategy <|-- SequentialPatchingStrategy: Implements
|
|
78
|
+
PatchingStrategy <|-- TilingStrategy: Implements
|
|
79
|
+
PatchSpecs <|-- TileSpecs: Inherits from
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### `ImageStack` and implementations
|
|
83
|
+
|
|
84
|
+
This interface represents a set of image data, which can be saved with any subset of the
|
|
85
|
+
axes STCZYX, in any order, see below for a description of the dimensions. The `ImageStack`
|
|
86
|
+
interface's job is to act as an adapter for different data storage types, so that higher
|
|
87
|
+
level classes can access the image data without having to know the implementation details of
|
|
88
|
+
how to load or read data from each storage type. This means we can decide to support new storage
|
|
89
|
+
types by implementing a new concrete `ImageStack` class without having to change anything
|
|
90
|
+
in the `CAREamistDataset` class. Advanced users can also choose to create their own
|
|
91
|
+
`ImageStack` class if they want to work with their own data storage type.
|
|
92
|
+
|
|
93
|
+
The interface provides an `extract_patch` method which will produce a patch from the image,
|
|
94
|
+
as a NumPy array, with the dimensions C(Z)YX. This method should be thought of as simply
|
|
95
|
+
a wrapper for the equivalent to NumPy slicing for each of the storage types.
|
|
96
|
+
|
|
97
|
+
#### Concrete implementations
|
|
98
|
+
|
|
99
|
+
- `InMemoryImageStack`: The underlying data is stored as a NumPy array in memory. It has some
|
|
100
|
+
additional constructor methods to load the data from known file formats such as TIFF files.
|
|
101
|
+
- `ZarrImageStack`: The underlying data is stored as a ZARR file on disk.
|
|
102
|
+
|
|
103
|
+
#### Axes description
|
|
104
|
+
|
|
105
|
+
- S is a generic sample dimension,
|
|
106
|
+
- T is a time dimension,
|
|
107
|
+
- C is a channel dimension,
|
|
108
|
+
- Z is a spatial dimension,
|
|
109
|
+
- Y is a spatial dimension,
|
|
110
|
+
- X is a spatial dimension.
|
|
111
|
+
|
|
112
|
+
### `PatchExtractor`
|
|
113
|
+
|
|
114
|
+
The `PatchExtractor` class aggregates many `ImageStack` instances, this allows for multiple
|
|
115
|
+
images with different dimensions, and possibly different storage types to be treated as a single entity.
|
|
116
|
+
The class has an `extract_patch` method to extract a patch from any one of its `ImageStack`
|
|
117
|
+
objects. It can also possibly be extended when extra logic to extract patches is needed,
|
|
118
|
+
for example when constructing lateral-context inputs for the MicroSplit LVAE models.
|
|
119
|
+
|
|
120
|
+
### `PatchingStrategy`
|
|
121
|
+
|
|
122
|
+
The `PatchingStrategy` class is an interface to generate patch specifications, where each of the
|
|
123
|
+
concrete implementations produce a set of patch specifications using a different strategy.
|
|
124
|
+
|
|
125
|
+
It has a `n_patches` attribute that can be accessed to find out how many patches the
|
|
126
|
+
strategy will produce, given the shapes of the image stacks it has been initialized with.
|
|
127
|
+
This is needed by the `CAREamicsDataset` to return its length.
|
|
128
|
+
|
|
129
|
+
Most importantly it has a `get_patch_spec` method, that takes an index and returns a
|
|
130
|
+
patch specification. For deterministic patching strategies, this method will always
|
|
131
|
+
return the same patch specification given the same index, but there are also random strategies
|
|
132
|
+
where the returned patch specification will change every time. The given index can never
|
|
133
|
+
be greater than `n_patches`.
|
|
134
|
+
|
|
135
|
+
#### Concrete implementations
|
|
136
|
+
|
|
137
|
+
- `RandomPatchingStrategy`: this strategy will produce random patches that will change
|
|
138
|
+
even if the `extract_patch` method is called with the same index.
|
|
139
|
+
- `FixedRandomPatchingStrategy`: this strategy will produce random patches, but the patch
|
|
140
|
+
will be the same if the `extract_patch` method is called with the same index. This is
|
|
141
|
+
useful for making sure validation is comparable epoch to epoch.
|
|
142
|
+
- `SequentialPatchingStrategy`: this strategy is deterministic and the patches will be
|
|
143
|
+
sequential with some specified overlap.
|
|
144
|
+
- `TilingStrategy`: this strategy is deterministic and the patches will be
|
|
145
|
+
sequential with some specified overlap. Rather than a `PatchSpecs` dictionary it will
|
|
146
|
+
produce a `TileSpecs` dictionary which includes some extra fields that are used for
|
|
147
|
+
stitching the tiles back together.
|
|
148
|
+
|
|
149
|
+
#### PatchSpecs
|
|
150
|
+
|
|
151
|
+
The `get_patch_spec` returns a dictionary containing the keys `data_idx`, `sample_idx`, `coords` and `patch_size`.
|
|
152
|
+
These are the exact arguments that the `PatchExtractor.extract_patch` method takes. The patch specification
|
|
153
|
+
produced by the patching strategy is received by the `PatchExtractor` to in-turn produce an image patch.
|
|
154
|
+
|
|
155
|
+
For type hinting, `PatchSpecs` is defined as a `TypedDict`.
|
|
156
|
+
|
|
157
|
+
## Key Principles
|
|
158
|
+
|
|
159
|
+
The aim of all these principles is to create a system of interacting classes that have
|
|
160
|
+
low coupling. This allows for one section to be changed or extended without breaking functionality
|
|
161
|
+
elsewhere in the codebase.
|
|
162
|
+
|
|
163
|
+
### Composition over inheritance
|
|
164
|
+
|
|
165
|
+
The principle of composition over inheritance is: rather than using inheritance to
|
|
166
|
+
extend or change the behavior of a class, instead, a class can be composed of modules
|
|
167
|
+
that can be swapped to extend or change behavior.
|
|
168
|
+
|
|
169
|
+
The reason to use composition is that it promotes the easy reuse of the underlying
|
|
170
|
+
components, it can prevent a subclass explosion, and it leads to a maintainable and
|
|
171
|
+
easily extendable design. A software architecture based on composition is normally
|
|
172
|
+
maintainable and extendable because if a component needs to change then the whole class
|
|
173
|
+
shouldn't have to be refactored and if a new feature needs to be added, usually an additional
|
|
174
|
+
component can be added to the class.
|
|
175
|
+
|
|
176
|
+
The `CAREamicsDataset` is composed of `PatchExtractor` and `PatchingStrategy` and `Transfrom` components.
|
|
177
|
+
The `PatchingStrategy` classes implement an interface so the dataset can switch between
|
|
178
|
+
different strategies. The `PatchExtractor` is composed of many `ImageStack` instances,
|
|
179
|
+
new image stacks can be added to extend the type of data that the dataset can read from.
|
|
180
|
+
|
|
181
|
+
### Dependency Inversion
|
|
182
|
+
|
|
183
|
+
The dependency inversion principle states:
|
|
184
|
+
|
|
185
|
+
1. High-level modules should not depend on low-level modules. Both high-level and
|
|
186
|
+
low-level modules should depend on abstractions (e.g. interfaces).
|
|
187
|
+
2. Abstractions should not depend on details (concrete implementations). Details should
|
|
188
|
+
depend on abstractions.
|
|
189
|
+
|
|
190
|
+
In other words high level modules that provide complex logic should be easily reusable
|
|
191
|
+
and not depend on implementation details of low-level modules that provide utility functionality.
|
|
192
|
+
This can be achieved by introducing abstractions that decouple high and low level modules.
|
|
193
|
+
|
|
194
|
+
An example of the dependency inversion principle in use is how the `PatchExtractor` only
|
|
195
|
+
depends on the `ImageStack` interface, and does not have to have any knowledge of the
|
|
196
|
+
concrete implementations. The concrete `ImageStack` implementations also do not have
|
|
197
|
+
any knowledge of the `PatchExtractor` or any other higher-level functionality that the
|
|
198
|
+
dataset needs.
|
|
199
|
+
|
|
200
|
+
### Single Responsibility Principle
|
|
201
|
+
|
|
202
|
+
Each component should have a small scope of responsibility that is easily defined. This
|
|
203
|
+
should make the code easier to maintain and hopefully reduce the number of places in the
|
|
204
|
+
code that have to change when introducing a new feature.
|
|
205
|
+
|
|
206
|
+
- `ImageStack` responsibility: to act as an adapter for loading and reading image data
|
|
207
|
+
from different underlying storage.
|
|
208
|
+
- `PatchExtractor` responsibility: to extract patches from a set of image stacks.
|
|
209
|
+
- `PatchingStrategy` responsibility: to produce patch specifications given an index, through
|
|
210
|
+
an interface that hides the underlying implementation.
|
|
211
|
+
- `CAREamicsDataset` responsibility: to orchestrate the interactions of its underlying
|
|
212
|
+
components to produce an input patch (and target patch when required) given an index.
|