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.

Files changed (98) hide show
  1. careamics/careamist.py +24 -7
  2. careamics/cli/utils.py +1 -1
  3. careamics/config/algorithms/n2v_algorithm_model.py +1 -1
  4. careamics/config/architectures/unet_model.py +3 -0
  5. careamics/config/callback_model.py +23 -34
  6. careamics/config/configuration.py +55 -4
  7. careamics/config/configuration_factories.py +288 -23
  8. careamics/config/data/__init__.py +2 -0
  9. careamics/config/data/data_model.py +41 -4
  10. careamics/config/data/ng_data_model.py +381 -0
  11. careamics/config/data/patching_strategies/__init__.py +14 -0
  12. careamics/config/data/patching_strategies/_overlapping_patched_model.py +103 -0
  13. careamics/config/data/patching_strategies/_patched_model.py +56 -0
  14. careamics/config/data/patching_strategies/random_patching_model.py +21 -0
  15. careamics/config/data/patching_strategies/sequential_patching_model.py +25 -0
  16. careamics/config/data/patching_strategies/tiled_patching_model.py +40 -0
  17. careamics/config/data/patching_strategies/whole_patching_model.py +12 -0
  18. careamics/config/inference_model.py +6 -3
  19. careamics/config/optimizer_models.py +1 -3
  20. careamics/config/support/supported_data.py +7 -0
  21. careamics/config/support/supported_patching_strategies.py +22 -0
  22. careamics/config/training_model.py +0 -2
  23. careamics/config/validators/validator_utils.py +4 -3
  24. careamics/dataset/dataset_utils/iterate_over_files.py +2 -2
  25. careamics/dataset/in_memory_dataset.py +2 -1
  26. careamics/dataset/iterable_dataset.py +2 -2
  27. careamics/dataset/iterable_pred_dataset.py +2 -2
  28. careamics/dataset/iterable_tiled_pred_dataset.py +2 -2
  29. careamics/dataset/patching/patching.py +3 -2
  30. careamics/dataset/tiling/lvae_tiled_patching.py +16 -6
  31. careamics/dataset/tiling/tiled_patching.py +2 -1
  32. careamics/dataset_ng/README.md +212 -0
  33. careamics/dataset_ng/dataset.py +229 -0
  34. careamics/dataset_ng/demos/bsd68_demo.ipynb +361 -0
  35. careamics/dataset_ng/demos/care_U2OS_demo.ipynb +330 -0
  36. careamics/dataset_ng/demos/demo_custom_image_stack.ipynb +734 -0
  37. careamics/dataset_ng/demos/demo_datamodule.ipynb +447 -0
  38. careamics/dataset_ng/{demo_dataset.ipynb → demos/demo_dataset.ipynb} +60 -53
  39. careamics/dataset_ng/{demo_patch_extractor.py → demos/demo_patch_extractor.py} +7 -9
  40. careamics/dataset_ng/demos/mouse_nuclei_demo.ipynb +292 -0
  41. careamics/dataset_ng/factory.py +451 -0
  42. careamics/dataset_ng/legacy_interoperability.py +170 -0
  43. careamics/dataset_ng/patch_extractor/__init__.py +3 -8
  44. careamics/dataset_ng/patch_extractor/demo_custom_image_stack_loader.py +7 -5
  45. careamics/dataset_ng/patch_extractor/image_stack/__init__.py +4 -1
  46. careamics/dataset_ng/patch_extractor/image_stack/czi_image_stack.py +360 -0
  47. careamics/dataset_ng/patch_extractor/image_stack/image_stack_protocol.py +5 -1
  48. careamics/dataset_ng/patch_extractor/image_stack/in_memory_image_stack.py +1 -1
  49. careamics/dataset_ng/patch_extractor/image_stack_loader.py +5 -75
  50. careamics/dataset_ng/patch_extractor/patch_extractor.py +5 -4
  51. careamics/dataset_ng/patch_extractor/patch_extractor_factory.py +114 -105
  52. careamics/dataset_ng/patching_strategies/__init__.py +6 -1
  53. careamics/dataset_ng/patching_strategies/patching_strategy_protocol.py +31 -0
  54. careamics/dataset_ng/patching_strategies/random_patching.py +5 -1
  55. careamics/dataset_ng/patching_strategies/sequential_patching.py +5 -5
  56. careamics/dataset_ng/patching_strategies/tiling_strategy.py +172 -0
  57. careamics/dataset_ng/patching_strategies/whole_sample.py +36 -0
  58. careamics/file_io/read/get_func.py +2 -1
  59. careamics/lightning/dataset_ng/__init__.py +1 -0
  60. careamics/lightning/dataset_ng/data_module.py +678 -0
  61. careamics/lightning/dataset_ng/lightning_modules/__init__.py +9 -0
  62. careamics/lightning/dataset_ng/lightning_modules/care_module.py +97 -0
  63. careamics/lightning/dataset_ng/lightning_modules/n2v_module.py +106 -0
  64. careamics/lightning/dataset_ng/lightning_modules/unet_module.py +212 -0
  65. careamics/lightning/lightning_module.py +5 -1
  66. careamics/lightning/predict_data_module.py +2 -1
  67. careamics/lightning/train_data_module.py +2 -1
  68. careamics/losses/loss_factory.py +2 -1
  69. careamics/lvae_training/dataset/__init__.py +8 -3
  70. careamics/lvae_training/dataset/config.py +3 -3
  71. careamics/lvae_training/dataset/ms_dataset_ref.py +1067 -0
  72. careamics/lvae_training/dataset/multich_dataset.py +46 -17
  73. careamics/lvae_training/dataset/multicrop_dset.py +196 -0
  74. careamics/lvae_training/dataset/types.py +3 -3
  75. careamics/lvae_training/dataset/utils/index_manager.py +259 -0
  76. careamics/lvae_training/eval_utils.py +93 -3
  77. careamics/model_io/bioimage/bioimage_utils.py +1 -1
  78. careamics/model_io/bioimage/model_description.py +1 -1
  79. careamics/model_io/bmz_io.py +1 -1
  80. careamics/model_io/model_io_utils.py +2 -2
  81. careamics/models/activation.py +2 -1
  82. careamics/prediction_utils/prediction_outputs.py +1 -1
  83. careamics/prediction_utils/stitch_prediction.py +1 -1
  84. careamics/transforms/compose.py +1 -0
  85. careamics/transforms/n2v_manipulate_torch.py +15 -9
  86. careamics/transforms/normalize.py +18 -7
  87. careamics/transforms/pixel_manipulation_torch.py +59 -92
  88. careamics/utils/lightning_utils.py +25 -11
  89. careamics/utils/metrics.py +2 -1
  90. careamics/utils/torch_utils.py +23 -0
  91. {careamics-0.0.11.dist-info → careamics-0.0.13.dist-info}/METADATA +12 -11
  92. {careamics-0.0.11.dist-info → careamics-0.0.13.dist-info}/RECORD +95 -69
  93. careamics/dataset_ng/dataset/__init__.py +0 -3
  94. careamics/dataset_ng/dataset/dataset.py +0 -184
  95. careamics/dataset_ng/demo_patch_extractor_factory.py +0 -37
  96. {careamics-0.0.11.dist-info → careamics-0.0.13.dist-info}/WHEEL +0 -0
  97. {careamics-0.0.11.dist-info → careamics-0.0.13.dist-info}/entry_points.txt +0 -0
  98. {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 typing import Optional, Union
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[Union[list[int], Union[tuple[int, ...]]]],
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 : list or typle of int, or None
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 Callable, Optional, Union
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, Callable, Optional, Union
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 Callable, Optional
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, Callable
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, Callable
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 Callable, Union
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(crop_coords_start, tile_size)
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) for start, end in zip(stitch_coords_start, stitch_coords_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(overlap_crop_coords_start, overlap_crop_coords_end)
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) for start, end in zip(stitch_coords_start, stitch_coords_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(overlap_crop_coords_start, overlap_crop_coords_end)
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((before, after) for before, after in zip(pad_before, pad_after))
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.