careamics 0.0.1__py3-none-any.whl → 0.0.2__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 (141) hide show
  1. careamics/__init__.py +6 -1
  2. careamics/careamist.py +726 -0
  3. careamics/config/__init__.py +35 -0
  4. careamics/config/algorithm_model.py +162 -0
  5. careamics/config/architectures/__init__.py +17 -0
  6. careamics/config/architectures/architecture_model.py +37 -0
  7. careamics/config/architectures/custom_model.py +159 -0
  8. careamics/config/architectures/register_model.py +103 -0
  9. careamics/config/architectures/unet_model.py +118 -0
  10. careamics/config/architectures/vae_model.py +42 -0
  11. careamics/config/callback_model.py +123 -0
  12. careamics/config/configuration_factory.py +575 -0
  13. careamics/config/configuration_model.py +600 -0
  14. careamics/config/data_model.py +502 -0
  15. careamics/config/inference_model.py +239 -0
  16. careamics/config/optimizer_models.py +187 -0
  17. careamics/config/references/__init__.py +45 -0
  18. careamics/config/references/algorithm_descriptions.py +132 -0
  19. careamics/config/references/references.py +39 -0
  20. careamics/config/support/__init__.py +31 -0
  21. careamics/config/support/supported_activations.py +26 -0
  22. careamics/config/support/supported_algorithms.py +20 -0
  23. careamics/config/support/supported_architectures.py +20 -0
  24. careamics/config/support/supported_data.py +109 -0
  25. careamics/config/support/supported_loggers.py +10 -0
  26. careamics/config/support/supported_losses.py +27 -0
  27. careamics/config/support/supported_optimizers.py +57 -0
  28. careamics/config/support/supported_pixel_manipulations.py +15 -0
  29. careamics/config/support/supported_struct_axis.py +21 -0
  30. careamics/config/support/supported_transforms.py +11 -0
  31. careamics/config/tile_information.py +65 -0
  32. careamics/config/training_model.py +72 -0
  33. careamics/config/transformations/__init__.py +15 -0
  34. careamics/config/transformations/n2v_manipulate_model.py +64 -0
  35. careamics/config/transformations/normalize_model.py +60 -0
  36. careamics/config/transformations/transform_model.py +45 -0
  37. careamics/config/transformations/xy_flip_model.py +43 -0
  38. careamics/config/transformations/xy_random_rotate90_model.py +35 -0
  39. careamics/config/validators/__init__.py +5 -0
  40. careamics/config/validators/validator_utils.py +101 -0
  41. careamics/conftest.py +39 -0
  42. careamics/dataset/__init__.py +17 -0
  43. careamics/dataset/dataset_utils/__init__.py +19 -0
  44. careamics/dataset/dataset_utils/dataset_utils.py +101 -0
  45. careamics/dataset/dataset_utils/file_utils.py +141 -0
  46. careamics/dataset/dataset_utils/iterate_over_files.py +83 -0
  47. careamics/dataset/dataset_utils/running_stats.py +186 -0
  48. careamics/dataset/in_memory_dataset.py +310 -0
  49. careamics/dataset/in_memory_pred_dataset.py +88 -0
  50. careamics/dataset/in_memory_tiled_pred_dataset.py +129 -0
  51. careamics/dataset/iterable_dataset.py +295 -0
  52. careamics/dataset/iterable_pred_dataset.py +122 -0
  53. careamics/dataset/iterable_tiled_pred_dataset.py +140 -0
  54. careamics/dataset/patching/__init__.py +1 -0
  55. careamics/dataset/patching/patching.py +299 -0
  56. careamics/dataset/patching/random_patching.py +201 -0
  57. careamics/dataset/patching/sequential_patching.py +212 -0
  58. careamics/dataset/patching/validate_patch_dimension.py +64 -0
  59. careamics/dataset/tiling/__init__.py +10 -0
  60. careamics/dataset/tiling/collate_tiles.py +33 -0
  61. careamics/dataset/tiling/tiled_patching.py +164 -0
  62. careamics/dataset/zarr_dataset.py +151 -0
  63. careamics/file_io/__init__.py +15 -0
  64. careamics/file_io/read/__init__.py +12 -0
  65. careamics/file_io/read/get_func.py +56 -0
  66. careamics/file_io/read/tiff.py +58 -0
  67. careamics/file_io/read/zarr.py +60 -0
  68. careamics/file_io/write/__init__.py +15 -0
  69. careamics/file_io/write/get_func.py +63 -0
  70. careamics/file_io/write/tiff.py +40 -0
  71. careamics/lightning/__init__.py +17 -0
  72. careamics/lightning/callbacks/__init__.py +11 -0
  73. careamics/lightning/callbacks/hyperparameters_callback.py +49 -0
  74. careamics/lightning/callbacks/prediction_writer_callback/__init__.py +20 -0
  75. careamics/lightning/callbacks/prediction_writer_callback/file_path_utils.py +56 -0
  76. careamics/lightning/callbacks/prediction_writer_callback/prediction_writer_callback.py +233 -0
  77. careamics/lightning/callbacks/prediction_writer_callback/write_strategy.py +398 -0
  78. careamics/lightning/callbacks/prediction_writer_callback/write_strategy_factory.py +215 -0
  79. careamics/lightning/callbacks/progress_bar_callback.py +90 -0
  80. careamics/lightning/lightning_module.py +276 -0
  81. careamics/lightning/predict_data_module.py +333 -0
  82. careamics/lightning/train_data_module.py +680 -0
  83. careamics/losses/__init__.py +5 -0
  84. careamics/losses/loss_factory.py +49 -0
  85. careamics/losses/losses.py +98 -0
  86. careamics/lvae_training/__init__.py +0 -0
  87. careamics/lvae_training/data_modules.py +1220 -0
  88. careamics/lvae_training/data_utils.py +618 -0
  89. careamics/lvae_training/eval_utils.py +905 -0
  90. careamics/lvae_training/get_config.py +84 -0
  91. careamics/lvae_training/lightning_module.py +701 -0
  92. careamics/lvae_training/metrics.py +214 -0
  93. careamics/lvae_training/train_lvae.py +339 -0
  94. careamics/lvae_training/train_utils.py +121 -0
  95. careamics/model_io/__init__.py +7 -0
  96. careamics/model_io/bioimage/__init__.py +11 -0
  97. careamics/model_io/bioimage/_readme_factory.py +121 -0
  98. careamics/model_io/bioimage/bioimage_utils.py +52 -0
  99. careamics/model_io/bioimage/model_description.py +327 -0
  100. careamics/model_io/bmz_io.py +233 -0
  101. careamics/model_io/model_io_utils.py +83 -0
  102. careamics/models/__init__.py +7 -0
  103. careamics/models/activation.py +37 -0
  104. careamics/models/layers.py +493 -0
  105. careamics/models/lvae/__init__.py +0 -0
  106. careamics/models/lvae/layers.py +1998 -0
  107. careamics/models/lvae/likelihoods.py +312 -0
  108. careamics/models/lvae/lvae.py +985 -0
  109. careamics/models/lvae/noise_models.py +409 -0
  110. careamics/models/lvae/utils.py +395 -0
  111. careamics/models/model_factory.py +52 -0
  112. careamics/models/unet.py +443 -0
  113. careamics/prediction_utils/__init__.py +10 -0
  114. careamics/prediction_utils/prediction_outputs.py +135 -0
  115. careamics/prediction_utils/stitch_prediction.py +98 -0
  116. careamics/transforms/__init__.py +20 -0
  117. careamics/transforms/compose.py +107 -0
  118. careamics/transforms/n2v_manipulate.py +146 -0
  119. careamics/transforms/normalize.py +243 -0
  120. careamics/transforms/pixel_manipulation.py +407 -0
  121. careamics/transforms/struct_mask_parameters.py +20 -0
  122. careamics/transforms/transform.py +24 -0
  123. careamics/transforms/tta.py +88 -0
  124. careamics/transforms/xy_flip.py +123 -0
  125. careamics/transforms/xy_random_rotate90.py +101 -0
  126. careamics/utils/__init__.py +19 -0
  127. careamics/utils/autocorrelation.py +40 -0
  128. careamics/utils/base_enum.py +60 -0
  129. careamics/utils/context.py +66 -0
  130. careamics/utils/logging.py +322 -0
  131. careamics/utils/metrics.py +115 -0
  132. careamics/utils/path_utils.py +26 -0
  133. careamics/utils/ram.py +15 -0
  134. careamics/utils/receptive_field.py +108 -0
  135. careamics/utils/torch_utils.py +127 -0
  136. careamics-0.0.2.dist-info/METADATA +78 -0
  137. careamics-0.0.2.dist-info/RECORD +140 -0
  138. {careamics-0.0.1.dist-info → careamics-0.0.2.dist-info}/WHEEL +1 -1
  139. {careamics-0.0.1.dist-info → careamics-0.0.2.dist-info}/licenses/LICENSE +1 -1
  140. careamics-0.0.1.dist-info/METADATA +0 -46
  141. careamics-0.0.1.dist-info/RECORD +0 -6
@@ -0,0 +1,502 @@
1
+ """Data configuration."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pprint import pformat
6
+ from typing import Any, Literal, Optional, Union
7
+
8
+ from numpy.typing import NDArray
9
+ from pydantic import (
10
+ BaseModel,
11
+ ConfigDict,
12
+ Discriminator,
13
+ Field,
14
+ field_validator,
15
+ model_validator,
16
+ )
17
+ from typing_extensions import Annotated, Self
18
+
19
+ from .support import SupportedTransform
20
+ from .transformations.n2v_manipulate_model import N2VManipulateModel
21
+ from .transformations.xy_flip_model import XYFlipModel
22
+ from .transformations.xy_random_rotate90_model import XYRandomRotate90Model
23
+ from .validators import check_axes_validity, patch_size_ge_than_8_power_of_2
24
+
25
+ TRANSFORMS_UNION = Annotated[
26
+ Union[
27
+ XYFlipModel,
28
+ XYRandomRotate90Model,
29
+ N2VManipulateModel,
30
+ ],
31
+ Discriminator("name"), # used to tell the different transform models apart
32
+ ]
33
+
34
+
35
+ class DataConfig(BaseModel):
36
+ """
37
+ Data configuration.
38
+
39
+ If std is specified, mean must be specified as well. Note that setting the std first
40
+ and then the mean (if they were both `None` before) will raise a validation error.
41
+ Prefer instead `set_mean_and_std` to set both at once. Means and stds are expected
42
+ to be lists of floats, one for each channel. For supervised tasks, the mean and std
43
+ of the target could be different from the input data.
44
+
45
+ All supported transforms are defined in the SupportedTransform enum.
46
+
47
+ Examples
48
+ --------
49
+ Minimum example:
50
+
51
+ >>> data = DataConfig(
52
+ ... data_type="array", # defined in SupportedData
53
+ ... patch_size=[128, 128],
54
+ ... batch_size=4,
55
+ ... axes="YX"
56
+ ... )
57
+
58
+ To change the image_means and image_stds of the data:
59
+ >>> data.set_means_and_stds(image_means=[214.3], image_stds=[84.5])
60
+
61
+ One can pass also a list of transformations, by keyword, using the
62
+ SupportedTransform value:
63
+ >>> from careamics.config.support import SupportedTransform
64
+ >>> data = DataConfig(
65
+ ... data_type="tiff",
66
+ ... patch_size=[128, 128],
67
+ ... batch_size=4,
68
+ ... axes="YX",
69
+ ... transforms=[
70
+ ... {
71
+ ... "name": "XYFlip",
72
+ ... }
73
+ ... ]
74
+ ... )
75
+ """
76
+
77
+ # Pydantic class configuration
78
+ model_config = ConfigDict(
79
+ validate_assignment=True,
80
+ )
81
+
82
+ # Dataset configuration
83
+ data_type: Literal["array", "tiff", "custom"]
84
+ """Type of input data, numpy.ndarray (array) or paths (tiff and custom), as defined
85
+ in SupportedData."""
86
+
87
+ axes: str
88
+ """Axes of the data, as defined in SupportedAxes."""
89
+
90
+ patch_size: Union[list[int]] = Field(..., min_length=2, max_length=3)
91
+ """Patch size, as used during training."""
92
+
93
+ batch_size: int = Field(default=1, ge=1, validate_default=True)
94
+ """Batch size for training."""
95
+
96
+ # Optional fields
97
+ image_means: Optional[list[float]] = Field(
98
+ default=None, min_length=0, max_length=32
99
+ )
100
+ """Means of the data across channels, used for normalization."""
101
+
102
+ image_stds: Optional[list[float]] = Field(default=None, min_length=0, max_length=32)
103
+ """Standard deviations of the data across channels, used for normalization."""
104
+
105
+ target_means: Optional[list[float]] = Field(
106
+ default=None, min_length=0, max_length=32
107
+ )
108
+ """Means of the target data across channels, used for normalization."""
109
+
110
+ target_stds: Optional[list[float]] = Field(
111
+ default=None, min_length=0, max_length=32
112
+ )
113
+ """Standard deviations of the target data across channels, used for
114
+ normalization."""
115
+
116
+ transforms: list[TRANSFORMS_UNION] = Field(
117
+ default=[
118
+ {
119
+ "name": SupportedTransform.XY_FLIP.value,
120
+ },
121
+ {
122
+ "name": SupportedTransform.XY_RANDOM_ROTATE90.value,
123
+ },
124
+ {
125
+ "name": SupportedTransform.N2V_MANIPULATE.value,
126
+ },
127
+ ],
128
+ validate_default=True,
129
+ )
130
+ """List of transformations to apply to the data, available transforms are defined
131
+ in SupportedTransform. The default values are set for Noise2Void."""
132
+
133
+ dataloader_params: Optional[dict] = None
134
+ """Dictionary of PyTorch dataloader parameters."""
135
+
136
+ @field_validator("patch_size")
137
+ @classmethod
138
+ def all_elements_power_of_2_minimum_8(
139
+ cls, patch_list: Union[list[int]]
140
+ ) -> Union[list[int]]:
141
+ """
142
+ Validate patch size.
143
+
144
+ Patch size must be powers of 2 and minimum 8.
145
+
146
+ Parameters
147
+ ----------
148
+ patch_list : list of int
149
+ Patch size.
150
+
151
+ Returns
152
+ -------
153
+ list of int
154
+ Validated patch size.
155
+
156
+ Raises
157
+ ------
158
+ ValueError
159
+ If the patch size is smaller than 8.
160
+ ValueError
161
+ If the patch size is not a power of 2.
162
+ """
163
+ patch_size_ge_than_8_power_of_2(patch_list)
164
+
165
+ return patch_list
166
+
167
+ @field_validator("axes")
168
+ @classmethod
169
+ def axes_valid(cls, axes: str) -> str:
170
+ """
171
+ Validate axes.
172
+
173
+ Axes must:
174
+ - be a combination of 'STCZYX'
175
+ - not contain duplicates
176
+ - contain at least 2 contiguous axes: X and Y
177
+ - contain at most 4 axes
178
+ - not contain both S and T axes
179
+
180
+ Parameters
181
+ ----------
182
+ axes : str
183
+ Axes to validate.
184
+
185
+ Returns
186
+ -------
187
+ str
188
+ Validated axes.
189
+
190
+ Raises
191
+ ------
192
+ ValueError
193
+ If axes are not valid.
194
+ """
195
+ # Validate axes
196
+ check_axes_validity(axes)
197
+
198
+ return axes
199
+
200
+ @field_validator("transforms")
201
+ @classmethod
202
+ def validate_prediction_transforms(
203
+ cls, transforms: list[TRANSFORMS_UNION]
204
+ ) -> list[TRANSFORMS_UNION]:
205
+ """
206
+ Validate N2VManipulate transform position in the transform list.
207
+
208
+ Parameters
209
+ ----------
210
+ transforms : list[Transformations_Union]
211
+ Transforms.
212
+
213
+ Returns
214
+ -------
215
+ list of transforms
216
+ Validated transforms.
217
+
218
+ Raises
219
+ ------
220
+ ValueError
221
+ If multiple instances of N2VManipulate are found.
222
+ """
223
+ transform_list = [t.name for t in transforms]
224
+
225
+ if SupportedTransform.N2V_MANIPULATE in transform_list:
226
+ # multiple N2V_MANIPULATE
227
+ if transform_list.count(SupportedTransform.N2V_MANIPULATE.value) > 1:
228
+ raise ValueError(
229
+ f"Multiple instances of "
230
+ f"{SupportedTransform.N2V_MANIPULATE} transforms "
231
+ f"are not allowed."
232
+ )
233
+
234
+ # N2V_MANIPULATE not the last transform
235
+ elif transform_list[-1] != SupportedTransform.N2V_MANIPULATE:
236
+ index = transform_list.index(SupportedTransform.N2V_MANIPULATE.value)
237
+ transform = transforms.pop(index)
238
+ transforms.append(transform)
239
+
240
+ return transforms
241
+
242
+ @model_validator(mode="after")
243
+ def std_only_with_mean(self: Self) -> Self:
244
+ """
245
+ Check that mean and std are either both None, or both specified.
246
+
247
+ Returns
248
+ -------
249
+ Self
250
+ Validated data model.
251
+
252
+ Raises
253
+ ------
254
+ ValueError
255
+ If std is not None and mean is None.
256
+ """
257
+ # check that mean and std are either both None, or both specified
258
+ if (self.image_means and not self.image_stds) or (
259
+ self.image_stds and not self.image_means
260
+ ):
261
+ raise ValueError(
262
+ "Mean and std must be either both None, or both specified."
263
+ )
264
+
265
+ elif (self.image_means is not None and self.image_stds is not None) and (
266
+ len(self.image_means) != len(self.image_stds)
267
+ ):
268
+ raise ValueError(
269
+ "Mean and std must be specified for each " "input channel."
270
+ )
271
+
272
+ if (self.target_means and not self.target_stds) or (
273
+ self.target_stds and not self.target_means
274
+ ):
275
+ raise ValueError(
276
+ "Mean and std must be either both None, or both specified "
277
+ )
278
+
279
+ elif self.target_means is not None and self.target_stds is not None:
280
+ if len(self.target_means) != len(self.target_stds):
281
+ raise ValueError(
282
+ "Mean and std must be either both None, or both specified for each "
283
+ "target channel."
284
+ )
285
+
286
+ return self
287
+
288
+ @model_validator(mode="after")
289
+ def validate_dimensions(self: Self) -> Self:
290
+ """
291
+ Validate 2D/3D dimensions between axes, patch size and transforms.
292
+
293
+ Returns
294
+ -------
295
+ Self
296
+ Validated data model.
297
+
298
+ Raises
299
+ ------
300
+ ValueError
301
+ If the transforms are not valid.
302
+ """
303
+ if "Z" in self.axes:
304
+ if len(self.patch_size) != 3:
305
+ raise ValueError(
306
+ f"Patch size must have 3 dimensions if the data is 3D "
307
+ f"({self.axes})."
308
+ )
309
+
310
+ else:
311
+ if len(self.patch_size) != 2:
312
+ raise ValueError(
313
+ f"Patch size must have 3 dimensions if the data is 3D "
314
+ f"({self.axes})."
315
+ )
316
+
317
+ return self
318
+
319
+ def __str__(self) -> str:
320
+ """
321
+ Pretty string reprensenting the configuration.
322
+
323
+ Returns
324
+ -------
325
+ str
326
+ Pretty string.
327
+ """
328
+ return pformat(self.model_dump())
329
+
330
+ def _update(self, **kwargs: Any) -> None:
331
+ """
332
+ Update multiple arguments at once.
333
+
334
+ Parameters
335
+ ----------
336
+ **kwargs : Any
337
+ Keyword arguments to update.
338
+ """
339
+ self.__dict__.update(kwargs)
340
+ self.__class__.model_validate(self.__dict__)
341
+
342
+ def has_n2v_manipulate(self) -> bool:
343
+ """
344
+ Check if the transforms contain N2VManipulate.
345
+
346
+ Returns
347
+ -------
348
+ bool
349
+ True if the transforms contain N2VManipulate, False otherwise.
350
+ """
351
+ return any(
352
+ transform.name == SupportedTransform.N2V_MANIPULATE.value
353
+ for transform in self.transforms
354
+ )
355
+
356
+ def add_n2v_manipulate(self) -> None:
357
+ """Add N2VManipulate to the transforms."""
358
+ if not self.has_n2v_manipulate():
359
+ self.transforms.append(
360
+ N2VManipulateModel(name=SupportedTransform.N2V_MANIPULATE.value)
361
+ )
362
+
363
+ def remove_n2v_manipulate(self) -> None:
364
+ """Remove N2VManipulate from the transforms."""
365
+ if self.has_n2v_manipulate():
366
+ self.transforms.pop(-1)
367
+
368
+ def set_means_and_stds(
369
+ self,
370
+ image_means: Union[NDArray, tuple, list, None],
371
+ image_stds: Union[NDArray, tuple, list, None],
372
+ target_means: Optional[Union[NDArray, tuple, list, None]] = None,
373
+ target_stds: Optional[Union[NDArray, tuple, list, None]] = None,
374
+ ) -> None:
375
+ """
376
+ Set mean and standard deviation of the data across channels.
377
+
378
+ This method should be used instead setting the fields directly, as it would
379
+ otherwise trigger a validation error.
380
+
381
+ Parameters
382
+ ----------
383
+ image_means : numpy.ndarray ,tuple or list
384
+ Mean values for normalization.
385
+ image_stds : numpy.ndarray, tuple or list
386
+ Standard deviation values for normalization.
387
+ target_means : numpy.ndarray, tuple or list, optional
388
+ Target mean values for normalization, by default ().
389
+ target_stds : numpy.ndarray, tuple or list, optional
390
+ Target standard deviation values for normalization, by default ().
391
+ """
392
+ # make sure we pass a list
393
+ if image_means is not None:
394
+ image_means = list(image_means)
395
+ if image_stds is not None:
396
+ image_stds = list(image_stds)
397
+ if target_means is not None:
398
+ target_means = list(target_means)
399
+ if target_stds is not None:
400
+ target_stds = list(target_stds)
401
+
402
+ self._update(
403
+ image_means=image_means,
404
+ image_stds=image_stds,
405
+ target_means=target_means,
406
+ target_stds=target_stds,
407
+ )
408
+
409
+ def set_3D(self, axes: str, patch_size: list[int]) -> None:
410
+ """
411
+ Set 3D parameters.
412
+
413
+ Parameters
414
+ ----------
415
+ axes : str
416
+ Axes.
417
+ patch_size : list of int
418
+ Patch size.
419
+ """
420
+ self._update(axes=axes, patch_size=patch_size)
421
+
422
+ def set_N2V2(self, use_n2v2: bool) -> None:
423
+ """
424
+ Set N2V2.
425
+
426
+ Parameters
427
+ ----------
428
+ use_n2v2 : bool
429
+ Whether to use N2V2.
430
+
431
+ Raises
432
+ ------
433
+ ValueError
434
+ If the N2V pixel manipulate transform is not found in the transforms.
435
+ """
436
+ if use_n2v2:
437
+ self.set_N2V2_strategy("median")
438
+ else:
439
+ self.set_N2V2_strategy("uniform")
440
+
441
+ def set_N2V2_strategy(self, strategy: Literal["uniform", "median"]) -> None:
442
+ """
443
+ Set N2V2 strategy.
444
+
445
+ Parameters
446
+ ----------
447
+ strategy : Literal["uniform", "median"]
448
+ Strategy to use for N2V2.
449
+
450
+ Raises
451
+ ------
452
+ ValueError
453
+ If the N2V pixel manipulate transform is not found in the transforms.
454
+ """
455
+ found_n2v = False
456
+
457
+ for transform in self.transforms:
458
+ if transform.name == SupportedTransform.N2V_MANIPULATE.value:
459
+ transform.strategy = strategy
460
+ found_n2v = True
461
+
462
+ if not found_n2v:
463
+ transforms = [t.name for t in self.transforms]
464
+ raise ValueError(
465
+ f"N2V_Manipulate transform not found in the transforms list "
466
+ f"({transforms})."
467
+ )
468
+
469
+ def set_structN2V_mask(
470
+ self, mask_axis: Literal["horizontal", "vertical", "none"], mask_span: int
471
+ ) -> None:
472
+ """
473
+ Set structN2V mask parameters.
474
+
475
+ Setting `mask_axis` to `none` will disable structN2V.
476
+
477
+ Parameters
478
+ ----------
479
+ mask_axis : Literal["horizontal", "vertical", "none"]
480
+ Axis along which to apply the mask. `none` will disable structN2V.
481
+ mask_span : int
482
+ Total span of the mask in pixels.
483
+
484
+ Raises
485
+ ------
486
+ ValueError
487
+ If the N2V pixel manipulate transform is not found in the transforms.
488
+ """
489
+ found_n2v = False
490
+
491
+ for transform in self.transforms:
492
+ if transform.name == SupportedTransform.N2V_MANIPULATE.value:
493
+ transform.struct_mask_axis = mask_axis
494
+ transform.struct_mask_span = mask_span
495
+ found_n2v = True
496
+
497
+ if not found_n2v:
498
+ transforms = [t.name for t in self.transforms]
499
+ raise ValueError(
500
+ f"N2V pixel manipulate transform not found in the transforms "
501
+ f"({transforms})."
502
+ )