stouputils 1.14.0__py3-none-any.whl → 1.14.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.
- stouputils/__init__.pyi +15 -0
- stouputils/_deprecated.pyi +12 -0
- stouputils/all_doctests.pyi +46 -0
- stouputils/applications/__init__.pyi +2 -0
- stouputils/applications/automatic_docs.py +3 -0
- stouputils/applications/automatic_docs.pyi +106 -0
- stouputils/applications/upscaler/__init__.pyi +3 -0
- stouputils/applications/upscaler/config.pyi +18 -0
- stouputils/applications/upscaler/image.pyi +109 -0
- stouputils/applications/upscaler/video.pyi +60 -0
- stouputils/archive.pyi +67 -0
- stouputils/backup.pyi +109 -0
- stouputils/collections.pyi +86 -0
- stouputils/continuous_delivery/__init__.pyi +5 -0
- stouputils/continuous_delivery/cd_utils.pyi +129 -0
- stouputils/continuous_delivery/github.pyi +162 -0
- stouputils/continuous_delivery/pypi.pyi +52 -0
- stouputils/continuous_delivery/pyproject.pyi +67 -0
- stouputils/continuous_delivery/stubs.pyi +39 -0
- stouputils/ctx.pyi +211 -0
- stouputils/data_science/config/get.py +51 -51
- stouputils/data_science/data_processing/image/__init__.py +66 -66
- stouputils/data_science/data_processing/image/auto_contrast.py +79 -79
- stouputils/data_science/data_processing/image/axis_flip.py +58 -58
- stouputils/data_science/data_processing/image/bias_field_correction.py +74 -74
- stouputils/data_science/data_processing/image/binary_threshold.py +73 -73
- stouputils/data_science/data_processing/image/blur.py +59 -59
- stouputils/data_science/data_processing/image/brightness.py +54 -54
- stouputils/data_science/data_processing/image/canny.py +110 -110
- stouputils/data_science/data_processing/image/clahe.py +92 -92
- stouputils/data_science/data_processing/image/common.py +30 -30
- stouputils/data_science/data_processing/image/contrast.py +53 -53
- stouputils/data_science/data_processing/image/curvature_flow_filter.py +74 -74
- stouputils/data_science/data_processing/image/denoise.py +378 -378
- stouputils/data_science/data_processing/image/histogram_equalization.py +123 -123
- stouputils/data_science/data_processing/image/invert.py +64 -64
- stouputils/data_science/data_processing/image/laplacian.py +60 -60
- stouputils/data_science/data_processing/image/median_blur.py +52 -52
- stouputils/data_science/data_processing/image/noise.py +59 -59
- stouputils/data_science/data_processing/image/normalize.py +65 -65
- stouputils/data_science/data_processing/image/random_erase.py +66 -66
- stouputils/data_science/data_processing/image/resize.py +69 -69
- stouputils/data_science/data_processing/image/rotation.py +80 -80
- stouputils/data_science/data_processing/image/salt_pepper.py +68 -68
- stouputils/data_science/data_processing/image/sharpening.py +55 -55
- stouputils/data_science/data_processing/image/shearing.py +64 -64
- stouputils/data_science/data_processing/image/threshold.py +64 -64
- stouputils/data_science/data_processing/image/translation.py +71 -71
- stouputils/data_science/data_processing/image/zoom.py +83 -83
- stouputils/data_science/data_processing/image_augmentation.py +118 -118
- stouputils/data_science/data_processing/image_preprocess.py +183 -183
- stouputils/data_science/data_processing/prosthesis_detection.py +359 -359
- stouputils/data_science/data_processing/technique.py +481 -481
- stouputils/data_science/dataset/__init__.py +45 -45
- stouputils/data_science/dataset/dataset.py +292 -292
- stouputils/data_science/dataset/dataset_loader.py +135 -135
- stouputils/data_science/dataset/grouping_strategy.py +296 -296
- stouputils/data_science/dataset/image_loader.py +100 -100
- stouputils/data_science/dataset/xy_tuple.py +696 -696
- stouputils/data_science/metric_dictionnary.py +106 -106
- stouputils/data_science/mlflow_utils.py +206 -206
- stouputils/data_science/models/abstract_model.py +149 -149
- stouputils/data_science/models/all.py +85 -85
- stouputils/data_science/models/keras/all.py +38 -38
- stouputils/data_science/models/keras/convnext.py +62 -62
- stouputils/data_science/models/keras/densenet.py +50 -50
- stouputils/data_science/models/keras/efficientnet.py +60 -60
- stouputils/data_science/models/keras/mobilenet.py +56 -56
- stouputils/data_science/models/keras/resnet.py +52 -52
- stouputils/data_science/models/keras/squeezenet.py +233 -233
- stouputils/data_science/models/keras/vgg.py +42 -42
- stouputils/data_science/models/keras/xception.py +38 -38
- stouputils/data_science/models/keras_utils/callbacks/__init__.py +20 -20
- stouputils/data_science/models/keras_utils/callbacks/colored_progress_bar.py +219 -219
- stouputils/data_science/models/keras_utils/callbacks/learning_rate_finder.py +148 -148
- stouputils/data_science/models/keras_utils/callbacks/model_checkpoint_v2.py +31 -31
- stouputils/data_science/models/keras_utils/callbacks/progressive_unfreezing.py +249 -249
- stouputils/data_science/models/keras_utils/callbacks/warmup_scheduler.py +66 -66
- stouputils/data_science/models/keras_utils/losses/__init__.py +12 -12
- stouputils/data_science/models/keras_utils/losses/next_generation_loss.py +56 -56
- stouputils/data_science/models/keras_utils/visualizations.py +416 -416
- stouputils/data_science/models/sandbox.py +116 -116
- stouputils/data_science/range_tuple.py +234 -234
- stouputils/data_science/utils.py +285 -285
- stouputils/decorators.pyi +242 -0
- stouputils/image.pyi +172 -0
- stouputils/installer/__init__.py +18 -18
- stouputils/installer/__init__.pyi +5 -0
- stouputils/installer/common.pyi +39 -0
- stouputils/installer/downloader.pyi +24 -0
- stouputils/installer/linux.py +144 -144
- stouputils/installer/linux.pyi +39 -0
- stouputils/installer/main.py +223 -223
- stouputils/installer/main.pyi +57 -0
- stouputils/installer/windows.py +136 -136
- stouputils/installer/windows.pyi +31 -0
- stouputils/io.pyi +213 -0
- stouputils/parallel.py +12 -10
- stouputils/parallel.pyi +211 -0
- stouputils/print.pyi +136 -0
- stouputils/py.typed +1 -1
- stouputils/stouputils/parallel.pyi +4 -4
- stouputils/version_pkg.pyi +15 -0
- {stouputils-1.14.0.dist-info → stouputils-1.14.2.dist-info}/METADATA +1 -1
- stouputils-1.14.2.dist-info/RECORD +171 -0
- stouputils-1.14.0.dist-info/RECORD +0 -140
- {stouputils-1.14.0.dist-info → stouputils-1.14.2.dist-info}/WHEEL +0 -0
- {stouputils-1.14.0.dist-info → stouputils-1.14.2.dist-info}/entry_points.txt +0 -0
|
@@ -1,378 +1,378 @@
|
|
|
1
|
-
|
|
2
|
-
# pyright: reportUnknownMemberType=false
|
|
3
|
-
# pyright: reportUnknownVariableType=false
|
|
4
|
-
# pyright: reportUnknownArgumentType=false
|
|
5
|
-
|
|
6
|
-
# Imports
|
|
7
|
-
from typing import Literal
|
|
8
|
-
|
|
9
|
-
from .common import Any, NDArray, check_image, cv2, np
|
|
10
|
-
from ....ctx import Muffle
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
# Functions
|
|
14
|
-
def nlm_denoise_image(
|
|
15
|
-
image: NDArray[Any],
|
|
16
|
-
h: float = 10,
|
|
17
|
-
template_window_size: int = 7,
|
|
18
|
-
search_window_size: int = 21,
|
|
19
|
-
ignore_dtype: bool = False
|
|
20
|
-
) -> NDArray[Any]:
|
|
21
|
-
""" Apply Non-Local Means denoising to an image.
|
|
22
|
-
|
|
23
|
-
This algorithm replaces each pixel with an average of similar pixels
|
|
24
|
-
found anywhere in the image. It is highly effective for removing Gaussian noise
|
|
25
|
-
while preserving edges and details.
|
|
26
|
-
|
|
27
|
-
Args:
|
|
28
|
-
image (NDArray[Any]): Image to denoise
|
|
29
|
-
h (float): Filter strength (higher values remove more noise but may blur details)
|
|
30
|
-
template_window_size (int): Size of the template window for patch comparison (should be odd)
|
|
31
|
-
search_window_size (int): Size of the search window (should be odd)
|
|
32
|
-
ignore_dtype (bool): Ignore the dtype check
|
|
33
|
-
Returns:
|
|
34
|
-
NDArray[Any]: Denoised image
|
|
35
|
-
|
|
36
|
-
>>> ## Basic tests
|
|
37
|
-
>>> image = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
|
|
38
|
-
>>> denoised = nlm_denoise_image(image.astype(np.uint8), 10, 3, 5)
|
|
39
|
-
>>> denoised.shape == image.shape
|
|
40
|
-
True
|
|
41
|
-
|
|
42
|
-
>>> ## Test with colored image
|
|
43
|
-
>>> rgb = np.random.randint(0, 256, (10, 10, 3), dtype=np.uint8)
|
|
44
|
-
>>> denoised_rgb = nlm_denoise_image(rgb, 10, 5, 11)
|
|
45
|
-
>>> denoised_rgb.shape == rgb.shape
|
|
46
|
-
True
|
|
47
|
-
|
|
48
|
-
>>> ## Test invalid inputs
|
|
49
|
-
>>> nlm_denoise_image("not an image", 10)
|
|
50
|
-
Traceback (most recent call last):
|
|
51
|
-
...
|
|
52
|
-
AssertionError: Image must be a numpy array
|
|
53
|
-
|
|
54
|
-
>>> nlm_denoise_image(image.astype(np.uint8), "10")
|
|
55
|
-
Traceback (most recent call last):
|
|
56
|
-
...
|
|
57
|
-
AssertionError: h must be a number, got <class 'str'>
|
|
58
|
-
|
|
59
|
-
>>> nlm_denoise_image(image.astype(np.uint8), 10, 4)
|
|
60
|
-
Traceback (most recent call last):
|
|
61
|
-
...
|
|
62
|
-
AssertionError: template_window_size must be odd, got 4
|
|
63
|
-
"""
|
|
64
|
-
# Check input data
|
|
65
|
-
check_image(image, ignore_dtype=ignore_dtype)
|
|
66
|
-
assert isinstance(h, float | int), f"h must be a number, got {type(h)}"
|
|
67
|
-
assert template_window_size % 2 == 1, f"template_window_size must be odd, got {template_window_size}"
|
|
68
|
-
assert search_window_size % 2 == 1, f"search_window_size must be odd, got {search_window_size}"
|
|
69
|
-
|
|
70
|
-
# Apply Non-Local Means denoising based on image type
|
|
71
|
-
if len(image.shape) == 2 or image.shape[2] == 1: # Grayscale
|
|
72
|
-
return cv2.fastNlMeansDenoising(
|
|
73
|
-
image, None, float(h), template_window_size, search_window_size
|
|
74
|
-
)
|
|
75
|
-
else: # Color
|
|
76
|
-
return cv2.fastNlMeansDenoisingColored(
|
|
77
|
-
image, None, float(h), float(h), template_window_size, search_window_size
|
|
78
|
-
)
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
def bilateral_denoise_image(
|
|
82
|
-
image: NDArray[Any],
|
|
83
|
-
d: int = 9,
|
|
84
|
-
sigma_color: float = 75,
|
|
85
|
-
sigma_space: float = 75,
|
|
86
|
-
ignore_dtype: bool = False
|
|
87
|
-
) -> NDArray[Any]:
|
|
88
|
-
""" Apply Bilateral Filter denoising to an image.
|
|
89
|
-
|
|
90
|
-
Bilateral filtering smooths images while preserving edges by considering
|
|
91
|
-
both spatial proximity and color similarity between pixels.
|
|
92
|
-
|
|
93
|
-
Args:
|
|
94
|
-
image (NDArray[Any]): Image to denoise
|
|
95
|
-
d (int): Diameter of each pixel neighborhood
|
|
96
|
-
sigma_color (float): Filter sigma in the color space
|
|
97
|
-
sigma_space (float): Filter sigma in the coordinate space
|
|
98
|
-
ignore_dtype (bool): Ignore the dtype check
|
|
99
|
-
Returns:
|
|
100
|
-
NDArray[Any]: Denoised image
|
|
101
|
-
|
|
102
|
-
>>> ## Basic tests
|
|
103
|
-
>>> image = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
|
|
104
|
-
>>> denoised = bilateral_denoise_image(image.astype(np.uint8))
|
|
105
|
-
>>> denoised.shape == image.shape
|
|
106
|
-
True
|
|
107
|
-
|
|
108
|
-
>>> ## Test with colored image
|
|
109
|
-
>>> rgb = np.random.randint(0, 256, (10, 10, 3), dtype=np.uint8)
|
|
110
|
-
>>> denoised_rgb = bilateral_denoise_image(rgb)
|
|
111
|
-
>>> denoised_rgb.shape == rgb.shape
|
|
112
|
-
True
|
|
113
|
-
|
|
114
|
-
>>> ## Test invalid inputs
|
|
115
|
-
>>> bilateral_denoise_image("not an image")
|
|
116
|
-
Traceback (most recent call last):
|
|
117
|
-
...
|
|
118
|
-
AssertionError: Image must be a numpy array
|
|
119
|
-
|
|
120
|
-
>>> bilateral_denoise_image(image.astype(np.uint8), "9")
|
|
121
|
-
Traceback (most recent call last):
|
|
122
|
-
...
|
|
123
|
-
AssertionError: d must be a number, got <class 'str'>
|
|
124
|
-
"""
|
|
125
|
-
# Check input data
|
|
126
|
-
check_image(image, ignore_dtype=ignore_dtype)
|
|
127
|
-
assert isinstance(d, int), f"d must be a number, got {type(d)}"
|
|
128
|
-
assert isinstance(sigma_color, float | int), f"sigma_color must be a number, got {type(sigma_color)}"
|
|
129
|
-
assert isinstance(sigma_space, float | int), f"sigma_space must be a number, got {type(sigma_space)}"
|
|
130
|
-
|
|
131
|
-
# Apply bilateral filter
|
|
132
|
-
return cv2.bilateralFilter(image, d, sigma_color, sigma_space)
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
def tv_denoise_image(
|
|
136
|
-
image: NDArray[Any],
|
|
137
|
-
weight: float = 0.1,
|
|
138
|
-
iterations: int = 30,
|
|
139
|
-
method: Literal["chambolle", "bregman"] = "chambolle",
|
|
140
|
-
ignore_dtype: bool = False
|
|
141
|
-
) -> NDArray[Any]:
|
|
142
|
-
""" Apply Total Variation denoising to an image.
|
|
143
|
-
|
|
144
|
-
Total Variation denoising removes noise while preserving sharp edges by
|
|
145
|
-
minimizing the total variation of the image.
|
|
146
|
-
|
|
147
|
-
Args:
|
|
148
|
-
image (NDArray[Any]): Image to denoise
|
|
149
|
-
weight (float): Denoising weight (higher values remove more noise)
|
|
150
|
-
iterations (int): Number of iterations
|
|
151
|
-
method (str): Method to use ("chambolle" or "bregman")
|
|
152
|
-
ignore_dtype (bool): Ignore the dtype check
|
|
153
|
-
Returns:
|
|
154
|
-
NDArray[Any]: Denoised image
|
|
155
|
-
|
|
156
|
-
>>> ## Basic tests
|
|
157
|
-
>>> image = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
|
|
158
|
-
>>> denoised = tv_denoise_image(image.astype(np.uint8), 0.1, 30)
|
|
159
|
-
>>> denoised.shape == image.shape
|
|
160
|
-
True
|
|
161
|
-
|
|
162
|
-
>>> ## Test invalid inputs
|
|
163
|
-
>>> tv_denoise_image("not an image")
|
|
164
|
-
Traceback (most recent call last):
|
|
165
|
-
...
|
|
166
|
-
AssertionError: Image must be a numpy array
|
|
167
|
-
|
|
168
|
-
>>> tv_denoise_image(image.astype(np.uint8), "0.1")
|
|
169
|
-
Traceback (most recent call last):
|
|
170
|
-
...
|
|
171
|
-
AssertionError: weight must be a number, got <class 'str'>
|
|
172
|
-
"""
|
|
173
|
-
# Check input data
|
|
174
|
-
check_image(image, ignore_dtype=ignore_dtype)
|
|
175
|
-
assert isinstance(weight, float | int), f"weight must be a number, got {type(weight)}"
|
|
176
|
-
assert isinstance(iterations, int), f"iterations must be an integer, got {type(iterations)}"
|
|
177
|
-
assert method in ["chambolle", "bregman"], f"method must be 'chambolle' or 'bregman', got {method}"
|
|
178
|
-
|
|
179
|
-
# Import skimage for TV denoising
|
|
180
|
-
try:
|
|
181
|
-
from skimage.restoration import denoise_tv_bregman, denoise_tv_chambolle
|
|
182
|
-
except ImportError as e:
|
|
183
|
-
raise ImportError("scikit-image is required for TV denoising. Install with 'pip install scikit-image'") from e
|
|
184
|
-
|
|
185
|
-
# Normalize image to [0, 1] for skimage functions
|
|
186
|
-
is_int_type = np.issubdtype(image.dtype, np.integer)
|
|
187
|
-
if is_int_type:
|
|
188
|
-
img_norm = image.astype(np.float32) / 255.0
|
|
189
|
-
else:
|
|
190
|
-
img_norm = image.astype(np.float32)
|
|
191
|
-
|
|
192
|
-
# Apply TV denoising based on method
|
|
193
|
-
if method == "chambolle":
|
|
194
|
-
denoised = denoise_tv_chambolle(
|
|
195
|
-
img_norm,
|
|
196
|
-
weight=weight,
|
|
197
|
-
max_num_iter=iterations,
|
|
198
|
-
channel_axis=-1 if len(image.shape) > 2 else None
|
|
199
|
-
)
|
|
200
|
-
else:
|
|
201
|
-
denoised = denoise_tv_bregman(
|
|
202
|
-
img_norm,
|
|
203
|
-
weight=weight,
|
|
204
|
-
max_num_iter=iterations,
|
|
205
|
-
channel_axis=-1 if len(image.shape) > 2 else None
|
|
206
|
-
)
|
|
207
|
-
|
|
208
|
-
# Convert back to original data type
|
|
209
|
-
if is_int_type:
|
|
210
|
-
denoised = np.clip(denoised * 255, 0, 255).astype(image.dtype)
|
|
211
|
-
else:
|
|
212
|
-
denoised = denoised.astype(image.dtype)
|
|
213
|
-
|
|
214
|
-
return denoised
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
def wavelet_denoise_image(
|
|
218
|
-
image: NDArray[Any],
|
|
219
|
-
sigma: float | None = None,
|
|
220
|
-
wavelet: str = 'db1',
|
|
221
|
-
mode: str = 'soft',
|
|
222
|
-
wavelet_levels: int = 3,
|
|
223
|
-
ignore_dtype: bool = False
|
|
224
|
-
) -> NDArray[Any]:
|
|
225
|
-
""" Apply Wavelet denoising to an image.
|
|
226
|
-
|
|
227
|
-
Wavelet denoising decomposes the image into wavelet coefficients,
|
|
228
|
-
applies thresholding, and reconstructs the image with reduced noise.
|
|
229
|
-
|
|
230
|
-
Args:
|
|
231
|
-
image (NDArray[Any]): Image to denoise
|
|
232
|
-
sigma (float): Noise standard deviation. If None, it's estimated from the image.
|
|
233
|
-
wavelet (str): Wavelet to use
|
|
234
|
-
mode (str): Thresholding mode ('soft' or 'hard')
|
|
235
|
-
wavelet_levels (int): Number of wavelet decomposition levels
|
|
236
|
-
ignore_dtype (bool): Ignore the dtype check
|
|
237
|
-
Returns:
|
|
238
|
-
NDArray[Any]: Denoised image
|
|
239
|
-
|
|
240
|
-
>>> ## Basic tests
|
|
241
|
-
>>> import importlib.util
|
|
242
|
-
>>> has_pywt = importlib.util.find_spec('pywt') is not None
|
|
243
|
-
>>> image = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
|
|
244
|
-
>>> if has_pywt:
|
|
245
|
-
... denoised = wavelet_denoise_image(image.astype(np.uint8))
|
|
246
|
-
... denoised.shape == image.shape
|
|
247
|
-
... else:
|
|
248
|
-
... True
|
|
249
|
-
True
|
|
250
|
-
|
|
251
|
-
>>> ## Test invalid inputs
|
|
252
|
-
>>> wavelet_denoise_image("not an image")
|
|
253
|
-
Traceback (most recent call last):
|
|
254
|
-
...
|
|
255
|
-
AssertionError: Image must be a numpy array
|
|
256
|
-
|
|
257
|
-
>>> wavelet_denoise_image(image.astype(np.uint8), wavelet=123)
|
|
258
|
-
Traceback (most recent call last):
|
|
259
|
-
...
|
|
260
|
-
AssertionError: wavelet must be a string, got <class 'int'>
|
|
261
|
-
"""
|
|
262
|
-
# Check input data
|
|
263
|
-
check_image(image, ignore_dtype=ignore_dtype)
|
|
264
|
-
if sigma is not None:
|
|
265
|
-
assert isinstance(sigma, float | int), f"sigma must be a number or None, got {type(sigma)}"
|
|
266
|
-
assert isinstance(wavelet, str), f"wavelet must be a string, got {type(wavelet)}"
|
|
267
|
-
assert mode in ["soft", "hard"], f"mode must be 'soft' or 'hard', got {mode}"
|
|
268
|
-
assert isinstance(wavelet_levels, int), f"wavelet_levels must be an integer, got {type(wavelet_levels)}"
|
|
269
|
-
|
|
270
|
-
# Import skimage for wavelet denoising
|
|
271
|
-
try:
|
|
272
|
-
from skimage.restoration import denoise_wavelet
|
|
273
|
-
|
|
274
|
-
# Check for PyWavelets dependency specifically
|
|
275
|
-
try:
|
|
276
|
-
import pywt # type: ignore
|
|
277
|
-
except ImportError as e:
|
|
278
|
-
raise ImportError(
|
|
279
|
-
"PyWavelets (pywt) is required for wavelet denoising. Install with 'pip install PyWavelets'", name="pywt"
|
|
280
|
-
) from e
|
|
281
|
-
except ImportError as e:
|
|
282
|
-
if e.name != "pywt":
|
|
283
|
-
raise ImportError("skimage is required for wavelet denoising. Install with 'pip install scikit-image'") from e
|
|
284
|
-
else:
|
|
285
|
-
raise e
|
|
286
|
-
|
|
287
|
-
# Normalize image to [0, 1] for skimage functions
|
|
288
|
-
is_int_type = np.issubdtype(image.dtype, np.integer)
|
|
289
|
-
if is_int_type:
|
|
290
|
-
img_norm = image.astype(np.float32) / 255.0
|
|
291
|
-
else:
|
|
292
|
-
img_norm = image.astype(np.float32)
|
|
293
|
-
|
|
294
|
-
# Apply wavelet denoising
|
|
295
|
-
with Muffle(mute_stderr=True):
|
|
296
|
-
denoised = denoise_wavelet(
|
|
297
|
-
img_norm, sigma=sigma, wavelet=wavelet, mode=mode,
|
|
298
|
-
wavelet_levels=wavelet_levels, channel_axis=-1 if len(image.shape) > 2 else None
|
|
299
|
-
)
|
|
300
|
-
|
|
301
|
-
# Convert back to original data type
|
|
302
|
-
if is_int_type:
|
|
303
|
-
denoised = np.clip(denoised * 255, 0, 255).astype(image.dtype)
|
|
304
|
-
else:
|
|
305
|
-
denoised = denoised.astype(image.dtype)
|
|
306
|
-
|
|
307
|
-
return denoised
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
def adaptive_denoise_image(
|
|
311
|
-
image: NDArray[Any],
|
|
312
|
-
method: Literal["nlm", "bilateral", "tv", "wavelet"] | str = "nlm",
|
|
313
|
-
strength: float = 0.5,
|
|
314
|
-
ignore_dtype: bool = False
|
|
315
|
-
) -> NDArray[Any]:
|
|
316
|
-
""" Apply adaptive denoising to an image using the specified method.
|
|
317
|
-
|
|
318
|
-
This is a convenience function that selects the appropriate denoising method
|
|
319
|
-
and parameters based on the image content and noise level.
|
|
320
|
-
|
|
321
|
-
Args:
|
|
322
|
-
image (NDArray[Any]): Image to denoise
|
|
323
|
-
method (str): Denoising method to use ("nlm", "bilateral", "tv", or "wavelet")
|
|
324
|
-
strength (float): Denoising strength (0.0 to 1.0)
|
|
325
|
-
ignore_dtype (bool): Ignore the dtype check
|
|
326
|
-
Returns:
|
|
327
|
-
NDArray[Any]: Denoised image
|
|
328
|
-
|
|
329
|
-
>>> ## Basic tests
|
|
330
|
-
>>> image = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
|
|
331
|
-
>>> denoised = adaptive_denoise_image(image.astype(np.uint8), "nlm", 0.5)
|
|
332
|
-
>>> denoised.shape == image.shape
|
|
333
|
-
True
|
|
334
|
-
|
|
335
|
-
>>> ## Test invalid inputs
|
|
336
|
-
>>> adaptive_denoise_image("not an image")
|
|
337
|
-
Traceback (most recent call last):
|
|
338
|
-
...
|
|
339
|
-
AssertionError: Image must be a numpy array
|
|
340
|
-
|
|
341
|
-
>>> adaptive_denoise_image(image.astype(np.uint8), "invalid_method")
|
|
342
|
-
Traceback (most recent call last):
|
|
343
|
-
...
|
|
344
|
-
AssertionError: method must be one of: nlm, bilateral, tv, wavelet
|
|
345
|
-
"""
|
|
346
|
-
# Check input data
|
|
347
|
-
check_image(image, ignore_dtype=ignore_dtype)
|
|
348
|
-
valid_methods = ["nlm", "bilateral", "tv", "wavelet"]
|
|
349
|
-
assert method in valid_methods, f"method must be one of: {', '.join(valid_methods)}"
|
|
350
|
-
assert isinstance(strength, float | int), f"strength must be a number, got {type(strength)}"
|
|
351
|
-
assert 0 <= strength <= 1, f"strength must be between 0 and 1, got {strength}"
|
|
352
|
-
|
|
353
|
-
# Scale parameters based on strength
|
|
354
|
-
if method == "bilateral":
|
|
355
|
-
# sigma parameters scale from 30 (minimal) to 150 (strong)
|
|
356
|
-
sigma = 30 + strength * 120
|
|
357
|
-
return bilateral_denoise_image(
|
|
358
|
-
image, d=9, sigma_color=sigma, sigma_space=sigma, ignore_dtype=ignore_dtype
|
|
359
|
-
)
|
|
360
|
-
|
|
361
|
-
elif method == "tv":
|
|
362
|
-
# weight scales from 0.05 (minimal) to 0.5 (strong)
|
|
363
|
-
weight = 0.05 + strength * 0.45
|
|
364
|
-
return tv_denoise_image(
|
|
365
|
-
image, weight=weight, iterations=30, ignore_dtype=ignore_dtype
|
|
366
|
-
)
|
|
367
|
-
|
|
368
|
-
elif method == "wavelet":
|
|
369
|
-
# We'll estimate sigma from the image, but scale wavelet levels
|
|
370
|
-
wavelet_levels = max(2, min(5, int(2 + strength * 3)))
|
|
371
|
-
return wavelet_denoise_image(
|
|
372
|
-
image, wavelet_levels=wavelet_levels, ignore_dtype=ignore_dtype
|
|
373
|
-
)
|
|
374
|
-
|
|
375
|
-
else:
|
|
376
|
-
# h parameter scales from 5 (minimal) to 20 (strong)
|
|
377
|
-
h = 5 + strength * 15
|
|
378
|
-
return nlm_denoise_image(image, h=h, ignore_dtype=ignore_dtype)
|
|
1
|
+
|
|
2
|
+
# pyright: reportUnknownMemberType=false
|
|
3
|
+
# pyright: reportUnknownVariableType=false
|
|
4
|
+
# pyright: reportUnknownArgumentType=false
|
|
5
|
+
|
|
6
|
+
# Imports
|
|
7
|
+
from typing import Literal
|
|
8
|
+
|
|
9
|
+
from .common import Any, NDArray, check_image, cv2, np
|
|
10
|
+
from ....ctx import Muffle
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# Functions
|
|
14
|
+
def nlm_denoise_image(
|
|
15
|
+
image: NDArray[Any],
|
|
16
|
+
h: float = 10,
|
|
17
|
+
template_window_size: int = 7,
|
|
18
|
+
search_window_size: int = 21,
|
|
19
|
+
ignore_dtype: bool = False
|
|
20
|
+
) -> NDArray[Any]:
|
|
21
|
+
""" Apply Non-Local Means denoising to an image.
|
|
22
|
+
|
|
23
|
+
This algorithm replaces each pixel with an average of similar pixels
|
|
24
|
+
found anywhere in the image. It is highly effective for removing Gaussian noise
|
|
25
|
+
while preserving edges and details.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
image (NDArray[Any]): Image to denoise
|
|
29
|
+
h (float): Filter strength (higher values remove more noise but may blur details)
|
|
30
|
+
template_window_size (int): Size of the template window for patch comparison (should be odd)
|
|
31
|
+
search_window_size (int): Size of the search window (should be odd)
|
|
32
|
+
ignore_dtype (bool): Ignore the dtype check
|
|
33
|
+
Returns:
|
|
34
|
+
NDArray[Any]: Denoised image
|
|
35
|
+
|
|
36
|
+
>>> ## Basic tests
|
|
37
|
+
>>> image = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
|
|
38
|
+
>>> denoised = nlm_denoise_image(image.astype(np.uint8), 10, 3, 5)
|
|
39
|
+
>>> denoised.shape == image.shape
|
|
40
|
+
True
|
|
41
|
+
|
|
42
|
+
>>> ## Test with colored image
|
|
43
|
+
>>> rgb = np.random.randint(0, 256, (10, 10, 3), dtype=np.uint8)
|
|
44
|
+
>>> denoised_rgb = nlm_denoise_image(rgb, 10, 5, 11)
|
|
45
|
+
>>> denoised_rgb.shape == rgb.shape
|
|
46
|
+
True
|
|
47
|
+
|
|
48
|
+
>>> ## Test invalid inputs
|
|
49
|
+
>>> nlm_denoise_image("not an image", 10)
|
|
50
|
+
Traceback (most recent call last):
|
|
51
|
+
...
|
|
52
|
+
AssertionError: Image must be a numpy array
|
|
53
|
+
|
|
54
|
+
>>> nlm_denoise_image(image.astype(np.uint8), "10")
|
|
55
|
+
Traceback (most recent call last):
|
|
56
|
+
...
|
|
57
|
+
AssertionError: h must be a number, got <class 'str'>
|
|
58
|
+
|
|
59
|
+
>>> nlm_denoise_image(image.astype(np.uint8), 10, 4)
|
|
60
|
+
Traceback (most recent call last):
|
|
61
|
+
...
|
|
62
|
+
AssertionError: template_window_size must be odd, got 4
|
|
63
|
+
"""
|
|
64
|
+
# Check input data
|
|
65
|
+
check_image(image, ignore_dtype=ignore_dtype)
|
|
66
|
+
assert isinstance(h, float | int), f"h must be a number, got {type(h)}"
|
|
67
|
+
assert template_window_size % 2 == 1, f"template_window_size must be odd, got {template_window_size}"
|
|
68
|
+
assert search_window_size % 2 == 1, f"search_window_size must be odd, got {search_window_size}"
|
|
69
|
+
|
|
70
|
+
# Apply Non-Local Means denoising based on image type
|
|
71
|
+
if len(image.shape) == 2 or image.shape[2] == 1: # Grayscale
|
|
72
|
+
return cv2.fastNlMeansDenoising(
|
|
73
|
+
image, None, float(h), template_window_size, search_window_size
|
|
74
|
+
)
|
|
75
|
+
else: # Color
|
|
76
|
+
return cv2.fastNlMeansDenoisingColored(
|
|
77
|
+
image, None, float(h), float(h), template_window_size, search_window_size
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def bilateral_denoise_image(
|
|
82
|
+
image: NDArray[Any],
|
|
83
|
+
d: int = 9,
|
|
84
|
+
sigma_color: float = 75,
|
|
85
|
+
sigma_space: float = 75,
|
|
86
|
+
ignore_dtype: bool = False
|
|
87
|
+
) -> NDArray[Any]:
|
|
88
|
+
""" Apply Bilateral Filter denoising to an image.
|
|
89
|
+
|
|
90
|
+
Bilateral filtering smooths images while preserving edges by considering
|
|
91
|
+
both spatial proximity and color similarity between pixels.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
image (NDArray[Any]): Image to denoise
|
|
95
|
+
d (int): Diameter of each pixel neighborhood
|
|
96
|
+
sigma_color (float): Filter sigma in the color space
|
|
97
|
+
sigma_space (float): Filter sigma in the coordinate space
|
|
98
|
+
ignore_dtype (bool): Ignore the dtype check
|
|
99
|
+
Returns:
|
|
100
|
+
NDArray[Any]: Denoised image
|
|
101
|
+
|
|
102
|
+
>>> ## Basic tests
|
|
103
|
+
>>> image = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
|
|
104
|
+
>>> denoised = bilateral_denoise_image(image.astype(np.uint8))
|
|
105
|
+
>>> denoised.shape == image.shape
|
|
106
|
+
True
|
|
107
|
+
|
|
108
|
+
>>> ## Test with colored image
|
|
109
|
+
>>> rgb = np.random.randint(0, 256, (10, 10, 3), dtype=np.uint8)
|
|
110
|
+
>>> denoised_rgb = bilateral_denoise_image(rgb)
|
|
111
|
+
>>> denoised_rgb.shape == rgb.shape
|
|
112
|
+
True
|
|
113
|
+
|
|
114
|
+
>>> ## Test invalid inputs
|
|
115
|
+
>>> bilateral_denoise_image("not an image")
|
|
116
|
+
Traceback (most recent call last):
|
|
117
|
+
...
|
|
118
|
+
AssertionError: Image must be a numpy array
|
|
119
|
+
|
|
120
|
+
>>> bilateral_denoise_image(image.astype(np.uint8), "9")
|
|
121
|
+
Traceback (most recent call last):
|
|
122
|
+
...
|
|
123
|
+
AssertionError: d must be a number, got <class 'str'>
|
|
124
|
+
"""
|
|
125
|
+
# Check input data
|
|
126
|
+
check_image(image, ignore_dtype=ignore_dtype)
|
|
127
|
+
assert isinstance(d, int), f"d must be a number, got {type(d)}"
|
|
128
|
+
assert isinstance(sigma_color, float | int), f"sigma_color must be a number, got {type(sigma_color)}"
|
|
129
|
+
assert isinstance(sigma_space, float | int), f"sigma_space must be a number, got {type(sigma_space)}"
|
|
130
|
+
|
|
131
|
+
# Apply bilateral filter
|
|
132
|
+
return cv2.bilateralFilter(image, d, sigma_color, sigma_space)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def tv_denoise_image(
|
|
136
|
+
image: NDArray[Any],
|
|
137
|
+
weight: float = 0.1,
|
|
138
|
+
iterations: int = 30,
|
|
139
|
+
method: Literal["chambolle", "bregman"] = "chambolle",
|
|
140
|
+
ignore_dtype: bool = False
|
|
141
|
+
) -> NDArray[Any]:
|
|
142
|
+
""" Apply Total Variation denoising to an image.
|
|
143
|
+
|
|
144
|
+
Total Variation denoising removes noise while preserving sharp edges by
|
|
145
|
+
minimizing the total variation of the image.
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
image (NDArray[Any]): Image to denoise
|
|
149
|
+
weight (float): Denoising weight (higher values remove more noise)
|
|
150
|
+
iterations (int): Number of iterations
|
|
151
|
+
method (str): Method to use ("chambolle" or "bregman")
|
|
152
|
+
ignore_dtype (bool): Ignore the dtype check
|
|
153
|
+
Returns:
|
|
154
|
+
NDArray[Any]: Denoised image
|
|
155
|
+
|
|
156
|
+
>>> ## Basic tests
|
|
157
|
+
>>> image = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
|
|
158
|
+
>>> denoised = tv_denoise_image(image.astype(np.uint8), 0.1, 30)
|
|
159
|
+
>>> denoised.shape == image.shape
|
|
160
|
+
True
|
|
161
|
+
|
|
162
|
+
>>> ## Test invalid inputs
|
|
163
|
+
>>> tv_denoise_image("not an image")
|
|
164
|
+
Traceback (most recent call last):
|
|
165
|
+
...
|
|
166
|
+
AssertionError: Image must be a numpy array
|
|
167
|
+
|
|
168
|
+
>>> tv_denoise_image(image.astype(np.uint8), "0.1")
|
|
169
|
+
Traceback (most recent call last):
|
|
170
|
+
...
|
|
171
|
+
AssertionError: weight must be a number, got <class 'str'>
|
|
172
|
+
"""
|
|
173
|
+
# Check input data
|
|
174
|
+
check_image(image, ignore_dtype=ignore_dtype)
|
|
175
|
+
assert isinstance(weight, float | int), f"weight must be a number, got {type(weight)}"
|
|
176
|
+
assert isinstance(iterations, int), f"iterations must be an integer, got {type(iterations)}"
|
|
177
|
+
assert method in ["chambolle", "bregman"], f"method must be 'chambolle' or 'bregman', got {method}"
|
|
178
|
+
|
|
179
|
+
# Import skimage for TV denoising
|
|
180
|
+
try:
|
|
181
|
+
from skimage.restoration import denoise_tv_bregman, denoise_tv_chambolle
|
|
182
|
+
except ImportError as e:
|
|
183
|
+
raise ImportError("scikit-image is required for TV denoising. Install with 'pip install scikit-image'") from e
|
|
184
|
+
|
|
185
|
+
# Normalize image to [0, 1] for skimage functions
|
|
186
|
+
is_int_type = np.issubdtype(image.dtype, np.integer)
|
|
187
|
+
if is_int_type:
|
|
188
|
+
img_norm = image.astype(np.float32) / 255.0
|
|
189
|
+
else:
|
|
190
|
+
img_norm = image.astype(np.float32)
|
|
191
|
+
|
|
192
|
+
# Apply TV denoising based on method
|
|
193
|
+
if method == "chambolle":
|
|
194
|
+
denoised = denoise_tv_chambolle(
|
|
195
|
+
img_norm,
|
|
196
|
+
weight=weight,
|
|
197
|
+
max_num_iter=iterations,
|
|
198
|
+
channel_axis=-1 if len(image.shape) > 2 else None
|
|
199
|
+
)
|
|
200
|
+
else:
|
|
201
|
+
denoised = denoise_tv_bregman(
|
|
202
|
+
img_norm,
|
|
203
|
+
weight=weight,
|
|
204
|
+
max_num_iter=iterations,
|
|
205
|
+
channel_axis=-1 if len(image.shape) > 2 else None
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
# Convert back to original data type
|
|
209
|
+
if is_int_type:
|
|
210
|
+
denoised = np.clip(denoised * 255, 0, 255).astype(image.dtype)
|
|
211
|
+
else:
|
|
212
|
+
denoised = denoised.astype(image.dtype)
|
|
213
|
+
|
|
214
|
+
return denoised
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def wavelet_denoise_image(
|
|
218
|
+
image: NDArray[Any],
|
|
219
|
+
sigma: float | None = None,
|
|
220
|
+
wavelet: str = 'db1',
|
|
221
|
+
mode: str = 'soft',
|
|
222
|
+
wavelet_levels: int = 3,
|
|
223
|
+
ignore_dtype: bool = False
|
|
224
|
+
) -> NDArray[Any]:
|
|
225
|
+
""" Apply Wavelet denoising to an image.
|
|
226
|
+
|
|
227
|
+
Wavelet denoising decomposes the image into wavelet coefficients,
|
|
228
|
+
applies thresholding, and reconstructs the image with reduced noise.
|
|
229
|
+
|
|
230
|
+
Args:
|
|
231
|
+
image (NDArray[Any]): Image to denoise
|
|
232
|
+
sigma (float): Noise standard deviation. If None, it's estimated from the image.
|
|
233
|
+
wavelet (str): Wavelet to use
|
|
234
|
+
mode (str): Thresholding mode ('soft' or 'hard')
|
|
235
|
+
wavelet_levels (int): Number of wavelet decomposition levels
|
|
236
|
+
ignore_dtype (bool): Ignore the dtype check
|
|
237
|
+
Returns:
|
|
238
|
+
NDArray[Any]: Denoised image
|
|
239
|
+
|
|
240
|
+
>>> ## Basic tests
|
|
241
|
+
>>> import importlib.util
|
|
242
|
+
>>> has_pywt = importlib.util.find_spec('pywt') is not None
|
|
243
|
+
>>> image = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
|
|
244
|
+
>>> if has_pywt:
|
|
245
|
+
... denoised = wavelet_denoise_image(image.astype(np.uint8))
|
|
246
|
+
... denoised.shape == image.shape
|
|
247
|
+
... else:
|
|
248
|
+
... True
|
|
249
|
+
True
|
|
250
|
+
|
|
251
|
+
>>> ## Test invalid inputs
|
|
252
|
+
>>> wavelet_denoise_image("not an image")
|
|
253
|
+
Traceback (most recent call last):
|
|
254
|
+
...
|
|
255
|
+
AssertionError: Image must be a numpy array
|
|
256
|
+
|
|
257
|
+
>>> wavelet_denoise_image(image.astype(np.uint8), wavelet=123)
|
|
258
|
+
Traceback (most recent call last):
|
|
259
|
+
...
|
|
260
|
+
AssertionError: wavelet must be a string, got <class 'int'>
|
|
261
|
+
"""
|
|
262
|
+
# Check input data
|
|
263
|
+
check_image(image, ignore_dtype=ignore_dtype)
|
|
264
|
+
if sigma is not None:
|
|
265
|
+
assert isinstance(sigma, float | int), f"sigma must be a number or None, got {type(sigma)}"
|
|
266
|
+
assert isinstance(wavelet, str), f"wavelet must be a string, got {type(wavelet)}"
|
|
267
|
+
assert mode in ["soft", "hard"], f"mode must be 'soft' or 'hard', got {mode}"
|
|
268
|
+
assert isinstance(wavelet_levels, int), f"wavelet_levels must be an integer, got {type(wavelet_levels)}"
|
|
269
|
+
|
|
270
|
+
# Import skimage for wavelet denoising
|
|
271
|
+
try:
|
|
272
|
+
from skimage.restoration import denoise_wavelet
|
|
273
|
+
|
|
274
|
+
# Check for PyWavelets dependency specifically
|
|
275
|
+
try:
|
|
276
|
+
import pywt # type: ignore
|
|
277
|
+
except ImportError as e:
|
|
278
|
+
raise ImportError(
|
|
279
|
+
"PyWavelets (pywt) is required for wavelet denoising. Install with 'pip install PyWavelets'", name="pywt"
|
|
280
|
+
) from e
|
|
281
|
+
except ImportError as e:
|
|
282
|
+
if e.name != "pywt":
|
|
283
|
+
raise ImportError("skimage is required for wavelet denoising. Install with 'pip install scikit-image'") from e
|
|
284
|
+
else:
|
|
285
|
+
raise e
|
|
286
|
+
|
|
287
|
+
# Normalize image to [0, 1] for skimage functions
|
|
288
|
+
is_int_type = np.issubdtype(image.dtype, np.integer)
|
|
289
|
+
if is_int_type:
|
|
290
|
+
img_norm = image.astype(np.float32) / 255.0
|
|
291
|
+
else:
|
|
292
|
+
img_norm = image.astype(np.float32)
|
|
293
|
+
|
|
294
|
+
# Apply wavelet denoising
|
|
295
|
+
with Muffle(mute_stderr=True):
|
|
296
|
+
denoised = denoise_wavelet(
|
|
297
|
+
img_norm, sigma=sigma, wavelet=wavelet, mode=mode,
|
|
298
|
+
wavelet_levels=wavelet_levels, channel_axis=-1 if len(image.shape) > 2 else None
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
# Convert back to original data type
|
|
302
|
+
if is_int_type:
|
|
303
|
+
denoised = np.clip(denoised * 255, 0, 255).astype(image.dtype)
|
|
304
|
+
else:
|
|
305
|
+
denoised = denoised.astype(image.dtype)
|
|
306
|
+
|
|
307
|
+
return denoised
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def adaptive_denoise_image(
|
|
311
|
+
image: NDArray[Any],
|
|
312
|
+
method: Literal["nlm", "bilateral", "tv", "wavelet"] | str = "nlm",
|
|
313
|
+
strength: float = 0.5,
|
|
314
|
+
ignore_dtype: bool = False
|
|
315
|
+
) -> NDArray[Any]:
|
|
316
|
+
""" Apply adaptive denoising to an image using the specified method.
|
|
317
|
+
|
|
318
|
+
This is a convenience function that selects the appropriate denoising method
|
|
319
|
+
and parameters based on the image content and noise level.
|
|
320
|
+
|
|
321
|
+
Args:
|
|
322
|
+
image (NDArray[Any]): Image to denoise
|
|
323
|
+
method (str): Denoising method to use ("nlm", "bilateral", "tv", or "wavelet")
|
|
324
|
+
strength (float): Denoising strength (0.0 to 1.0)
|
|
325
|
+
ignore_dtype (bool): Ignore the dtype check
|
|
326
|
+
Returns:
|
|
327
|
+
NDArray[Any]: Denoised image
|
|
328
|
+
|
|
329
|
+
>>> ## Basic tests
|
|
330
|
+
>>> image = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
|
|
331
|
+
>>> denoised = adaptive_denoise_image(image.astype(np.uint8), "nlm", 0.5)
|
|
332
|
+
>>> denoised.shape == image.shape
|
|
333
|
+
True
|
|
334
|
+
|
|
335
|
+
>>> ## Test invalid inputs
|
|
336
|
+
>>> adaptive_denoise_image("not an image")
|
|
337
|
+
Traceback (most recent call last):
|
|
338
|
+
...
|
|
339
|
+
AssertionError: Image must be a numpy array
|
|
340
|
+
|
|
341
|
+
>>> adaptive_denoise_image(image.astype(np.uint8), "invalid_method")
|
|
342
|
+
Traceback (most recent call last):
|
|
343
|
+
...
|
|
344
|
+
AssertionError: method must be one of: nlm, bilateral, tv, wavelet
|
|
345
|
+
"""
|
|
346
|
+
# Check input data
|
|
347
|
+
check_image(image, ignore_dtype=ignore_dtype)
|
|
348
|
+
valid_methods = ["nlm", "bilateral", "tv", "wavelet"]
|
|
349
|
+
assert method in valid_methods, f"method must be one of: {', '.join(valid_methods)}"
|
|
350
|
+
assert isinstance(strength, float | int), f"strength must be a number, got {type(strength)}"
|
|
351
|
+
assert 0 <= strength <= 1, f"strength must be between 0 and 1, got {strength}"
|
|
352
|
+
|
|
353
|
+
# Scale parameters based on strength
|
|
354
|
+
if method == "bilateral":
|
|
355
|
+
# sigma parameters scale from 30 (minimal) to 150 (strong)
|
|
356
|
+
sigma = 30 + strength * 120
|
|
357
|
+
return bilateral_denoise_image(
|
|
358
|
+
image, d=9, sigma_color=sigma, sigma_space=sigma, ignore_dtype=ignore_dtype
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
elif method == "tv":
|
|
362
|
+
# weight scales from 0.05 (minimal) to 0.5 (strong)
|
|
363
|
+
weight = 0.05 + strength * 0.45
|
|
364
|
+
return tv_denoise_image(
|
|
365
|
+
image, weight=weight, iterations=30, ignore_dtype=ignore_dtype
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
elif method == "wavelet":
|
|
369
|
+
# We'll estimate sigma from the image, but scale wavelet levels
|
|
370
|
+
wavelet_levels = max(2, min(5, int(2 + strength * 3)))
|
|
371
|
+
return wavelet_denoise_image(
|
|
372
|
+
image, wavelet_levels=wavelet_levels, ignore_dtype=ignore_dtype
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
else:
|
|
376
|
+
# h parameter scales from 5 (minimal) to 20 (strong)
|
|
377
|
+
h = 5 + strength * 15
|
|
378
|
+
return nlm_denoise_image(image, h=h, ignore_dtype=ignore_dtype)
|