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,73 +1,73 @@
|
|
|
1
|
-
|
|
2
|
-
# pyright: reportUnusedImport=false
|
|
3
|
-
# ruff: noqa: F401
|
|
4
|
-
|
|
5
|
-
# Imports
|
|
6
|
-
from .common import Any, NDArray, check_image, cv2, np
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
# Functions
|
|
10
|
-
def binary_threshold_image(image: NDArray[Any], threshold: float, ignore_dtype: bool = False) -> NDArray[Any]:
|
|
11
|
-
""" Apply binary threshold to an image.
|
|
12
|
-
|
|
13
|
-
Args:
|
|
14
|
-
image (NDArray[Any]): Image to threshold
|
|
15
|
-
threshold (float): Threshold value (between 0 and 1)
|
|
16
|
-
ignore_dtype (bool): Ignore the dtype check
|
|
17
|
-
Returns:
|
|
18
|
-
NDArray[Any]: Thresholded binary image
|
|
19
|
-
|
|
20
|
-
>>> ## Basic tests
|
|
21
|
-
>>> image = np.array([[100, 150, 200], [50, 125, 175], [25, 75, 225]])
|
|
22
|
-
>>> binary_threshold_image(image.astype(np.uint8), 0.5).tolist()
|
|
23
|
-
[[0, 255, 255], [0, 0, 255], [0, 0, 255]]
|
|
24
|
-
|
|
25
|
-
>>> np.random.seed(42)
|
|
26
|
-
>>> img = np.random.randint(0, 256, (4,4), dtype=np.uint8)
|
|
27
|
-
>>> thresholded = binary_threshold_image(img, 0.7)
|
|
28
|
-
>>> set(np.unique(thresholded).tolist()) <= {0, 255} # Should only contain 0 and 255
|
|
29
|
-
True
|
|
30
|
-
|
|
31
|
-
>>> rgb = np.random.randint(0, 256, (3,3,3), dtype=np.uint8)
|
|
32
|
-
>>> thresh_rgb = binary_threshold_image(rgb, 0.5)
|
|
33
|
-
>>> thresh_rgb.shape == rgb.shape
|
|
34
|
-
True
|
|
35
|
-
>>> set(np.unique(thresh_rgb).tolist()) <= {0, 255}
|
|
36
|
-
True
|
|
37
|
-
|
|
38
|
-
>>> ## Test invalid inputs
|
|
39
|
-
>>> binary_threshold_image("not an image", 0.5)
|
|
40
|
-
Traceback (most recent call last):
|
|
41
|
-
...
|
|
42
|
-
AssertionError: Image must be a numpy array
|
|
43
|
-
|
|
44
|
-
>>> binary_threshold_image(image.astype(np.uint8), "0.5")
|
|
45
|
-
Traceback (most recent call last):
|
|
46
|
-
...
|
|
47
|
-
AssertionError: threshold must be a number, got <class 'str'>
|
|
48
|
-
|
|
49
|
-
>>> binary_threshold_image(image.astype(np.uint8), 1.5)
|
|
50
|
-
Traceback (most recent call last):
|
|
51
|
-
...
|
|
52
|
-
AssertionError: threshold must be between 0 and 1, got 1.5
|
|
53
|
-
"""
|
|
54
|
-
# Check input data
|
|
55
|
-
check_image(image, ignore_dtype=ignore_dtype)
|
|
56
|
-
assert isinstance(threshold, float | int), f"threshold must be a number, got {type(threshold)}"
|
|
57
|
-
assert 0 <= threshold <= 1, f"threshold must be between 0 and 1, got {threshold}"
|
|
58
|
-
|
|
59
|
-
# Convert threshold from 0-1 range to 0-255 range
|
|
60
|
-
threshold_value: int = int(threshold * 255)
|
|
61
|
-
|
|
62
|
-
# Apply threshold
|
|
63
|
-
if len(image.shape) == 2:
|
|
64
|
-
# Grayscale image
|
|
65
|
-
binary: NDArray[Any] = cv2.threshold(image, threshold_value, 255, cv2.THRESH_BINARY)[1]
|
|
66
|
-
else:
|
|
67
|
-
# Color image - convert to grayscale first, then back to color
|
|
68
|
-
gray: NDArray[Any] = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
|
|
69
|
-
binary: NDArray[Any] = cv2.threshold(gray, threshold_value, 255, cv2.THRESH_BINARY)[1]
|
|
70
|
-
binary = cv2.cvtColor(binary, cv2.COLOR_GRAY2BGR)
|
|
71
|
-
|
|
72
|
-
return binary
|
|
73
|
-
|
|
1
|
+
|
|
2
|
+
# pyright: reportUnusedImport=false
|
|
3
|
+
# ruff: noqa: F401
|
|
4
|
+
|
|
5
|
+
# Imports
|
|
6
|
+
from .common import Any, NDArray, check_image, cv2, np
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
# Functions
|
|
10
|
+
def binary_threshold_image(image: NDArray[Any], threshold: float, ignore_dtype: bool = False) -> NDArray[Any]:
|
|
11
|
+
""" Apply binary threshold to an image.
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
image (NDArray[Any]): Image to threshold
|
|
15
|
+
threshold (float): Threshold value (between 0 and 1)
|
|
16
|
+
ignore_dtype (bool): Ignore the dtype check
|
|
17
|
+
Returns:
|
|
18
|
+
NDArray[Any]: Thresholded binary image
|
|
19
|
+
|
|
20
|
+
>>> ## Basic tests
|
|
21
|
+
>>> image = np.array([[100, 150, 200], [50, 125, 175], [25, 75, 225]])
|
|
22
|
+
>>> binary_threshold_image(image.astype(np.uint8), 0.5).tolist()
|
|
23
|
+
[[0, 255, 255], [0, 0, 255], [0, 0, 255]]
|
|
24
|
+
|
|
25
|
+
>>> np.random.seed(42)
|
|
26
|
+
>>> img = np.random.randint(0, 256, (4,4), dtype=np.uint8)
|
|
27
|
+
>>> thresholded = binary_threshold_image(img, 0.7)
|
|
28
|
+
>>> set(np.unique(thresholded).tolist()) <= {0, 255} # Should only contain 0 and 255
|
|
29
|
+
True
|
|
30
|
+
|
|
31
|
+
>>> rgb = np.random.randint(0, 256, (3,3,3), dtype=np.uint8)
|
|
32
|
+
>>> thresh_rgb = binary_threshold_image(rgb, 0.5)
|
|
33
|
+
>>> thresh_rgb.shape == rgb.shape
|
|
34
|
+
True
|
|
35
|
+
>>> set(np.unique(thresh_rgb).tolist()) <= {0, 255}
|
|
36
|
+
True
|
|
37
|
+
|
|
38
|
+
>>> ## Test invalid inputs
|
|
39
|
+
>>> binary_threshold_image("not an image", 0.5)
|
|
40
|
+
Traceback (most recent call last):
|
|
41
|
+
...
|
|
42
|
+
AssertionError: Image must be a numpy array
|
|
43
|
+
|
|
44
|
+
>>> binary_threshold_image(image.astype(np.uint8), "0.5")
|
|
45
|
+
Traceback (most recent call last):
|
|
46
|
+
...
|
|
47
|
+
AssertionError: threshold must be a number, got <class 'str'>
|
|
48
|
+
|
|
49
|
+
>>> binary_threshold_image(image.astype(np.uint8), 1.5)
|
|
50
|
+
Traceback (most recent call last):
|
|
51
|
+
...
|
|
52
|
+
AssertionError: threshold must be between 0 and 1, got 1.5
|
|
53
|
+
"""
|
|
54
|
+
# Check input data
|
|
55
|
+
check_image(image, ignore_dtype=ignore_dtype)
|
|
56
|
+
assert isinstance(threshold, float | int), f"threshold must be a number, got {type(threshold)}"
|
|
57
|
+
assert 0 <= threshold <= 1, f"threshold must be between 0 and 1, got {threshold}"
|
|
58
|
+
|
|
59
|
+
# Convert threshold from 0-1 range to 0-255 range
|
|
60
|
+
threshold_value: int = int(threshold * 255)
|
|
61
|
+
|
|
62
|
+
# Apply threshold
|
|
63
|
+
if len(image.shape) == 2:
|
|
64
|
+
# Grayscale image
|
|
65
|
+
binary: NDArray[Any] = cv2.threshold(image, threshold_value, 255, cv2.THRESH_BINARY)[1]
|
|
66
|
+
else:
|
|
67
|
+
# Color image - convert to grayscale first, then back to color
|
|
68
|
+
gray: NDArray[Any] = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
|
|
69
|
+
binary: NDArray[Any] = cv2.threshold(gray, threshold_value, 255, cv2.THRESH_BINARY)[1]
|
|
70
|
+
binary = cv2.cvtColor(binary, cv2.COLOR_GRAY2BGR)
|
|
71
|
+
|
|
72
|
+
return binary
|
|
73
|
+
|
|
@@ -1,59 +1,59 @@
|
|
|
1
|
-
|
|
2
|
-
# pyright: reportUnusedImport=false
|
|
3
|
-
# ruff: noqa: F401
|
|
4
|
-
|
|
5
|
-
# Imports
|
|
6
|
-
from .common import Any, NDArray, check_image, cv2, np
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
# Functions
|
|
10
|
-
def blur_image(image: NDArray[Any], blur_strength: float, ignore_dtype: bool = False) -> NDArray[Any]:
|
|
11
|
-
""" Apply Gaussian blur to an image.
|
|
12
|
-
|
|
13
|
-
Args:
|
|
14
|
-
image (NDArray[Any]): Image to blur
|
|
15
|
-
blur_strength (float): Strength of the blur
|
|
16
|
-
ignore_dtype (bool): Ignore the dtype check
|
|
17
|
-
Returns:
|
|
18
|
-
NDArray[Any]: Blurred image
|
|
19
|
-
|
|
20
|
-
>>> ## Basic tests
|
|
21
|
-
>>> image = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
|
|
22
|
-
>>> blurred = blur_image(image.astype(np.uint8), 1.5)
|
|
23
|
-
>>> blurred.shape == image.shape
|
|
24
|
-
True
|
|
25
|
-
|
|
26
|
-
>>> img = np.zeros((5,5), dtype=np.uint8)
|
|
27
|
-
>>> img[2,2] = 255 # Single bright pixel
|
|
28
|
-
>>> blurred = blur_image(img, 1.0)
|
|
29
|
-
>>> bool(blurred[2,2] < 255) # Center should be blurred
|
|
30
|
-
True
|
|
31
|
-
|
|
32
|
-
>>> rgb = np.full((3,3,3), 128, dtype=np.uint8)
|
|
33
|
-
>>> blurred_rgb = blur_image(rgb, 1.0)
|
|
34
|
-
>>> blurred_rgb.shape == (3,3,3)
|
|
35
|
-
True
|
|
36
|
-
|
|
37
|
-
>>> ## Test invalid inputs
|
|
38
|
-
>>> blur_image("not an image", 1.5)
|
|
39
|
-
Traceback (most recent call last):
|
|
40
|
-
...
|
|
41
|
-
AssertionError: Image must be a numpy array
|
|
42
|
-
|
|
43
|
-
>>> blur_image(image.astype(np.uint8), "1.5")
|
|
44
|
-
Traceback (most recent call last):
|
|
45
|
-
...
|
|
46
|
-
AssertionError: blur_strength must be a number, got <class 'str'>
|
|
47
|
-
"""
|
|
48
|
-
# Check input data
|
|
49
|
-
check_image(image, ignore_dtype=ignore_dtype)
|
|
50
|
-
assert isinstance(blur_strength, float | int), f"blur_strength must be a number, got {type(blur_strength)}"
|
|
51
|
-
|
|
52
|
-
# Apply Gaussian blur
|
|
53
|
-
kernel_size: int = max(3, int(blur_strength * 2) + 1)
|
|
54
|
-
if kernel_size % 2 == 0:
|
|
55
|
-
kernel_size += 1
|
|
56
|
-
blurred_image: NDArray[Any] = cv2.GaussianBlur(image, (kernel_size, kernel_size), 0)
|
|
57
|
-
|
|
58
|
-
return blurred_image
|
|
59
|
-
|
|
1
|
+
|
|
2
|
+
# pyright: reportUnusedImport=false
|
|
3
|
+
# ruff: noqa: F401
|
|
4
|
+
|
|
5
|
+
# Imports
|
|
6
|
+
from .common import Any, NDArray, check_image, cv2, np
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
# Functions
|
|
10
|
+
def blur_image(image: NDArray[Any], blur_strength: float, ignore_dtype: bool = False) -> NDArray[Any]:
|
|
11
|
+
""" Apply Gaussian blur to an image.
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
image (NDArray[Any]): Image to blur
|
|
15
|
+
blur_strength (float): Strength of the blur
|
|
16
|
+
ignore_dtype (bool): Ignore the dtype check
|
|
17
|
+
Returns:
|
|
18
|
+
NDArray[Any]: Blurred image
|
|
19
|
+
|
|
20
|
+
>>> ## Basic tests
|
|
21
|
+
>>> image = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
|
|
22
|
+
>>> blurred = blur_image(image.astype(np.uint8), 1.5)
|
|
23
|
+
>>> blurred.shape == image.shape
|
|
24
|
+
True
|
|
25
|
+
|
|
26
|
+
>>> img = np.zeros((5,5), dtype=np.uint8)
|
|
27
|
+
>>> img[2,2] = 255 # Single bright pixel
|
|
28
|
+
>>> blurred = blur_image(img, 1.0)
|
|
29
|
+
>>> bool(blurred[2,2] < 255) # Center should be blurred
|
|
30
|
+
True
|
|
31
|
+
|
|
32
|
+
>>> rgb = np.full((3,3,3), 128, dtype=np.uint8)
|
|
33
|
+
>>> blurred_rgb = blur_image(rgb, 1.0)
|
|
34
|
+
>>> blurred_rgb.shape == (3,3,3)
|
|
35
|
+
True
|
|
36
|
+
|
|
37
|
+
>>> ## Test invalid inputs
|
|
38
|
+
>>> blur_image("not an image", 1.5)
|
|
39
|
+
Traceback (most recent call last):
|
|
40
|
+
...
|
|
41
|
+
AssertionError: Image must be a numpy array
|
|
42
|
+
|
|
43
|
+
>>> blur_image(image.astype(np.uint8), "1.5")
|
|
44
|
+
Traceback (most recent call last):
|
|
45
|
+
...
|
|
46
|
+
AssertionError: blur_strength must be a number, got <class 'str'>
|
|
47
|
+
"""
|
|
48
|
+
# Check input data
|
|
49
|
+
check_image(image, ignore_dtype=ignore_dtype)
|
|
50
|
+
assert isinstance(blur_strength, float | int), f"blur_strength must be a number, got {type(blur_strength)}"
|
|
51
|
+
|
|
52
|
+
# Apply Gaussian blur
|
|
53
|
+
kernel_size: int = max(3, int(blur_strength * 2) + 1)
|
|
54
|
+
if kernel_size % 2 == 0:
|
|
55
|
+
kernel_size += 1
|
|
56
|
+
blurred_image: NDArray[Any] = cv2.GaussianBlur(image, (kernel_size, kernel_size), 0)
|
|
57
|
+
|
|
58
|
+
return blurred_image
|
|
59
|
+
|
|
@@ -1,54 +1,54 @@
|
|
|
1
|
-
|
|
2
|
-
# pyright: reportUnusedImport=false
|
|
3
|
-
# ruff: noqa: F401
|
|
4
|
-
|
|
5
|
-
# Imports
|
|
6
|
-
from .common import Any, NDArray, check_image, cv2, np
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
# Functions
|
|
10
|
-
def brightness_image(image: NDArray[Any], brightness_factor: float, ignore_dtype: bool = False) -> NDArray[Any]:
|
|
11
|
-
""" Adjust the brightness of an image.
|
|
12
|
-
|
|
13
|
-
Args:
|
|
14
|
-
image (NDArray[Any]): Image to adjust brightness
|
|
15
|
-
brightness_factor (float): Brightness adjustment factor
|
|
16
|
-
ignore_dtype (bool): Ignore the dtype check
|
|
17
|
-
Returns:
|
|
18
|
-
NDArray[Any]: Image with adjusted brightness
|
|
19
|
-
|
|
20
|
-
>>> ## Basic tests
|
|
21
|
-
>>> image = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
|
|
22
|
-
>>> brightened = brightness_image(image.astype(np.uint8), 1.5)
|
|
23
|
-
>>> brightened.shape == image.shape
|
|
24
|
-
True
|
|
25
|
-
|
|
26
|
-
>>> img = np.full((3,3), 100, dtype=np.uint8)
|
|
27
|
-
>>> bright = brightness_image(img, 2.0)
|
|
28
|
-
>>> dark = brightness_image(img, 0.5)
|
|
29
|
-
>>> bool(np.mean(bright) > np.mean(img) > np.mean(dark))
|
|
30
|
-
True
|
|
31
|
-
|
|
32
|
-
>>> rgb = np.full((3,3,3), 128, dtype=np.uint8)
|
|
33
|
-
>>> bright_rgb = brightness_image(rgb, 1.5)
|
|
34
|
-
>>> bright_rgb.shape == (3,3,3)
|
|
35
|
-
True
|
|
36
|
-
|
|
37
|
-
>>> ## Test invalid inputs
|
|
38
|
-
>>> brightness_image("not an image", 1.5)
|
|
39
|
-
Traceback (most recent call last):
|
|
40
|
-
...
|
|
41
|
-
AssertionError: Image must be a numpy array
|
|
42
|
-
|
|
43
|
-
>>> brightness_image(image.astype(np.uint8), "1.5")
|
|
44
|
-
Traceback (most recent call last):
|
|
45
|
-
...
|
|
46
|
-
AssertionError: brightness_factor must be a number, got <class 'str'>
|
|
47
|
-
"""
|
|
48
|
-
# Check input data
|
|
49
|
-
check_image(image, ignore_dtype=ignore_dtype)
|
|
50
|
-
assert isinstance(brightness_factor, float | int), f"brightness_factor must be a number, got {type(brightness_factor)}"
|
|
51
|
-
|
|
52
|
-
# Apply brightness adjustment
|
|
53
|
-
return cv2.convertScaleAbs(image, alpha=brightness_factor, beta=0)
|
|
54
|
-
|
|
1
|
+
|
|
2
|
+
# pyright: reportUnusedImport=false
|
|
3
|
+
# ruff: noqa: F401
|
|
4
|
+
|
|
5
|
+
# Imports
|
|
6
|
+
from .common import Any, NDArray, check_image, cv2, np
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
# Functions
|
|
10
|
+
def brightness_image(image: NDArray[Any], brightness_factor: float, ignore_dtype: bool = False) -> NDArray[Any]:
|
|
11
|
+
""" Adjust the brightness of an image.
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
image (NDArray[Any]): Image to adjust brightness
|
|
15
|
+
brightness_factor (float): Brightness adjustment factor
|
|
16
|
+
ignore_dtype (bool): Ignore the dtype check
|
|
17
|
+
Returns:
|
|
18
|
+
NDArray[Any]: Image with adjusted brightness
|
|
19
|
+
|
|
20
|
+
>>> ## Basic tests
|
|
21
|
+
>>> image = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
|
|
22
|
+
>>> brightened = brightness_image(image.astype(np.uint8), 1.5)
|
|
23
|
+
>>> brightened.shape == image.shape
|
|
24
|
+
True
|
|
25
|
+
|
|
26
|
+
>>> img = np.full((3,3), 100, dtype=np.uint8)
|
|
27
|
+
>>> bright = brightness_image(img, 2.0)
|
|
28
|
+
>>> dark = brightness_image(img, 0.5)
|
|
29
|
+
>>> bool(np.mean(bright) > np.mean(img) > np.mean(dark))
|
|
30
|
+
True
|
|
31
|
+
|
|
32
|
+
>>> rgb = np.full((3,3,3), 128, dtype=np.uint8)
|
|
33
|
+
>>> bright_rgb = brightness_image(rgb, 1.5)
|
|
34
|
+
>>> bright_rgb.shape == (3,3,3)
|
|
35
|
+
True
|
|
36
|
+
|
|
37
|
+
>>> ## Test invalid inputs
|
|
38
|
+
>>> brightness_image("not an image", 1.5)
|
|
39
|
+
Traceback (most recent call last):
|
|
40
|
+
...
|
|
41
|
+
AssertionError: Image must be a numpy array
|
|
42
|
+
|
|
43
|
+
>>> brightness_image(image.astype(np.uint8), "1.5")
|
|
44
|
+
Traceback (most recent call last):
|
|
45
|
+
...
|
|
46
|
+
AssertionError: brightness_factor must be a number, got <class 'str'>
|
|
47
|
+
"""
|
|
48
|
+
# Check input data
|
|
49
|
+
check_image(image, ignore_dtype=ignore_dtype)
|
|
50
|
+
assert isinstance(brightness_factor, float | int), f"brightness_factor must be a number, got {type(brightness_factor)}"
|
|
51
|
+
|
|
52
|
+
# Apply brightness adjustment
|
|
53
|
+
return cv2.convertScaleAbs(image, alpha=brightness_factor, beta=0)
|
|
54
|
+
|
|
@@ -1,110 +1,110 @@
|
|
|
1
|
-
|
|
2
|
-
# Imports
|
|
3
|
-
from .common import Any, NDArray, check_image, cv2, np
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
# Functions
|
|
7
|
-
def canny_image(
|
|
8
|
-
image: NDArray[Any],
|
|
9
|
-
threshold1: float,
|
|
10
|
-
threshold2: float,
|
|
11
|
-
aperture_size: int = 3,
|
|
12
|
-
sigma: float = 3,
|
|
13
|
-
stop_at_nms: bool = False,
|
|
14
|
-
ignore_dtype: bool = False,
|
|
15
|
-
) -> NDArray[Any]:
|
|
16
|
-
""" Apply Canny edge detection to an image.
|
|
17
|
-
|
|
18
|
-
Args:
|
|
19
|
-
image (NDArray[Any]): Image to apply Canny edge detection
|
|
20
|
-
threshold1 (float): First threshold for hysteresis (between 0 and 1)
|
|
21
|
-
threshold2 (float): Second threshold for hysteresis (between 0 and 1)
|
|
22
|
-
aperture_size (int): Aperture size for Sobel operator (3, 5, or 7)
|
|
23
|
-
sigma (float): Standard deviation for Gaussian blur
|
|
24
|
-
stop_at_nms (bool): Stop at non-maximum suppression step (don't apply thresholding)
|
|
25
|
-
ignore_dtype (bool): Ignore the dtype check
|
|
26
|
-
Returns:
|
|
27
|
-
NDArray[Any]: Image with Canny edge detection applied
|
|
28
|
-
|
|
29
|
-
>>> ## Basic tests
|
|
30
|
-
>>> image = np.array([[100, 150, 200], [50, 125, 175], [25, 75, 225]])
|
|
31
|
-
>>> edges = canny_image(image.astype(np.uint8), 0.1, 0.2)
|
|
32
|
-
>>> edges.shape == image.shape[:2] # Canny returns single channel
|
|
33
|
-
True
|
|
34
|
-
>>> set(np.unique(edges).tolist()) <= {0, 255} # Only contains 0 and 255
|
|
35
|
-
True
|
|
36
|
-
|
|
37
|
-
>>> rgb = np.random.randint(0, 256, (3,3,3), dtype=np.uint8)
|
|
38
|
-
>>> edges_rgb = canny_image(rgb, 0.1, 0.2)
|
|
39
|
-
>>> edges_rgb.shape == rgb.shape[:2] # Canny returns single channel
|
|
40
|
-
True
|
|
41
|
-
|
|
42
|
-
>>> ## Test invalid inputs
|
|
43
|
-
>>> canny_image("not an image", 0.1, 0.2)
|
|
44
|
-
Traceback (most recent call last):
|
|
45
|
-
...
|
|
46
|
-
AssertionError: Image must be a numpy array
|
|
47
|
-
|
|
48
|
-
>>> canny_image(image.astype(np.uint8), 1.5, 0.2)
|
|
49
|
-
Traceback (most recent call last):
|
|
50
|
-
...
|
|
51
|
-
AssertionError: threshold1 must be between 0 and 1, got 1.5
|
|
52
|
-
"""
|
|
53
|
-
# Check input data
|
|
54
|
-
check_image(image, ignore_dtype=ignore_dtype)
|
|
55
|
-
assert 0 <= threshold1 <= 1, f"threshold1 must be between 0 and 1, got {threshold1}"
|
|
56
|
-
assert 0 <= threshold2 <= 1, f"threshold2 must be between 0 and 1, got {threshold2}"
|
|
57
|
-
assert aperture_size in (3, 5, 7), f"aperture_size must be 3, 5 or 7, got {aperture_size}"
|
|
58
|
-
|
|
59
|
-
# Convert to grayscale if needed
|
|
60
|
-
if len(image.shape) > 2:
|
|
61
|
-
image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
|
|
62
|
-
|
|
63
|
-
# Convert thresholds to 0-255 range
|
|
64
|
-
t1: int = int(threshold1 * 255)
|
|
65
|
-
t2: int = int(threshold2 * 255)
|
|
66
|
-
|
|
67
|
-
if not stop_at_nms:
|
|
68
|
-
# Apply full Canny edge detection
|
|
69
|
-
return cv2.Canny(image, t1, t2, apertureSize=aperture_size)
|
|
70
|
-
else:
|
|
71
|
-
# Manual implementation up to non-maximum suppression
|
|
72
|
-
# 1. Apply Gaussian blur to reduce noise
|
|
73
|
-
blurred: NDArray[Any] = cv2.GaussianBlur(image, (5, 5), sigma)
|
|
74
|
-
|
|
75
|
-
# 2. Calculate gradients using Sobel
|
|
76
|
-
# Use constant value 6 for 64-bit float depth (CV_64F)
|
|
77
|
-
gx: NDArray[Any] = cv2.Sobel(blurred, 6, 1, 0, ksize=aperture_size)
|
|
78
|
-
gy: NDArray[Any] = cv2.Sobel(blurred, 6, 0, 1, ksize=aperture_size)
|
|
79
|
-
|
|
80
|
-
# Calculate gradient magnitude and direction
|
|
81
|
-
magnitude: NDArray[Any] = np.sqrt(gx**2 + gy**2)
|
|
82
|
-
direction: NDArray[Any] = np.arctan2(gy, gx) * 180 / np.pi
|
|
83
|
-
|
|
84
|
-
# 3. Non-maximum suppression
|
|
85
|
-
nms: NDArray[Any] = np.zeros_like(magnitude, dtype=np.uint8)
|
|
86
|
-
height, width = magnitude.shape
|
|
87
|
-
|
|
88
|
-
for i in range(1, height - 1):
|
|
89
|
-
for j in range(1, width - 1):
|
|
90
|
-
# Get gradient direction
|
|
91
|
-
angle: float = direction[i, j]
|
|
92
|
-
if angle < 0:
|
|
93
|
-
angle += 180
|
|
94
|
-
|
|
95
|
-
# Round to 0, 45, 90, or 135 degrees
|
|
96
|
-
if (0 <= angle < 22.5) or (157.5 <= angle <= 180):
|
|
97
|
-
neighbors = [magnitude[i, j-1], magnitude[i, j+1]]
|
|
98
|
-
elif 22.5 <= angle < 67.5:
|
|
99
|
-
neighbors = [magnitude[i-1, j-1], magnitude[i+1, j+1]]
|
|
100
|
-
elif 67.5 <= angle < 112.5:
|
|
101
|
-
neighbors = [magnitude[i-1, j], magnitude[i+1, j]]
|
|
102
|
-
else: # 112.5 <= angle < 157.5
|
|
103
|
-
neighbors = [magnitude[i-1, j+1], magnitude[i+1, j-1]]
|
|
104
|
-
|
|
105
|
-
# Check if current pixel is local maximum
|
|
106
|
-
if magnitude[i, j] >= max(neighbors):
|
|
107
|
-
nms[i, j] = np.uint8(min(magnitude[i, j], 255))
|
|
108
|
-
|
|
109
|
-
return nms
|
|
110
|
-
|
|
1
|
+
|
|
2
|
+
# Imports
|
|
3
|
+
from .common import Any, NDArray, check_image, cv2, np
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
# Functions
|
|
7
|
+
def canny_image(
|
|
8
|
+
image: NDArray[Any],
|
|
9
|
+
threshold1: float,
|
|
10
|
+
threshold2: float,
|
|
11
|
+
aperture_size: int = 3,
|
|
12
|
+
sigma: float = 3,
|
|
13
|
+
stop_at_nms: bool = False,
|
|
14
|
+
ignore_dtype: bool = False,
|
|
15
|
+
) -> NDArray[Any]:
|
|
16
|
+
""" Apply Canny edge detection to an image.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
image (NDArray[Any]): Image to apply Canny edge detection
|
|
20
|
+
threshold1 (float): First threshold for hysteresis (between 0 and 1)
|
|
21
|
+
threshold2 (float): Second threshold for hysteresis (between 0 and 1)
|
|
22
|
+
aperture_size (int): Aperture size for Sobel operator (3, 5, or 7)
|
|
23
|
+
sigma (float): Standard deviation for Gaussian blur
|
|
24
|
+
stop_at_nms (bool): Stop at non-maximum suppression step (don't apply thresholding)
|
|
25
|
+
ignore_dtype (bool): Ignore the dtype check
|
|
26
|
+
Returns:
|
|
27
|
+
NDArray[Any]: Image with Canny edge detection applied
|
|
28
|
+
|
|
29
|
+
>>> ## Basic tests
|
|
30
|
+
>>> image = np.array([[100, 150, 200], [50, 125, 175], [25, 75, 225]])
|
|
31
|
+
>>> edges = canny_image(image.astype(np.uint8), 0.1, 0.2)
|
|
32
|
+
>>> edges.shape == image.shape[:2] # Canny returns single channel
|
|
33
|
+
True
|
|
34
|
+
>>> set(np.unique(edges).tolist()) <= {0, 255} # Only contains 0 and 255
|
|
35
|
+
True
|
|
36
|
+
|
|
37
|
+
>>> rgb = np.random.randint(0, 256, (3,3,3), dtype=np.uint8)
|
|
38
|
+
>>> edges_rgb = canny_image(rgb, 0.1, 0.2)
|
|
39
|
+
>>> edges_rgb.shape == rgb.shape[:2] # Canny returns single channel
|
|
40
|
+
True
|
|
41
|
+
|
|
42
|
+
>>> ## Test invalid inputs
|
|
43
|
+
>>> canny_image("not an image", 0.1, 0.2)
|
|
44
|
+
Traceback (most recent call last):
|
|
45
|
+
...
|
|
46
|
+
AssertionError: Image must be a numpy array
|
|
47
|
+
|
|
48
|
+
>>> canny_image(image.astype(np.uint8), 1.5, 0.2)
|
|
49
|
+
Traceback (most recent call last):
|
|
50
|
+
...
|
|
51
|
+
AssertionError: threshold1 must be between 0 and 1, got 1.5
|
|
52
|
+
"""
|
|
53
|
+
# Check input data
|
|
54
|
+
check_image(image, ignore_dtype=ignore_dtype)
|
|
55
|
+
assert 0 <= threshold1 <= 1, f"threshold1 must be between 0 and 1, got {threshold1}"
|
|
56
|
+
assert 0 <= threshold2 <= 1, f"threshold2 must be between 0 and 1, got {threshold2}"
|
|
57
|
+
assert aperture_size in (3, 5, 7), f"aperture_size must be 3, 5 or 7, got {aperture_size}"
|
|
58
|
+
|
|
59
|
+
# Convert to grayscale if needed
|
|
60
|
+
if len(image.shape) > 2:
|
|
61
|
+
image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
|
|
62
|
+
|
|
63
|
+
# Convert thresholds to 0-255 range
|
|
64
|
+
t1: int = int(threshold1 * 255)
|
|
65
|
+
t2: int = int(threshold2 * 255)
|
|
66
|
+
|
|
67
|
+
if not stop_at_nms:
|
|
68
|
+
# Apply full Canny edge detection
|
|
69
|
+
return cv2.Canny(image, t1, t2, apertureSize=aperture_size)
|
|
70
|
+
else:
|
|
71
|
+
# Manual implementation up to non-maximum suppression
|
|
72
|
+
# 1. Apply Gaussian blur to reduce noise
|
|
73
|
+
blurred: NDArray[Any] = cv2.GaussianBlur(image, (5, 5), sigma)
|
|
74
|
+
|
|
75
|
+
# 2. Calculate gradients using Sobel
|
|
76
|
+
# Use constant value 6 for 64-bit float depth (CV_64F)
|
|
77
|
+
gx: NDArray[Any] = cv2.Sobel(blurred, 6, 1, 0, ksize=aperture_size)
|
|
78
|
+
gy: NDArray[Any] = cv2.Sobel(blurred, 6, 0, 1, ksize=aperture_size)
|
|
79
|
+
|
|
80
|
+
# Calculate gradient magnitude and direction
|
|
81
|
+
magnitude: NDArray[Any] = np.sqrt(gx**2 + gy**2)
|
|
82
|
+
direction: NDArray[Any] = np.arctan2(gy, gx) * 180 / np.pi
|
|
83
|
+
|
|
84
|
+
# 3. Non-maximum suppression
|
|
85
|
+
nms: NDArray[Any] = np.zeros_like(magnitude, dtype=np.uint8)
|
|
86
|
+
height, width = magnitude.shape
|
|
87
|
+
|
|
88
|
+
for i in range(1, height - 1):
|
|
89
|
+
for j in range(1, width - 1):
|
|
90
|
+
# Get gradient direction
|
|
91
|
+
angle: float = direction[i, j]
|
|
92
|
+
if angle < 0:
|
|
93
|
+
angle += 180
|
|
94
|
+
|
|
95
|
+
# Round to 0, 45, 90, or 135 degrees
|
|
96
|
+
if (0 <= angle < 22.5) or (157.5 <= angle <= 180):
|
|
97
|
+
neighbors = [magnitude[i, j-1], magnitude[i, j+1]]
|
|
98
|
+
elif 22.5 <= angle < 67.5:
|
|
99
|
+
neighbors = [magnitude[i-1, j-1], magnitude[i+1, j+1]]
|
|
100
|
+
elif 67.5 <= angle < 112.5:
|
|
101
|
+
neighbors = [magnitude[i-1, j], magnitude[i+1, j]]
|
|
102
|
+
else: # 112.5 <= angle < 157.5
|
|
103
|
+
neighbors = [magnitude[i-1, j+1], magnitude[i+1, j-1]]
|
|
104
|
+
|
|
105
|
+
# Check if current pixel is local maximum
|
|
106
|
+
if magnitude[i, j] >= max(neighbors):
|
|
107
|
+
nms[i, j] = np.uint8(min(magnitude[i, j], 255))
|
|
108
|
+
|
|
109
|
+
return nms
|
|
110
|
+
|