stouputils 1.14.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- stouputils/__init__.py +40 -0
- stouputils/__main__.py +86 -0
- stouputils/_deprecated.py +37 -0
- stouputils/all_doctests.py +160 -0
- stouputils/applications/__init__.py +22 -0
- stouputils/applications/automatic_docs.py +634 -0
- stouputils/applications/upscaler/__init__.py +39 -0
- stouputils/applications/upscaler/config.py +128 -0
- stouputils/applications/upscaler/image.py +247 -0
- stouputils/applications/upscaler/video.py +287 -0
- stouputils/archive.py +344 -0
- stouputils/backup.py +488 -0
- stouputils/collections.py +244 -0
- stouputils/continuous_delivery/__init__.py +27 -0
- stouputils/continuous_delivery/cd_utils.py +243 -0
- stouputils/continuous_delivery/github.py +522 -0
- stouputils/continuous_delivery/pypi.py +130 -0
- stouputils/continuous_delivery/pyproject.py +147 -0
- stouputils/continuous_delivery/stubs.py +86 -0
- stouputils/ctx.py +408 -0
- stouputils/data_science/config/get.py +51 -0
- stouputils/data_science/config/set.py +125 -0
- stouputils/data_science/data_processing/image/__init__.py +66 -0
- stouputils/data_science/data_processing/image/auto_contrast.py +79 -0
- stouputils/data_science/data_processing/image/axis_flip.py +58 -0
- stouputils/data_science/data_processing/image/bias_field_correction.py +74 -0
- stouputils/data_science/data_processing/image/binary_threshold.py +73 -0
- stouputils/data_science/data_processing/image/blur.py +59 -0
- stouputils/data_science/data_processing/image/brightness.py +54 -0
- stouputils/data_science/data_processing/image/canny.py +110 -0
- stouputils/data_science/data_processing/image/clahe.py +92 -0
- stouputils/data_science/data_processing/image/common.py +30 -0
- stouputils/data_science/data_processing/image/contrast.py +53 -0
- stouputils/data_science/data_processing/image/curvature_flow_filter.py +74 -0
- stouputils/data_science/data_processing/image/denoise.py +378 -0
- stouputils/data_science/data_processing/image/histogram_equalization.py +123 -0
- stouputils/data_science/data_processing/image/invert.py +64 -0
- stouputils/data_science/data_processing/image/laplacian.py +60 -0
- stouputils/data_science/data_processing/image/median_blur.py +52 -0
- stouputils/data_science/data_processing/image/noise.py +59 -0
- stouputils/data_science/data_processing/image/normalize.py +65 -0
- stouputils/data_science/data_processing/image/random_erase.py +66 -0
- stouputils/data_science/data_processing/image/resize.py +69 -0
- stouputils/data_science/data_processing/image/rotation.py +80 -0
- stouputils/data_science/data_processing/image/salt_pepper.py +68 -0
- stouputils/data_science/data_processing/image/sharpening.py +55 -0
- stouputils/data_science/data_processing/image/shearing.py +64 -0
- stouputils/data_science/data_processing/image/threshold.py +64 -0
- stouputils/data_science/data_processing/image/translation.py +71 -0
- stouputils/data_science/data_processing/image/zoom.py +83 -0
- stouputils/data_science/data_processing/image_augmentation.py +118 -0
- stouputils/data_science/data_processing/image_preprocess.py +183 -0
- stouputils/data_science/data_processing/prosthesis_detection.py +359 -0
- stouputils/data_science/data_processing/technique.py +481 -0
- stouputils/data_science/dataset/__init__.py +45 -0
- stouputils/data_science/dataset/dataset.py +292 -0
- stouputils/data_science/dataset/dataset_loader.py +135 -0
- stouputils/data_science/dataset/grouping_strategy.py +296 -0
- stouputils/data_science/dataset/image_loader.py +100 -0
- stouputils/data_science/dataset/xy_tuple.py +696 -0
- stouputils/data_science/metric_dictionnary.py +106 -0
- stouputils/data_science/metric_utils.py +847 -0
- stouputils/data_science/mlflow_utils.py +206 -0
- stouputils/data_science/models/abstract_model.py +149 -0
- stouputils/data_science/models/all.py +85 -0
- stouputils/data_science/models/base_keras.py +765 -0
- stouputils/data_science/models/keras/all.py +38 -0
- stouputils/data_science/models/keras/convnext.py +62 -0
- stouputils/data_science/models/keras/densenet.py +50 -0
- stouputils/data_science/models/keras/efficientnet.py +60 -0
- stouputils/data_science/models/keras/mobilenet.py +56 -0
- stouputils/data_science/models/keras/resnet.py +52 -0
- stouputils/data_science/models/keras/squeezenet.py +233 -0
- stouputils/data_science/models/keras/vgg.py +42 -0
- stouputils/data_science/models/keras/xception.py +38 -0
- stouputils/data_science/models/keras_utils/callbacks/__init__.py +20 -0
- stouputils/data_science/models/keras_utils/callbacks/colored_progress_bar.py +219 -0
- stouputils/data_science/models/keras_utils/callbacks/learning_rate_finder.py +148 -0
- stouputils/data_science/models/keras_utils/callbacks/model_checkpoint_v2.py +31 -0
- stouputils/data_science/models/keras_utils/callbacks/progressive_unfreezing.py +249 -0
- stouputils/data_science/models/keras_utils/callbacks/warmup_scheduler.py +66 -0
- stouputils/data_science/models/keras_utils/losses/__init__.py +12 -0
- stouputils/data_science/models/keras_utils/losses/next_generation_loss.py +56 -0
- stouputils/data_science/models/keras_utils/visualizations.py +416 -0
- stouputils/data_science/models/model_interface.py +939 -0
- stouputils/data_science/models/sandbox.py +116 -0
- stouputils/data_science/range_tuple.py +234 -0
- stouputils/data_science/scripts/augment_dataset.py +77 -0
- stouputils/data_science/scripts/exhaustive_process.py +133 -0
- stouputils/data_science/scripts/preprocess_dataset.py +70 -0
- stouputils/data_science/scripts/routine.py +168 -0
- stouputils/data_science/utils.py +285 -0
- stouputils/decorators.py +605 -0
- stouputils/image.py +441 -0
- stouputils/installer/__init__.py +18 -0
- stouputils/installer/common.py +67 -0
- stouputils/installer/downloader.py +101 -0
- stouputils/installer/linux.py +144 -0
- stouputils/installer/main.py +223 -0
- stouputils/installer/windows.py +136 -0
- stouputils/io.py +486 -0
- stouputils/parallel.py +483 -0
- stouputils/print.py +482 -0
- stouputils/py.typed +1 -0
- stouputils/stouputils/__init__.pyi +15 -0
- stouputils/stouputils/_deprecated.pyi +12 -0
- stouputils/stouputils/all_doctests.pyi +46 -0
- stouputils/stouputils/applications/__init__.pyi +2 -0
- stouputils/stouputils/applications/automatic_docs.pyi +106 -0
- stouputils/stouputils/applications/upscaler/__init__.pyi +3 -0
- stouputils/stouputils/applications/upscaler/config.pyi +18 -0
- stouputils/stouputils/applications/upscaler/image.pyi +109 -0
- stouputils/stouputils/applications/upscaler/video.pyi +60 -0
- stouputils/stouputils/archive.pyi +67 -0
- stouputils/stouputils/backup.pyi +109 -0
- stouputils/stouputils/collections.pyi +86 -0
- stouputils/stouputils/continuous_delivery/__init__.pyi +5 -0
- stouputils/stouputils/continuous_delivery/cd_utils.pyi +129 -0
- stouputils/stouputils/continuous_delivery/github.pyi +162 -0
- stouputils/stouputils/continuous_delivery/pypi.pyi +53 -0
- stouputils/stouputils/continuous_delivery/pyproject.pyi +67 -0
- stouputils/stouputils/continuous_delivery/stubs.pyi +39 -0
- stouputils/stouputils/ctx.pyi +211 -0
- stouputils/stouputils/decorators.pyi +252 -0
- stouputils/stouputils/image.pyi +172 -0
- stouputils/stouputils/installer/__init__.pyi +5 -0
- stouputils/stouputils/installer/common.pyi +39 -0
- stouputils/stouputils/installer/downloader.pyi +24 -0
- stouputils/stouputils/installer/linux.pyi +39 -0
- stouputils/stouputils/installer/main.pyi +57 -0
- stouputils/stouputils/installer/windows.pyi +31 -0
- stouputils/stouputils/io.pyi +213 -0
- stouputils/stouputils/parallel.pyi +216 -0
- stouputils/stouputils/print.pyi +136 -0
- stouputils/stouputils/version_pkg.pyi +15 -0
- stouputils/version_pkg.py +189 -0
- stouputils-1.14.0.dist-info/METADATA +178 -0
- stouputils-1.14.0.dist-info/RECORD +140 -0
- stouputils-1.14.0.dist-info/WHEEL +4 -0
- stouputils-1.14.0.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,481 @@
|
|
|
1
|
+
|
|
2
|
+
# pyright: reportIncompatibleMethodOverride=false
|
|
3
|
+
|
|
4
|
+
# Imports
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import random
|
|
8
|
+
from collections.abc import Callable, Iterable
|
|
9
|
+
from enum import Enum
|
|
10
|
+
from typing import Any, Literal, NamedTuple
|
|
11
|
+
|
|
12
|
+
import cv2
|
|
13
|
+
from numpy.typing import NDArray
|
|
14
|
+
from PIL import Image
|
|
15
|
+
|
|
16
|
+
from ..range_tuple import RangeTuple
|
|
17
|
+
|
|
18
|
+
# Import folders
|
|
19
|
+
from .image import (
|
|
20
|
+
adaptive_denoise_image,
|
|
21
|
+
auto_contrast_image,
|
|
22
|
+
bias_field_correction_image,
|
|
23
|
+
bilateral_denoise_image,
|
|
24
|
+
binary_threshold_image,
|
|
25
|
+
blur_image,
|
|
26
|
+
brightness_image,
|
|
27
|
+
canny_image,
|
|
28
|
+
clahe_image,
|
|
29
|
+
contrast_image,
|
|
30
|
+
curvature_flow_filter_image,
|
|
31
|
+
flip_image,
|
|
32
|
+
invert_image,
|
|
33
|
+
laplacian_image,
|
|
34
|
+
median_blur_image,
|
|
35
|
+
nlm_denoise_image,
|
|
36
|
+
noise_image,
|
|
37
|
+
normalize_image,
|
|
38
|
+
random_erase_image,
|
|
39
|
+
resize_image,
|
|
40
|
+
rotate_image,
|
|
41
|
+
salt_pepper_image,
|
|
42
|
+
sharpen_image,
|
|
43
|
+
shear_image,
|
|
44
|
+
threshold_image,
|
|
45
|
+
translate_image,
|
|
46
|
+
tv_denoise_image,
|
|
47
|
+
wavelet_denoise_image,
|
|
48
|
+
zoom_image,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
# Tuple class
|
|
53
|
+
class ProcessingTechnique(NamedTuple):
|
|
54
|
+
""" A named tuple containing an processing technique.
|
|
55
|
+
|
|
56
|
+
The following descriptions are the recommendations:
|
|
57
|
+
|
|
58
|
+
- rotation: Rotation between -20° and +20° is generally safe
|
|
59
|
+
- translation: Translation up to 15% to avoid position bias
|
|
60
|
+
- shearing: Shearing up to 15° on x and y axes
|
|
61
|
+
- noise: Gaussian noise with fixed values [0.3,0.4,0.5]
|
|
62
|
+
- salt-pepper: Salt-pepper noise with densities [0.01,0.02,0.03]
|
|
63
|
+
- sharpening: Gaussian blur (variance=1) then subtract from original
|
|
64
|
+
- contrast: Linear scaling between min/max intensities [0.7,1.3]
|
|
65
|
+
- axis_flip: Flip along x and/or y axis (0 for x, 1 for y, 2 for both), probability=0.5
|
|
66
|
+
- zoom: Magnification/zoom between 85% and 115%
|
|
67
|
+
- brightness: Adjust overall image brightness [0.7,1.3]
|
|
68
|
+
- blur: Gaussian blur for reduced sharpness [0.5,2.0]
|
|
69
|
+
- random_erase: Randomly erase rectangles (1-10% of image) to learn descriptive features
|
|
70
|
+
- clahe: Contrast Limited Adaptive Histogram Equalization (clip_limit=[1.0,4.0], tile_size=8)
|
|
71
|
+
- binary_threshold: Binary thresholding with threshold [0.1,0.9]
|
|
72
|
+
- threshold: Multi-level thresholding with levels [0.3,0.6]
|
|
73
|
+
- canny: Canny edge detection (thresholds=[50/255,150/255], aperture=3)
|
|
74
|
+
- laplacian: Laplacian edge detection with 3x3 kernel
|
|
75
|
+
- auto_contrast: Auto contrast the image
|
|
76
|
+
- bias_field_correction: Bias field correction
|
|
77
|
+
- curvature_flow_filter: Curvature flow filter (time_step=0.05, iterations=5)
|
|
78
|
+
- median_blur: Median blur (kernel_size=15, iterations=10)
|
|
79
|
+
- resize: Resize the image (width=224, height=224, resample=Image.Resampling.LANCZOS)
|
|
80
|
+
- normalize: Normalize the image (min=0, max=255, method=cv2.NORM_MINMAX)
|
|
81
|
+
- nlm_denoise: Non-local means denoising (h=10, template_window_size=7, search_window_size=21)
|
|
82
|
+
- bilateral_denoise: Bilateral filter denoising (d=9, sigma_color=75, sigma_space=75)
|
|
83
|
+
- tv_denoise: Total variation denoising (weight=0.1, iterations=30)
|
|
84
|
+
- wavelet_denoise: Wavelet denoising (wavelet='db1', mode='soft', wavelet_levels=3)
|
|
85
|
+
- adaptive_denoise: Adaptive denoising (method="nlm", strength=0.5)
|
|
86
|
+
- custom: Custom processing technique (callable) # Ex: ProcessingTechnique("custom", custom=f)
|
|
87
|
+
"""
|
|
88
|
+
# The following descriptions are the recommendations
|
|
89
|
+
name: Literal[
|
|
90
|
+
"rotation", "translation", "shearing", "noise", "salt-pepper", "sharpening",
|
|
91
|
+
"contrast", "axis_flip", "zoom", "brightness", "blur", "random_erase", "clahe",
|
|
92
|
+
"binary_threshold", "threshold", "canny", "laplacian", "auto_contrast",
|
|
93
|
+
"bias_field_correction", "curvature_flow_filter", "resize",
|
|
94
|
+
"normalize", "median_blur", "nlm_denoise", "bilateral_denoise",
|
|
95
|
+
"tv_denoise", "wavelet_denoise", "adaptive_denoise", "invert", "custom"
|
|
96
|
+
]
|
|
97
|
+
""" Name of the processing technique """
|
|
98
|
+
ranges: list[Iterable[Any] | RangeTuple]
|
|
99
|
+
""" List of ranges for the processing technique.
|
|
100
|
+
Depending on the technique, multiple ranges might be needed.
|
|
101
|
+
Ex: Translation (x and y axes) or Shearing (x and y axes). """
|
|
102
|
+
probability: float = 1.0
|
|
103
|
+
""" Probability of applying the processing technique (default: 1.0).
|
|
104
|
+
Should be used on techniques like "axis_flip" or "random_erase"
|
|
105
|
+
where the probability of applying the technique is not 100%. """
|
|
106
|
+
custom: Callable[..., NDArray[Any]] | None = None
|
|
107
|
+
""" Custom processing technique (callable), name must be "custom", e.g. ProcessingTechnique("custom", custom=f) """
|
|
108
|
+
|
|
109
|
+
def __str__(self) -> str:
|
|
110
|
+
return (
|
|
111
|
+
f"name={self.name}, ranges={self.ranges}, probability={self.probability}, "
|
|
112
|
+
f"custom={self.custom.__name__ if self.custom else None}"
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
def __repr__(self) -> str:
|
|
116
|
+
return (
|
|
117
|
+
f"ProcessingTechnique(name={self.name!r}, ranges={self.ranges!r}, "
|
|
118
|
+
f"probability={self.probability}, custom={self.custom.__name__ if self.custom else None}"
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
def __mul__(self, other: float) -> ProcessingTechnique:
|
|
122
|
+
""" Multiply all ranges by a scalar value.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
other (float): Value to multiply ranges by
|
|
126
|
+
Returns:
|
|
127
|
+
ProcessingTechnique: New object with scaled ranges
|
|
128
|
+
"""
|
|
129
|
+
return ProcessingTechnique(
|
|
130
|
+
self.name,
|
|
131
|
+
[
|
|
132
|
+
(r * other) if isinstance(r, RangeTuple) else [x * other if isinstance(x, float | int) else x for x in r]
|
|
133
|
+
for r in self.ranges
|
|
134
|
+
],
|
|
135
|
+
probability=self.probability,
|
|
136
|
+
custom=self.custom
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
def __truediv__(self, other: float) -> ProcessingTechnique:
|
|
140
|
+
""" Divide all ranges by a scalar value.
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
other (float): Value to divide ranges by
|
|
144
|
+
Returns:
|
|
145
|
+
ProcessingTechnique: New object with scaled ranges
|
|
146
|
+
"""
|
|
147
|
+
return ProcessingTechnique(
|
|
148
|
+
self.name,
|
|
149
|
+
[
|
|
150
|
+
(r / other) if isinstance(r, RangeTuple) else [x / other if isinstance(x, float | int) else x for x in r]
|
|
151
|
+
for r in self.ranges
|
|
152
|
+
],
|
|
153
|
+
probability=self.probability,
|
|
154
|
+
custom=self.custom
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
def deterministic(self, use_default: bool = False) -> ProcessingTechnique:
|
|
158
|
+
""" Convert the RangeTuple to values by calling the RangeTuple.random() method. """
|
|
159
|
+
# Make values deterministic
|
|
160
|
+
values: list[Iterable[Any]] = []
|
|
161
|
+
for range in self.ranges:
|
|
162
|
+
if isinstance(range, RangeTuple):
|
|
163
|
+
if use_default:
|
|
164
|
+
values.append([range.default])
|
|
165
|
+
else:
|
|
166
|
+
values.append([range.random()])
|
|
167
|
+
else:
|
|
168
|
+
values.append(range)
|
|
169
|
+
|
|
170
|
+
# Make probability deterministic (0 or 1)
|
|
171
|
+
probability: float = 1.0 if random.random() < self.probability else 0.0
|
|
172
|
+
return ProcessingTechnique(self.name, values, probability=probability, custom=self.custom)
|
|
173
|
+
|
|
174
|
+
def apply(self, image: NDArray[Any], dividers: tuple[float, float] = (1.0, 1.0), times: int = 1) -> NDArray[Any]:
|
|
175
|
+
""" Apply the processing technique to the image.
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
image (NDArray[Any]): Image to apply the processing technique to
|
|
179
|
+
dividers (tuple[float, float]): Dividers used to adjust the processing technique parameters (default: (1.0, 1.0))
|
|
180
|
+
times (int): Number of times to apply the processing technique (default: 1)
|
|
181
|
+
Returns:
|
|
182
|
+
NDArray[Any]: Processed image
|
|
183
|
+
"""
|
|
184
|
+
assert not any(isinstance(x, RangeTuple) for x in self.ranges), (
|
|
185
|
+
"All RangeTuples must be converted to values, "
|
|
186
|
+
f"please call deterministic() before. {self.ranges=}"
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
# Check if the technique is applied
|
|
190
|
+
if random.random() > self.probability:
|
|
191
|
+
return image
|
|
192
|
+
|
|
193
|
+
for _ in range(times):
|
|
194
|
+
|
|
195
|
+
# Apply the processing technique
|
|
196
|
+
if self.name == "rotation":
|
|
197
|
+
angle: float = next(iter(self.ranges[0]))
|
|
198
|
+
image = rotate_image(image, angle)
|
|
199
|
+
|
|
200
|
+
elif self.name == "translation":
|
|
201
|
+
x_shift: float = next(iter(self.ranges[0])) / dividers[0]
|
|
202
|
+
y_shift: float = next(iter(self.ranges[1])) / dividers[1]
|
|
203
|
+
image = translate_image(image, x_shift, y_shift)
|
|
204
|
+
|
|
205
|
+
elif self.name == "shearing":
|
|
206
|
+
x_shear: float = next(iter(self.ranges[0])) / dividers[0]
|
|
207
|
+
y_shear: float = next(iter(self.ranges[1])) / dividers[1]
|
|
208
|
+
image = shear_image(image, x_shear, y_shear)
|
|
209
|
+
|
|
210
|
+
elif self.name == "axis_flip":
|
|
211
|
+
axis: Literal["horizontal", "vertical", "both"] = next(iter(self.ranges[0]))
|
|
212
|
+
image = flip_image(image, axis)
|
|
213
|
+
|
|
214
|
+
elif self.name == "noise":
|
|
215
|
+
intensity: float = next(iter(self.ranges[0]))
|
|
216
|
+
image = noise_image(image, intensity)
|
|
217
|
+
|
|
218
|
+
elif self.name == "salt-pepper":
|
|
219
|
+
density: float = next(iter(self.ranges[0]))
|
|
220
|
+
image = salt_pepper_image(image, density)
|
|
221
|
+
|
|
222
|
+
elif self.name == "sharpening":
|
|
223
|
+
alpha: float = next(iter(self.ranges[0]))
|
|
224
|
+
image = sharpen_image(image, alpha)
|
|
225
|
+
|
|
226
|
+
elif self.name == "contrast":
|
|
227
|
+
factor: float = next(iter(self.ranges[0]))
|
|
228
|
+
image = contrast_image(image, factor)
|
|
229
|
+
|
|
230
|
+
elif self.name == "zoom":
|
|
231
|
+
zoom_factor: float = next(iter(self.ranges[0])) / max(dividers)
|
|
232
|
+
image = zoom_image(image, zoom_factor)
|
|
233
|
+
|
|
234
|
+
elif self.name == "brightness":
|
|
235
|
+
brightness_factor: float = next(iter(self.ranges[0]))
|
|
236
|
+
image = brightness_image(image, brightness_factor)
|
|
237
|
+
|
|
238
|
+
elif self.name == "blur":
|
|
239
|
+
sigma: float = next(iter(self.ranges[0]))
|
|
240
|
+
image = blur_image(image, sigma)
|
|
241
|
+
|
|
242
|
+
elif self.name == "random_erase":
|
|
243
|
+
ratio: float = next(iter(self.ranges[0])) / max(dividers)
|
|
244
|
+
image = random_erase_image(image, ratio)
|
|
245
|
+
|
|
246
|
+
elif self.name == "clahe":
|
|
247
|
+
clip_limit: float = next(iter(self.ranges[0]))
|
|
248
|
+
tile_grid_size: int = int(next(iter(self.ranges[0])))
|
|
249
|
+
image = clahe_image(image, clip_limit, tile_grid_size)
|
|
250
|
+
|
|
251
|
+
elif self.name == "binary_threshold":
|
|
252
|
+
threshold: float = next(iter(self.ranges[0]))
|
|
253
|
+
image = binary_threshold_image(image, threshold)
|
|
254
|
+
|
|
255
|
+
elif self.name == "threshold":
|
|
256
|
+
thresholds: list[float] = [next(iter(r)) for r in self.ranges]
|
|
257
|
+
image = threshold_image(image, thresholds)
|
|
258
|
+
|
|
259
|
+
elif self.name == "canny":
|
|
260
|
+
threshold1: float = next(iter(self.ranges[0]))
|
|
261
|
+
threshold2: float = next(iter(self.ranges[1]))
|
|
262
|
+
aperture_size: int = int(next(iter(self.ranges[2]))) if len(self.ranges) > 2 else 3
|
|
263
|
+
image = canny_image(image, threshold1, threshold2, aperture_size)
|
|
264
|
+
|
|
265
|
+
elif self.name == "laplacian":
|
|
266
|
+
kernel_size: int = int(next(iter(self.ranges[0]))) if self.ranges else 3
|
|
267
|
+
image = laplacian_image(image, kernel_size)
|
|
268
|
+
|
|
269
|
+
elif self.name == "auto_contrast":
|
|
270
|
+
image = auto_contrast_image(image)
|
|
271
|
+
|
|
272
|
+
elif self.name == "curvature_flow_filter":
|
|
273
|
+
time_step: float = next(iter(self.ranges[0]))
|
|
274
|
+
number_of_iterations: int = int(next(iter(self.ranges[1])))
|
|
275
|
+
image = curvature_flow_filter_image(image, time_step, number_of_iterations)
|
|
276
|
+
|
|
277
|
+
elif self.name == "bias_field_correction":
|
|
278
|
+
image = bias_field_correction_image(image)
|
|
279
|
+
|
|
280
|
+
elif self.name == "resize":
|
|
281
|
+
width: int = int(next(iter(self.ranges[0])))
|
|
282
|
+
height: int = int(next(iter(self.ranges[1])))
|
|
283
|
+
if len(self.ranges) > 2:
|
|
284
|
+
image = resize_image(image, width, height, Image.Resampling(next(iter(self.ranges[2]))))
|
|
285
|
+
else:
|
|
286
|
+
image = resize_image(image, width, height)
|
|
287
|
+
|
|
288
|
+
elif self.name == "normalize":
|
|
289
|
+
mini: float | int = next(iter(self.ranges[0]))
|
|
290
|
+
maxi: float | int = next(iter(self.ranges[1]))
|
|
291
|
+
norm_method: int = int(next(iter(self.ranges[2])))
|
|
292
|
+
image = normalize_image(image, mini, maxi, norm_method)
|
|
293
|
+
|
|
294
|
+
elif self.name == "median_blur":
|
|
295
|
+
kernel_size: int = int(next(iter(self.ranges[0])))
|
|
296
|
+
iterations: int = int(next(iter(self.ranges[1])))
|
|
297
|
+
image = median_blur_image(image, kernel_size, iterations)
|
|
298
|
+
|
|
299
|
+
elif self.name == "nlm_denoise":
|
|
300
|
+
h: float = float(next(iter(self.ranges[0])))
|
|
301
|
+
template_window_size: int = int(next(iter(self.ranges[1]))) if len(self.ranges) > 1 else 7
|
|
302
|
+
search_window_size: int = int(next(iter(self.ranges[2]))) if len(self.ranges) > 2 else 21
|
|
303
|
+
image = nlm_denoise_image(image, h, template_window_size, search_window_size)
|
|
304
|
+
|
|
305
|
+
elif self.name == "bilateral_denoise":
|
|
306
|
+
d: int = int(next(iter(self.ranges[0])))
|
|
307
|
+
sigma_color: float = float(next(iter(self.ranges[1]))) if len(self.ranges) > 1 else 75.0
|
|
308
|
+
sigma_space: float = float(next(iter(self.ranges[2]))) if len(self.ranges) > 2 else 75.0
|
|
309
|
+
image = bilateral_denoise_image(image, d, sigma_color, sigma_space)
|
|
310
|
+
|
|
311
|
+
elif self.name == "tv_denoise":
|
|
312
|
+
weight: float = float(next(iter(self.ranges[0])))
|
|
313
|
+
iterations: int = int(next(iter(self.ranges[1]))) if len(self.ranges) > 1 else 30
|
|
314
|
+
method_value = str(next(iter(self.ranges[2]))) if len(self.ranges) > 2 else "chambolle"
|
|
315
|
+
tv_method: Literal["chambolle", "bregman"] = "chambolle" if method_value == "chambolle" else "bregman"
|
|
316
|
+
image = tv_denoise_image(image, weight, iterations, tv_method)
|
|
317
|
+
|
|
318
|
+
elif self.name == "wavelet_denoise":
|
|
319
|
+
wavelet_levels: int = int(next(iter(self.ranges[0])))
|
|
320
|
+
wavelet_value = str(next(iter(self.ranges[1]))) if len(self.ranges) > 1 else "db1"
|
|
321
|
+
mode_value = str(next(iter(self.ranges[2]))) if len(self.ranges) > 2 else "soft"
|
|
322
|
+
# sigma is None by default in the function, so we don't provide it explicitly
|
|
323
|
+
image = wavelet_denoise_image(
|
|
324
|
+
image,
|
|
325
|
+
wavelet=wavelet_value,
|
|
326
|
+
mode=mode_value,
|
|
327
|
+
wavelet_levels=wavelet_levels
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
elif self.name == "adaptive_denoise":
|
|
331
|
+
method_value = str(next(iter(self.ranges[0])))
|
|
332
|
+
if len(self.ranges) > 1:
|
|
333
|
+
image = adaptive_denoise_image(image, method_value, float(next(iter(self.ranges[1]))))
|
|
334
|
+
else:
|
|
335
|
+
image = adaptive_denoise_image(image, method_value)
|
|
336
|
+
|
|
337
|
+
elif self.name == "invert":
|
|
338
|
+
image = invert_image(image)
|
|
339
|
+
|
|
340
|
+
elif self.name == "custom":
|
|
341
|
+
if self.custom is None:
|
|
342
|
+
raise ValueError(
|
|
343
|
+
"Custom processing technique is not defined, please set the custom attribute, "
|
|
344
|
+
"ex: ProcessingTechnique('custom', custom=f)"
|
|
345
|
+
)
|
|
346
|
+
args: list[Any] = [next(iter(r)) for r in self.ranges]
|
|
347
|
+
image = self.custom(image, *args)
|
|
348
|
+
|
|
349
|
+
else:
|
|
350
|
+
raise ValueError(f"Augmentation technique {self.name} is not supported.")
|
|
351
|
+
|
|
352
|
+
return image
|
|
353
|
+
|
|
354
|
+
def __call__(self, image: NDArray[Any], dividers: tuple[float, float] = (1.0, 1.0), times: int = 1) -> NDArray[Any]:
|
|
355
|
+
""" Apply the processing technique to the image.
|
|
356
|
+
|
|
357
|
+
Args:
|
|
358
|
+
image (NDArray[Any]): Image to apply the processing technique to
|
|
359
|
+
dividers (tuple[float, float]): Dividers used to adjust the processing technique parameters
|
|
360
|
+
(default: (1.0, 1.0))
|
|
361
|
+
times (int): Number of times to apply the processing technique
|
|
362
|
+
(default: 1)
|
|
363
|
+
Returns:
|
|
364
|
+
NDArray[Any]: Processed image
|
|
365
|
+
"""
|
|
366
|
+
return self.apply(image, dividers, times)
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
# Recommendations enumerated
|
|
370
|
+
class RecommendedProcessingTechnique(Enum):
|
|
371
|
+
""" A class containing the processing techniques with their recommended ranges based on scientific papers. """
|
|
372
|
+
|
|
373
|
+
ROTATION = ProcessingTechnique("rotation", [RangeTuple(mini=-20, maxi=20, step=1, default=0)])
|
|
374
|
+
""" Rotation between -20° and +20° is generally safe for medical images """
|
|
375
|
+
|
|
376
|
+
TRANSLATION = ProcessingTechnique("translation", list(2 * [RangeTuple(mini=-0.15, maxi=0.15, step=0.01, default=0)]))
|
|
377
|
+
""" Translation between -15% and 15% of image size """
|
|
378
|
+
|
|
379
|
+
SHEARING = ProcessingTechnique("shearing", list(2 * [RangeTuple(mini=-10, maxi=10, step=1, default=0)]))
|
|
380
|
+
""" Shearing between -10% and 10% distortion """
|
|
381
|
+
|
|
382
|
+
AXIS_FLIP = ProcessingTechnique("axis_flip", [["horizontal"]], probability=0.5)
|
|
383
|
+
""" Flipping: horizontal is much more used than vertical """
|
|
384
|
+
|
|
385
|
+
NOISE = ProcessingTechnique("noise", [RangeTuple(mini=0.1, maxi=0.4, step=0.05, default=0.2)])
|
|
386
|
+
""" Noise: FV values between 0.1 and 0.4 for gaussian noise """
|
|
387
|
+
|
|
388
|
+
SALT_PEPPER = ProcessingTechnique("salt-pepper", [RangeTuple(mini=0.1, maxi=0.5, step=0.05, default=0.2)])
|
|
389
|
+
""" Salt-pepper: densities between 0.1 and 0.5 """
|
|
390
|
+
|
|
391
|
+
SHARPENING = ProcessingTechnique("sharpening", [RangeTuple(mini=0.5, maxi=1.5, step=0.1, default=1.1)])
|
|
392
|
+
""" Sharpening: gaussian blur with variance=1 then subtract from original """
|
|
393
|
+
|
|
394
|
+
CONTRAST = ProcessingTechnique("contrast", [RangeTuple(mini=0.7, maxi=1.3, step=0.1, default=1.1)])
|
|
395
|
+
""" Contrast: linear scaling between min and max intensity """
|
|
396
|
+
|
|
397
|
+
ZOOM = ProcessingTechnique("zoom", [RangeTuple(mini=0.85, maxi=1.15, step=0.05, default=1.05)])
|
|
398
|
+
""" Zoom (Magnification): between 85% and 115% """
|
|
399
|
+
|
|
400
|
+
BRIGHTNESS = ProcessingTechnique("brightness", [RangeTuple(mini=0.7, maxi=1.3, step=0.1, default=1.1)])
|
|
401
|
+
""" Brightness: moderate changes to avoid losing details """
|
|
402
|
+
|
|
403
|
+
BLUR = ProcessingTechnique("blur", [RangeTuple(mini=0.5, maxi=2, step=0.25, default=1)])
|
|
404
|
+
""" Blur: gaussian blur preferred, moderate values """
|
|
405
|
+
|
|
406
|
+
RANDOM_ERASE = ProcessingTechnique("random_erase", [RangeTuple(mini=0.01, maxi=0.1, step=0.01, default=0.05)])
|
|
407
|
+
""" Random erasing: small regions to force model to learn descriptive features """
|
|
408
|
+
|
|
409
|
+
CLAHE = ProcessingTechnique(
|
|
410
|
+
"clahe",
|
|
411
|
+
[
|
|
412
|
+
RangeTuple(mini=1.0, maxi=4.0, step=0.5, default=2.0), # clip_limit
|
|
413
|
+
[8] # tile_grid_size (fixed at 8x8)
|
|
414
|
+
]
|
|
415
|
+
)
|
|
416
|
+
""" CLAHE: Contrast Limited Adaptive Histogram Equalization with recommended parameters """
|
|
417
|
+
|
|
418
|
+
BINARY_THRESHOLD = ProcessingTechnique("binary_threshold", [RangeTuple(mini=0.1, maxi=0.9, step=0.1, default=0.5)])
|
|
419
|
+
""" Binary threshold: threshold between 0.1 and 0.9 """
|
|
420
|
+
|
|
421
|
+
THRESHOLD = ProcessingTechnique("threshold", [[0.3], [0.6]])
|
|
422
|
+
""" Multi-level threshold: creates X levels of thresholding """
|
|
423
|
+
|
|
424
|
+
CANNY = ProcessingTechnique("canny", [[50 / 255], [150 / 255], [3]])
|
|
425
|
+
""" Canny edge detection with recommended parameters """
|
|
426
|
+
|
|
427
|
+
LAPLACIAN = ProcessingTechnique("laplacian", [[3]]) # kernel_size
|
|
428
|
+
""" Laplacian edge detection with 3x3 kernel """
|
|
429
|
+
|
|
430
|
+
AUTO_CONTRAST = ProcessingTechnique("auto_contrast", [])
|
|
431
|
+
""" Auto contrast the image """
|
|
432
|
+
|
|
433
|
+
CURVATURE_FLOW_FILTER = ProcessingTechnique("curvature_flow_filter", [[0.05], [5]])
|
|
434
|
+
""" Curvature flow filter with recommended parameters """
|
|
435
|
+
|
|
436
|
+
BIAS_FIELD_CORRECTION = ProcessingTechnique("bias_field_correction", [])
|
|
437
|
+
""" Bias field correction with recommended parameters """
|
|
438
|
+
|
|
439
|
+
NORMALIZE = ProcessingTechnique("normalize", [[0], [255], [cv2.NORM_MINMAX]])
|
|
440
|
+
""" Normalize the image to the range 0-255 """
|
|
441
|
+
|
|
442
|
+
MEDIAN_BLUR = ProcessingTechnique("median_blur", [[7], [1]])
|
|
443
|
+
""" Median blur with 7x7 kernel and 1 iteration """
|
|
444
|
+
|
|
445
|
+
NLM_DENOISE = ProcessingTechnique("nlm_denoise", [
|
|
446
|
+
RangeTuple(mini=5, maxi=20, step=1, default=10), # h: affects the strength of the denoising
|
|
447
|
+
[7], # template_window_size: size of the template window
|
|
448
|
+
[21] # search_window_size: size of the search window
|
|
449
|
+
])
|
|
450
|
+
""" Non-local means denoising with recommended parameters """
|
|
451
|
+
|
|
452
|
+
BILATERAL_DENOISE = ProcessingTechnique("bilateral_denoise", [
|
|
453
|
+
[9], # diameter: size of the pixel neighborhood
|
|
454
|
+
RangeTuple(mini=30, maxi=150, step=10, default=75), # sigma_color: filter sigma in the color space
|
|
455
|
+
RangeTuple(mini=30, maxi=150, step=10, default=75) # sigma_space: filter sigma in the coordinate space
|
|
456
|
+
])
|
|
457
|
+
""" Bilateral filter denoising with recommended parameters """
|
|
458
|
+
|
|
459
|
+
TV_DENOISE = ProcessingTechnique("tv_denoise", [
|
|
460
|
+
RangeTuple(mini=0.05, maxi=0.5, step=0.05, default=0.1), # weight: denoising weight
|
|
461
|
+
[30], # max_iter: maximum number of iterations
|
|
462
|
+
["chambolle"] # method: algorithm used for denoising
|
|
463
|
+
])
|
|
464
|
+
""" Total variation denoising with recommended parameters """
|
|
465
|
+
|
|
466
|
+
WAVELET_DENOISE = ProcessingTechnique("wavelet_denoise", [
|
|
467
|
+
[3], # wavelet_levels: number of wavelet decomposition levels
|
|
468
|
+
["db1"], # wavelet: wavelet to use
|
|
469
|
+
["soft"] # mode: thresholding mode
|
|
470
|
+
])
|
|
471
|
+
""" Wavelet denoising with recommended parameters """
|
|
472
|
+
|
|
473
|
+
ADAPTIVE_DENOISE = ProcessingTechnique("adaptive_denoise", [
|
|
474
|
+
["nlm"], # method: denoising method to use
|
|
475
|
+
RangeTuple(mini=0.1, maxi=0.9, step=0.1, default=0.5) # strength: denoising strength parameter
|
|
476
|
+
])
|
|
477
|
+
""" Adaptive denoising with recommended parameters """
|
|
478
|
+
|
|
479
|
+
INVERT = ProcessingTechnique("invert", [], probability=0.5)
|
|
480
|
+
""" Invert the colors of an image with a 50% probability, hoping the model doesn't focus on bright parts only """
|
|
481
|
+
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
""" Package for advanced dataset handling.
|
|
2
|
+
|
|
3
|
+
Provides comprehensive tools for loading, processing and managing image datasets
|
|
4
|
+
with special handling for augmented data and group-aware operations.
|
|
5
|
+
|
|
6
|
+
Main Components:
|
|
7
|
+
|
|
8
|
+
- Dataset : Core class for storing and managing dataset splits with metadata
|
|
9
|
+
- DatasetLoader : Handles dataset loading from directories with various strategies
|
|
10
|
+
- DatasetSplitter : Manages stratified splitting while maintaining group integrity
|
|
11
|
+
- GroupingStrategy : Enum defining image grouping approaches (NONE/SIMPLE/CONCATENATE)
|
|
12
|
+
- XyTuple : Specialized container for features/labels with file tracking
|
|
13
|
+
|
|
14
|
+
Key Features:
|
|
15
|
+
|
|
16
|
+
- Augmented data handling with original file mapping
|
|
17
|
+
- Prevention of data leakage between train/test sets
|
|
18
|
+
- Support for multiple grouping strategies at subject/image level
|
|
19
|
+
- Class-aware dataset splitting with stratification
|
|
20
|
+
- Comprehensive metadata tracking (class distributions, file paths)
|
|
21
|
+
- Compatibility with keras.image_dataset_from_directory
|
|
22
|
+
- Group-aware k-fold cross validation support
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
# Imports
|
|
26
|
+
from .dataset import Dataset
|
|
27
|
+
from .dataset_loader import DatasetLoader
|
|
28
|
+
from .grouping_strategy import GroupingStrategy
|
|
29
|
+
from .image_loader import ALLOWLIST_FORMATS, load_images_from_directory
|
|
30
|
+
from .xy_tuple import XyTuple
|
|
31
|
+
|
|
32
|
+
# Constants
|
|
33
|
+
LOWER_GS: tuple[str, ...] = tuple(x.name.lower() for x in GroupingStrategy)
|
|
34
|
+
|
|
35
|
+
# Exports
|
|
36
|
+
__all__ = [
|
|
37
|
+
"ALLOWLIST_FORMATS",
|
|
38
|
+
"LOWER_GS",
|
|
39
|
+
"Dataset",
|
|
40
|
+
"DatasetLoader",
|
|
41
|
+
"GroupingStrategy",
|
|
42
|
+
"XyTuple",
|
|
43
|
+
"load_images_from_directory",
|
|
44
|
+
]
|
|
45
|
+
|