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.
Files changed (140) hide show
  1. stouputils/__init__.py +40 -0
  2. stouputils/__main__.py +86 -0
  3. stouputils/_deprecated.py +37 -0
  4. stouputils/all_doctests.py +160 -0
  5. stouputils/applications/__init__.py +22 -0
  6. stouputils/applications/automatic_docs.py +634 -0
  7. stouputils/applications/upscaler/__init__.py +39 -0
  8. stouputils/applications/upscaler/config.py +128 -0
  9. stouputils/applications/upscaler/image.py +247 -0
  10. stouputils/applications/upscaler/video.py +287 -0
  11. stouputils/archive.py +344 -0
  12. stouputils/backup.py +488 -0
  13. stouputils/collections.py +244 -0
  14. stouputils/continuous_delivery/__init__.py +27 -0
  15. stouputils/continuous_delivery/cd_utils.py +243 -0
  16. stouputils/continuous_delivery/github.py +522 -0
  17. stouputils/continuous_delivery/pypi.py +130 -0
  18. stouputils/continuous_delivery/pyproject.py +147 -0
  19. stouputils/continuous_delivery/stubs.py +86 -0
  20. stouputils/ctx.py +408 -0
  21. stouputils/data_science/config/get.py +51 -0
  22. stouputils/data_science/config/set.py +125 -0
  23. stouputils/data_science/data_processing/image/__init__.py +66 -0
  24. stouputils/data_science/data_processing/image/auto_contrast.py +79 -0
  25. stouputils/data_science/data_processing/image/axis_flip.py +58 -0
  26. stouputils/data_science/data_processing/image/bias_field_correction.py +74 -0
  27. stouputils/data_science/data_processing/image/binary_threshold.py +73 -0
  28. stouputils/data_science/data_processing/image/blur.py +59 -0
  29. stouputils/data_science/data_processing/image/brightness.py +54 -0
  30. stouputils/data_science/data_processing/image/canny.py +110 -0
  31. stouputils/data_science/data_processing/image/clahe.py +92 -0
  32. stouputils/data_science/data_processing/image/common.py +30 -0
  33. stouputils/data_science/data_processing/image/contrast.py +53 -0
  34. stouputils/data_science/data_processing/image/curvature_flow_filter.py +74 -0
  35. stouputils/data_science/data_processing/image/denoise.py +378 -0
  36. stouputils/data_science/data_processing/image/histogram_equalization.py +123 -0
  37. stouputils/data_science/data_processing/image/invert.py +64 -0
  38. stouputils/data_science/data_processing/image/laplacian.py +60 -0
  39. stouputils/data_science/data_processing/image/median_blur.py +52 -0
  40. stouputils/data_science/data_processing/image/noise.py +59 -0
  41. stouputils/data_science/data_processing/image/normalize.py +65 -0
  42. stouputils/data_science/data_processing/image/random_erase.py +66 -0
  43. stouputils/data_science/data_processing/image/resize.py +69 -0
  44. stouputils/data_science/data_processing/image/rotation.py +80 -0
  45. stouputils/data_science/data_processing/image/salt_pepper.py +68 -0
  46. stouputils/data_science/data_processing/image/sharpening.py +55 -0
  47. stouputils/data_science/data_processing/image/shearing.py +64 -0
  48. stouputils/data_science/data_processing/image/threshold.py +64 -0
  49. stouputils/data_science/data_processing/image/translation.py +71 -0
  50. stouputils/data_science/data_processing/image/zoom.py +83 -0
  51. stouputils/data_science/data_processing/image_augmentation.py +118 -0
  52. stouputils/data_science/data_processing/image_preprocess.py +183 -0
  53. stouputils/data_science/data_processing/prosthesis_detection.py +359 -0
  54. stouputils/data_science/data_processing/technique.py +481 -0
  55. stouputils/data_science/dataset/__init__.py +45 -0
  56. stouputils/data_science/dataset/dataset.py +292 -0
  57. stouputils/data_science/dataset/dataset_loader.py +135 -0
  58. stouputils/data_science/dataset/grouping_strategy.py +296 -0
  59. stouputils/data_science/dataset/image_loader.py +100 -0
  60. stouputils/data_science/dataset/xy_tuple.py +696 -0
  61. stouputils/data_science/metric_dictionnary.py +106 -0
  62. stouputils/data_science/metric_utils.py +847 -0
  63. stouputils/data_science/mlflow_utils.py +206 -0
  64. stouputils/data_science/models/abstract_model.py +149 -0
  65. stouputils/data_science/models/all.py +85 -0
  66. stouputils/data_science/models/base_keras.py +765 -0
  67. stouputils/data_science/models/keras/all.py +38 -0
  68. stouputils/data_science/models/keras/convnext.py +62 -0
  69. stouputils/data_science/models/keras/densenet.py +50 -0
  70. stouputils/data_science/models/keras/efficientnet.py +60 -0
  71. stouputils/data_science/models/keras/mobilenet.py +56 -0
  72. stouputils/data_science/models/keras/resnet.py +52 -0
  73. stouputils/data_science/models/keras/squeezenet.py +233 -0
  74. stouputils/data_science/models/keras/vgg.py +42 -0
  75. stouputils/data_science/models/keras/xception.py +38 -0
  76. stouputils/data_science/models/keras_utils/callbacks/__init__.py +20 -0
  77. stouputils/data_science/models/keras_utils/callbacks/colored_progress_bar.py +219 -0
  78. stouputils/data_science/models/keras_utils/callbacks/learning_rate_finder.py +148 -0
  79. stouputils/data_science/models/keras_utils/callbacks/model_checkpoint_v2.py +31 -0
  80. stouputils/data_science/models/keras_utils/callbacks/progressive_unfreezing.py +249 -0
  81. stouputils/data_science/models/keras_utils/callbacks/warmup_scheduler.py +66 -0
  82. stouputils/data_science/models/keras_utils/losses/__init__.py +12 -0
  83. stouputils/data_science/models/keras_utils/losses/next_generation_loss.py +56 -0
  84. stouputils/data_science/models/keras_utils/visualizations.py +416 -0
  85. stouputils/data_science/models/model_interface.py +939 -0
  86. stouputils/data_science/models/sandbox.py +116 -0
  87. stouputils/data_science/range_tuple.py +234 -0
  88. stouputils/data_science/scripts/augment_dataset.py +77 -0
  89. stouputils/data_science/scripts/exhaustive_process.py +133 -0
  90. stouputils/data_science/scripts/preprocess_dataset.py +70 -0
  91. stouputils/data_science/scripts/routine.py +168 -0
  92. stouputils/data_science/utils.py +285 -0
  93. stouputils/decorators.py +605 -0
  94. stouputils/image.py +441 -0
  95. stouputils/installer/__init__.py +18 -0
  96. stouputils/installer/common.py +67 -0
  97. stouputils/installer/downloader.py +101 -0
  98. stouputils/installer/linux.py +144 -0
  99. stouputils/installer/main.py +223 -0
  100. stouputils/installer/windows.py +136 -0
  101. stouputils/io.py +486 -0
  102. stouputils/parallel.py +483 -0
  103. stouputils/print.py +482 -0
  104. stouputils/py.typed +1 -0
  105. stouputils/stouputils/__init__.pyi +15 -0
  106. stouputils/stouputils/_deprecated.pyi +12 -0
  107. stouputils/stouputils/all_doctests.pyi +46 -0
  108. stouputils/stouputils/applications/__init__.pyi +2 -0
  109. stouputils/stouputils/applications/automatic_docs.pyi +106 -0
  110. stouputils/stouputils/applications/upscaler/__init__.pyi +3 -0
  111. stouputils/stouputils/applications/upscaler/config.pyi +18 -0
  112. stouputils/stouputils/applications/upscaler/image.pyi +109 -0
  113. stouputils/stouputils/applications/upscaler/video.pyi +60 -0
  114. stouputils/stouputils/archive.pyi +67 -0
  115. stouputils/stouputils/backup.pyi +109 -0
  116. stouputils/stouputils/collections.pyi +86 -0
  117. stouputils/stouputils/continuous_delivery/__init__.pyi +5 -0
  118. stouputils/stouputils/continuous_delivery/cd_utils.pyi +129 -0
  119. stouputils/stouputils/continuous_delivery/github.pyi +162 -0
  120. stouputils/stouputils/continuous_delivery/pypi.pyi +53 -0
  121. stouputils/stouputils/continuous_delivery/pyproject.pyi +67 -0
  122. stouputils/stouputils/continuous_delivery/stubs.pyi +39 -0
  123. stouputils/stouputils/ctx.pyi +211 -0
  124. stouputils/stouputils/decorators.pyi +252 -0
  125. stouputils/stouputils/image.pyi +172 -0
  126. stouputils/stouputils/installer/__init__.pyi +5 -0
  127. stouputils/stouputils/installer/common.pyi +39 -0
  128. stouputils/stouputils/installer/downloader.pyi +24 -0
  129. stouputils/stouputils/installer/linux.pyi +39 -0
  130. stouputils/stouputils/installer/main.pyi +57 -0
  131. stouputils/stouputils/installer/windows.pyi +31 -0
  132. stouputils/stouputils/io.pyi +213 -0
  133. stouputils/stouputils/parallel.pyi +216 -0
  134. stouputils/stouputils/print.pyi +136 -0
  135. stouputils/stouputils/version_pkg.pyi +15 -0
  136. stouputils/version_pkg.py +189 -0
  137. stouputils-1.14.0.dist-info/METADATA +178 -0
  138. stouputils-1.14.0.dist-info/RECORD +140 -0
  139. stouputils-1.14.0.dist-info/WHEEL +4 -0
  140. stouputils-1.14.0.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,59 @@
1
+
2
+ # Imports
3
+ from .common import Any, NDArray, check_image, np
4
+
5
+
6
+ # Functions
7
+ def noise_image(image: NDArray[Any], amount: float, ignore_dtype: bool = False) -> NDArray[Any]:
8
+ """ Add Gaussian noise to an image.
9
+
10
+ Args:
11
+ image (NDArray[Any]): Image to add noise to
12
+ amount (float): Amount of noise to add (between 0 and 1)
13
+ ignore_dtype (bool): Ignore the dtype check
14
+ Returns:
15
+ NDArray[Any]: Noisy image
16
+
17
+ >>> ## Basic tests
18
+ >>> image = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
19
+ >>> noisy = noise_image(image.astype(np.uint8), 0.5)
20
+ >>> noisy.shape == image.shape
21
+ True
22
+ >>> bool(np.all(noisy >= 0) and np.all(noisy <= 255))
23
+ True
24
+
25
+ >>> np.random.seed(0)
26
+ >>> image = np.array([[128] * 3] * 3)
27
+ >>> noise_image(image.astype(np.uint8), 0.1).tolist()
28
+ [[172, 138, 152], [185, 175, 104], [152, 125, 126]]
29
+
30
+ >>> rgb = np.full((3,3,3), 128, dtype=np.uint8)
31
+ >>> noisy_rgb = noise_image(rgb, 0.1)
32
+ >>> noisy_rgb.shape == (3,3,3)
33
+ True
34
+
35
+ >>> ## Test invalid inputs
36
+ >>> noise_image("not an image", 0.5)
37
+ Traceback (most recent call last):
38
+ ...
39
+ AssertionError: Image must be a numpy array
40
+
41
+ >>> noise_image(image.astype(np.uint8), "0.5")
42
+ Traceback (most recent call last):
43
+ ...
44
+ AssertionError: amount must be a number, got <class 'str'>
45
+
46
+ >>> noise_image(image.astype(np.uint8), 1.5)
47
+ Traceback (most recent call last):
48
+ ...
49
+ AssertionError: amount must be between 0 and 1, got 1.5
50
+ """
51
+ # Check input data
52
+ check_image(image, ignore_dtype=ignore_dtype)
53
+ assert isinstance(amount, float | int), f"amount must be a number, got {type(amount)}"
54
+ assert 0 <= amount <= 1, f"amount must be between 0 and 1, got {amount}"
55
+
56
+ # Generate noise
57
+ noise: NDArray[Any] = np.random.normal(0, amount * 255, image.shape).astype(np.int16)
58
+ return np.clip(image.astype(np.int16) + noise, 0, 255).astype(image.dtype) # pyright: ignore [reportUnknownMemberType]
59
+
@@ -0,0 +1,65 @@
1
+
2
+ # pyright: reportUnknownVariableType=false
3
+ # pyright: reportUnusedImport=false
4
+ # pyright: reportArgumentType=false
5
+ # pyright: reportCallIssue=false
6
+
7
+ # Imports
8
+ from .common import Any, NDArray, check_image, cv2, np # noqa: F401
9
+
10
+
11
+ # Functions
12
+ def normalize_image(
13
+ image: NDArray[Any],
14
+ a: float | int = 0,
15
+ b: float | int = 255,
16
+ method: int = cv2.NORM_MINMAX,
17
+ ignore_dtype: bool = False,
18
+ ) -> NDArray[Any]:
19
+ """ Normalize an image to the range 0-255.
20
+
21
+ Args:
22
+ image (NDArray[Any]): Image to normalize
23
+ a (float | int): Minimum value (default: 0)
24
+ b (float | int): Maximum value (default: 255)
25
+ method (int): Normalization method (default: cv2.NORM_MINMAX)
26
+ ignore_dtype (bool): Ignore the dtype check
27
+ Returns:
28
+ NDArray[Any]: Normalized image
29
+
30
+ >>> ## Basic tests
31
+ >>> image = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]], dtype=np.uint8)
32
+ >>> normalized = normalize_image(image)
33
+ >>> normalized.tolist()
34
+ [[0, 32, 64], [96, 128, 159], [191, 223, 255]]
35
+ >>> normalized.shape == image.shape
36
+ True
37
+ >>> normalized.dtype == image.dtype
38
+ True
39
+
40
+ >>> ## Test invalid inputs
41
+ >>> normalize_image("not an image")
42
+ Traceback (most recent call last):
43
+ ...
44
+ AssertionError: Image must be a numpy array
45
+
46
+ >>> normalize_image(image, a="not an integer")
47
+ Traceback (most recent call last):
48
+ ...
49
+ AssertionError: a must be a float or an integer
50
+
51
+ >>> normalize_image(image, b="not an integer")
52
+ Traceback (most recent call last):
53
+ ...
54
+ AssertionError: b must be a float or an integer
55
+ """
56
+ # Check input data
57
+ check_image(image, ignore_dtype=ignore_dtype)
58
+ assert isinstance(a, float | int), "a must be a float or an integer"
59
+ assert isinstance(b, float | int), "b must be a float or an integer"
60
+ assert isinstance(method, int), "method must be an integer"
61
+ assert a < b, "a must be less than b"
62
+
63
+ # Normalize image
64
+ return cv2.normalize(image, None, a, b, method)
65
+
@@ -0,0 +1,66 @@
1
+
2
+ # pyright: reportUnknownMemberType=false
3
+
4
+ # Imports
5
+ from .common import Any, NDArray, check_image, np
6
+
7
+
8
+ # Functions
9
+ def random_erase_image(image: NDArray[Any], erase_factor: float, ignore_dtype: bool = False) -> NDArray[Any]:
10
+ """ Randomly erase a rectangle in the image.
11
+
12
+ Args:
13
+ image (NDArray[Any]): Image to apply random erase
14
+ erase_factor (float): Factor determining the size of the rectangle to erase
15
+ ignore_dtype (bool): Ignore the dtype check
16
+ Returns:
17
+ NDArray[Any]: Image with random erasing applied
18
+
19
+ >>> ## Basic tests
20
+ >>> image = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
21
+ >>> erased = random_erase_image(image.astype(np.uint8), 0.5)
22
+ >>> erased.shape == image.shape
23
+ True
24
+
25
+ >>> np.random.seed(42)
26
+ >>> img = np.ones((5,5), dtype=np.uint8) * 255
27
+ >>> erased = random_erase_image(img, 0.4)
28
+ >>> bool(np.any(erased == 0)) # Should have some erased pixels
29
+ True
30
+
31
+ >>> rgb = np.full((3,3,3), 128, dtype=np.uint8)
32
+ >>> erased_rgb = random_erase_image(rgb, 0.3)
33
+ >>> erased_rgb.shape == (3,3,3)
34
+ True
35
+
36
+ >>> ## Test invalid inputs
37
+ >>> random_erase_image("not an image", 0.5)
38
+ Traceback (most recent call last):
39
+ ...
40
+ AssertionError: Image must be a numpy array
41
+
42
+ >>> random_erase_image(image.astype(np.uint8), "0.5")
43
+ Traceback (most recent call last):
44
+ ...
45
+ AssertionError: erase_factor must be a number, got <class 'str'>
46
+ """
47
+ # Check input data
48
+ check_image(image, ignore_dtype=ignore_dtype)
49
+ assert isinstance(erase_factor, float | int), f"erase_factor must be a number, got {type(erase_factor)}"
50
+
51
+ # Get image dimensions
52
+ height, width = image.shape[:2]
53
+
54
+ # Determine size of the rectangle to erase
55
+ erase_height: int = int(height * erase_factor)
56
+ erase_width: int = int(width * erase_factor)
57
+
58
+ # Randomly choose the top-left corner of the rectangle
59
+ top_left_x: int = np.random.randint(0, width - erase_width)
60
+ top_left_y: int = np.random.randint(0, height - erase_height)
61
+
62
+ # Apply random erasing
63
+ erased_image: NDArray[Any] = image.copy()
64
+ erased_image[top_left_y:top_left_y + erase_height, top_left_x:top_left_x + erase_width] = 0
65
+ return erased_image
66
+
@@ -0,0 +1,69 @@
1
+
2
+ # Imports
3
+ from PIL import Image
4
+
5
+ from .common import Any, NDArray, check_image, np
6
+
7
+
8
+ # Functions
9
+ def resize_image(
10
+ image: NDArray[Any],
11
+ width: int,
12
+ height: int,
13
+ resample: Image.Resampling | int = Image.Resampling.LANCZOS,
14
+ ignore_dtype: bool = False
15
+ ) -> NDArray[Any]:
16
+ """ Resize an image to a new width and height.
17
+
18
+ Args:
19
+ image (NDArray[Any]): Image to resize
20
+ width (int): New width
21
+ height (int): New height
22
+ resample (Image.Resampling | int): Resampling method
23
+ ignore_dtype (bool): Ignore the dtype check
24
+ Returns:
25
+ NDArray[Any]: Image with resized dimensions
26
+
27
+ >>> ## Basic tests
28
+ >>> image = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
29
+ >>> resized = resize_image(image.astype(np.uint8), 6, 6)
30
+ >>> resized.shape
31
+ (6, 6)
32
+
33
+ >>> img = np.ones((5, 5), dtype=np.uint8) * 255
34
+ >>> resized = resize_image(img, 10, 10)
35
+ >>> resized.shape
36
+ (10, 10)
37
+
38
+ >>> rgb = np.full((3, 3, 3), 128, dtype=np.uint8)
39
+ >>> resized_rgb = resize_image(rgb, 6, 6)
40
+ >>> resized_rgb.shape
41
+ (6, 6, 3)
42
+
43
+ >>> ## Test invalid inputs
44
+ >>> resize_image("not an image", 10, 10)
45
+ Traceback (most recent call last):
46
+ ...
47
+ AssertionError: Image must be a numpy array
48
+
49
+ >>> resize_image(image.astype(np.uint8), "10", 10)
50
+ Traceback (most recent call last):
51
+ ...
52
+ AssertionError: width must be integer, got <class 'str'>
53
+
54
+ >>> resize_image(image.astype(np.uint8), 10, "10")
55
+ Traceback (most recent call last):
56
+ ...
57
+ AssertionError: height must be integer, got <class 'str'>
58
+ """
59
+ # Check input data
60
+ check_image(image, ignore_dtype=ignore_dtype)
61
+ assert isinstance(width, int), f"width must be integer, got {type(width)}"
62
+ assert isinstance(height, int), f"height must be integer, got {type(height)}"
63
+ assert isinstance(resample, Image.Resampling | int), f"resample must be Image.Resampling, got {type(resample)}"
64
+
65
+ # Resize image
66
+ new_image: Image.Image = Image.fromarray(image)
67
+ new_image = new_image.resize((width, height), resample=resample)
68
+ return np.array(new_image)
69
+
@@ -0,0 +1,80 @@
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 rotate_image(image: NDArray[Any], angle: float | int, ignore_dtype: bool = False) -> NDArray[Any]:
11
+ """ Rotate an image by a given angle.
12
+
13
+ Args:
14
+ image (NDArray[Any]): Image to rotate
15
+ angle (float|int): Angle in degrees to rotate the image (between -360 and 360)
16
+ ignore_dtype (bool): Ignore the dtype check
17
+ Returns:
18
+ NDArray[Any]: Rotated image
19
+
20
+ >>> ## Basic tests
21
+ >>> image = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
22
+ >>> rotate_image(image.astype(np.int16), 90).tolist()
23
+ [[3, 6, 9], [2, 5, 8], [1, 4, 7]]
24
+
25
+ >>> rotate_image(image.astype(np.float32), 90).tolist()
26
+ [[3.0, 6.0, 9.0], [2.0, 5.0, 8.0], [1.0, 4.0, 7.0]]
27
+
28
+ >>> rotate_image(image.astype(np.uint8), 45).tolist()
29
+ [[1, 4, 4], [2, 5, 8], [2, 6, 5]]
30
+
31
+ >>> rotate_image(image.astype(np.float32), 45).tolist()
32
+ [[1.1875, 3.5625, 3.5625], [2.125, 5.0, 7.875], [2.375, 6.4375, 4.75]]
33
+
34
+ >>> ## Test invalid inputs
35
+ >>> rotate_image([1,2,3], 90)
36
+ Traceback (most recent call last):
37
+ ...
38
+ AssertionError: Image must be a numpy array
39
+
40
+ >>> rotate_image(np.array([1,2,3]), 90)
41
+ Traceback (most recent call last):
42
+ ...
43
+ AssertionError: Image must have at least 2 dimensions
44
+
45
+ >>> rotate_image(image.astype(np.uint8), "90")
46
+ Traceback (most recent call last):
47
+ ...
48
+ AssertionError: Angle must be a number, got <class 'str'>
49
+
50
+ >>> rotate_image(image.astype(np.uint8), 400)
51
+ Traceback (most recent call last):
52
+ ...
53
+ AssertionError: Angle must be between -360 and 360 degrees, got 400
54
+
55
+ >>> rotate_image(image.astype(np.int32), 90)
56
+ Traceback (most recent call last):
57
+ ...
58
+ AssertionError: Image must be of type [<class 'numpy.uint8'>, <class 'numpy.int16'>, <class 'numpy.float32'>, <class 'numpy.float64'>]
59
+ """ # noqa: E501
60
+ # Check input data
61
+ check_image(image, ignore_dtype=ignore_dtype)
62
+ assert isinstance(angle, float | int), f"Angle must be a number, got {type(angle)}"
63
+ assert -360 <= angle <= 360, f"Angle must be between -360 and 360 degrees, got {angle}"
64
+
65
+ # Get image dimensions
66
+ height: int
67
+ width: int
68
+ height, width = image.shape[:2]
69
+ image_center: tuple[int, int] = (width // 2, height // 2)
70
+
71
+ # Get rotation matrix and rotate image
72
+ rotation_matrix: NDArray[Any] = cv2.getRotationMatrix2D(image_center, angle, 1.0)
73
+ rotated_image: NDArray[Any] = cv2.warpAffine(
74
+ image,
75
+ rotation_matrix,
76
+ (width, height)
77
+ )
78
+
79
+ return rotated_image
80
+
@@ -0,0 +1,68 @@
1
+
2
+ # Imports
3
+ from .common import Any, NDArray, check_image, np
4
+
5
+
6
+ # Functions
7
+ def salt_pepper_image(image: NDArray[Any], density: float, ignore_dtype: bool = False) -> NDArray[Any]:
8
+ """ Add salt and pepper noise to an image.
9
+
10
+ Args:
11
+ image (NDArray[Any]): Image to add noise to
12
+ density (float): Density of the noise (between 0 and 1)
13
+ ignore_dtype (bool): Ignore the dtype check
14
+ Returns:
15
+ NDArray[Any]: Image with salt and pepper noise
16
+
17
+ >>> ## Basic tests
18
+ >>> image = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
19
+ >>> noisy = salt_pepper_image(image.astype(np.uint8), 0.1)
20
+ >>> noisy.shape == image.shape
21
+ True
22
+
23
+ >>> np.random.seed(42)
24
+ >>> img = np.full((4,4), 128, dtype=np.uint8)
25
+ >>> noisy = salt_pepper_image(img, 1.0)
26
+ >>> sorted(np.unique(noisy).tolist()) # Should only contain 0 and 255
27
+ [0, 255]
28
+
29
+ >>> rgb = np.full((3,3,3), 128, dtype=np.uint8)
30
+ >>> noisy_rgb = salt_pepper_image(rgb, 0.1)
31
+ >>> noisy_rgb.shape == (3,3,3)
32
+ True
33
+
34
+ >>> ## Test invalid inputs
35
+ >>> salt_pepper_image("not an image", 0.1)
36
+ Traceback (most recent call last):
37
+ ...
38
+ AssertionError: Image must be a numpy array
39
+
40
+ >>> salt_pepper_image(image.astype(np.uint8), "0.1")
41
+ Traceback (most recent call last):
42
+ ...
43
+ AssertionError: density must be a number, got <class 'str'>
44
+
45
+ >>> salt_pepper_image(image.astype(np.uint8), 1.5)
46
+ Traceback (most recent call last):
47
+ ...
48
+ AssertionError: density must be between 0 and 1, got 1.5
49
+ """
50
+ # Check input data
51
+ check_image(image, ignore_dtype=ignore_dtype)
52
+ assert isinstance(density, float | int), f"density must be a number, got {type(density)}"
53
+ assert 0 <= density <= 1, f"density must be between 0 and 1, got {density}"
54
+
55
+ # Create a mask of the same shape as the input image
56
+ mask: NDArray[Any] = np.random.choice( # pyright: ignore [reportUnknownMemberType]
57
+ [0, 1, 2],
58
+ size=image.shape,
59
+ p=[1-density, density/2, density/2],
60
+ )
61
+
62
+ # Apply the mask to the input image
63
+ noisy_image: NDArray[Any] = image.copy()
64
+ noisy_image[mask == 1] = 0 # Pepper noise
65
+ noisy_image[mask == 2] = 255 # Salt noise
66
+
67
+ return noisy_image
68
+
@@ -0,0 +1,55 @@
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 sharpen_image(image: NDArray[Any], alpha: float, ignore_dtype: bool = False) -> NDArray[Any]:
11
+ """ Sharpen an image.
12
+
13
+ Args:
14
+ image (NDArray[Any]): Image to sharpen
15
+ alpha (float): Sharpening factor
16
+ ignore_dtype (bool): Ignore the dtype check
17
+ Returns:
18
+ NDArray[Any]: Sharpened image
19
+
20
+ >>> ## Basic tests
21
+ >>> image = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
22
+ >>> sharpened = sharpen_image(image.astype(np.uint8), 1.5)
23
+ >>> sharpened.shape == image.shape
24
+ True
25
+
26
+ >>> img = np.full((5,5), 128, dtype=np.uint8)
27
+ >>> img[2,2] = 255 # Center bright pixel
28
+ >>> sharp = sharpen_image(img, 1.0)
29
+ >>> bool(sharp[2,2] > img[2,2] * 0.9) # Center should stay bright
30
+ True
31
+
32
+ >>> rgb = np.full((3,3,3), 128, dtype=np.uint8)
33
+ >>> sharp_rgb = sharpen_image(rgb, 1.0)
34
+ >>> sharp_rgb.shape == (3,3,3)
35
+ True
36
+
37
+ >>> ## Test invalid inputs
38
+ >>> sharpen_image("not an image", 1.5)
39
+ Traceback (most recent call last):
40
+ ...
41
+ AssertionError: Image must be a numpy array
42
+
43
+ >>> sharpen_image(image.astype(np.uint8), "1.5")
44
+ Traceback (most recent call last):
45
+ ...
46
+ AssertionError: alpha must be a number, got <class 'str'>
47
+ """
48
+ # Check input data
49
+ check_image(image, ignore_dtype=ignore_dtype)
50
+ assert isinstance(alpha, float | int), f"alpha must be a number, got {type(alpha)}"
51
+
52
+ # Apply sharpening
53
+ blurred: NDArray[Any] = cv2.GaussianBlur(image, (0, 0), 3)
54
+ return cv2.addWeighted(image, 1 + alpha, blurred, -alpha, 0)
55
+
@@ -0,0 +1,64 @@
1
+
2
+ # Imports
3
+ from .common import Any, NDArray, check_image, cv2, np
4
+
5
+
6
+ # Functions
7
+ def shear_image(image: NDArray[Any], x: float, y: float, ignore_dtype: bool = False) -> NDArray[Any]:
8
+ """ Shear an image
9
+
10
+ Args:
11
+ image (NDArray[Any]): Image to shear
12
+ x (float): Shearing along the x axis (between -180 and 180)
13
+ y (float): Shearing along the y axis (between -180 and 180)
14
+ ignore_dtype (bool): Ignore the dtype check
15
+ Returns:
16
+ NDArray[Any]: Sheared image
17
+
18
+ >>> ## Basic tests
19
+ >>> image = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
20
+ >>> shear_image(image.astype(np.uint8), 15, 0).tolist()
21
+ [[1, 2, 3], [3, 5, 6], [3, 7, 8]]
22
+
23
+ >>> shear_image(image.astype(np.float32), 0, 15).tolist()
24
+ [[1.0, 1.4375, 1.40625], [4.0, 4.15625, 4.40625], [7.0, 7.15625, 7.40625]]
25
+
26
+ >>> ## Test invalid inputs
27
+ >>> shear_image(image.astype(np.uint8), 200, 0)
28
+ Traceback (most recent call last):
29
+ ...
30
+ AssertionError: x must be between -180 and 180, got 200
31
+
32
+ >>> shear_image(image.astype(np.uint8), 0, -200)
33
+ Traceback (most recent call last):
34
+ ...
35
+ AssertionError: y must be between -180 and 180, got -200
36
+
37
+ >>> shear_image("not an image", 0, 0)
38
+ Traceback (most recent call last):
39
+ ...
40
+ AssertionError: Image must be a numpy array
41
+ """
42
+ # Check input data
43
+ check_image(image, ignore_dtype=ignore_dtype)
44
+ assert isinstance(x, float | int), f"x must be a number, got {type(x)}"
45
+ assert isinstance(y, float | int), f"y must be a number, got {type(y)}"
46
+ assert -180 <= x <= 180, f"x must be between -180 and 180, got {x}"
47
+ assert -180 <= y <= 180, f"y must be between -180 and 180, got {y}"
48
+
49
+ # Get image dimensions
50
+ height, width = image.shape[:2]
51
+
52
+ # Convert relative shear angles to absolute values
53
+ x_shear: float = np.tan(np.radians(x))
54
+ y_shear: float = np.tan(np.radians(y))
55
+
56
+ # Create shear matrix
57
+ shear_matrix: NDArray[Any] = np.array([
58
+ [1, x_shear, 0],
59
+ [y_shear, 1, 0]
60
+ ], dtype=np.float32)
61
+
62
+ # Apply affine transformation
63
+ return cv2.warpAffine(image, shear_matrix, (width, height))
64
+
@@ -0,0 +1,64 @@
1
+
2
+ # Imports
3
+ from .common import Any, NDArray, check_image, cv2, np
4
+
5
+
6
+ # Functions
7
+ def threshold_image(image: NDArray[Any], thresholds: list[float], ignore_dtype: bool = False) -> NDArray[Any]:
8
+ """ Apply multi-level threshold to an image.
9
+
10
+ Args:
11
+ image (NDArray[Any]): Image to threshold
12
+ threshold (list[float]): List of threshold values (between 0 and 1)
13
+ ignore_dtype (bool): Ignore the dtype check
14
+ Returns:
15
+ NDArray[Any]: Multi-level thresholded image
16
+
17
+ >>> ## Basic tests
18
+ >>> image = np.array([[100, 150, 200], [50, 125, 175], [25, 75, 225]])
19
+ >>> threshold_image(image.astype(np.uint8), [0.3, 0.6]).tolist()
20
+ [[85, 85, 170], [0, 85, 170], [0, 0, 170]]
21
+
22
+ >>> rgb = np.random.randint(0, 256, (3,3,3), dtype=np.uint8)
23
+ >>> thresh_rgb = threshold_image(rgb, [0.3, 0.6])
24
+ >>> thresh_rgb.shape == rgb.shape
25
+ True
26
+
27
+ >>> ## Test invalid inputs
28
+ >>> threshold_image("not an image", [0.5])
29
+ Traceback (most recent call last):
30
+ ...
31
+ AssertionError: Image must be a numpy array
32
+
33
+ >>> threshold_image(image.astype(np.uint8), [1.5])
34
+ Traceback (most recent call last):
35
+ ...
36
+ AssertionError: threshold values must be between 0 and 1, got [1.5]
37
+ """
38
+ # Check input data
39
+ check_image(image, ignore_dtype=ignore_dtype)
40
+ assert all(0 <= t <= 1 for t in thresholds), f"threshold values must be between 0 and 1, got {thresholds}"
41
+
42
+ # Convert thresholds from 0-1 range to 0-255 range
43
+ threshold_values: list[int] = [int(t * 255) for t in sorted(thresholds)]
44
+
45
+ # Apply threshold
46
+ if len(image.shape) == 2:
47
+ # Grayscale image
48
+ result: NDArray[Any] = np.zeros_like(image)
49
+ for i, thresh in enumerate(threshold_values):
50
+ value: int = int(255 * (i + 1) / (len(threshold_values) + 1))
51
+ mask: NDArray[Any] = cv2.threshold(image, thresh, value, cv2.THRESH_BINARY)[1]
52
+ result = cv2.max(result, mask)
53
+ else:
54
+ # Color image - convert to grayscale first, then back to color
55
+ gray: NDArray[Any] = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
56
+ result: NDArray[Any] = np.zeros_like(gray)
57
+ for i, thresh in enumerate(threshold_values):
58
+ value: int = int(255 * (i + 1) / (len(threshold_values) + 1))
59
+ mask: NDArray[Any] = cv2.threshold(gray, thresh, value, cv2.THRESH_BINARY)[1]
60
+ result = cv2.max(result, mask)
61
+ result = cv2.cvtColor(result, cv2.COLOR_GRAY2BGR)
62
+
63
+ return result
64
+
@@ -0,0 +1,71 @@
1
+
2
+ # Imports
3
+ from .common import Any, NDArray, check_image, cv2, np
4
+
5
+
6
+ # Functions
7
+ def translate_image(image: NDArray[Any], x: float, y: float, padding: int = 0, ignore_dtype: bool = False) -> NDArray[Any]:
8
+ """ Translate an image
9
+
10
+ Args:
11
+ image (NDArray[Any]): Image to translate
12
+ x (float): Translation along the x axis (between -1 and 1)
13
+ y (float): Translation along the y axis (between -1 and 1)
14
+ padding (int): Padding that has been added to the image before calling this function
15
+ ignore_dtype (bool): Ignore the dtype check
16
+ Returns:
17
+ NDArray[Any]: Translated image
18
+
19
+ >>> ## Basic tests
20
+ >>> image = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]).astype(np.uint8)
21
+ >>> translate_image(image, 0.5, 0.5).tolist()
22
+ [[0, 0, 0], [0, 1, 2], [0, 4, 5]]
23
+
24
+ >>> translate_image(image, 0, -2/3).tolist()
25
+ [[7, 8, 9], [0, 0, 0], [0, 0, 0]]
26
+
27
+ >>> ## Test invalid inputs
28
+ >>> translate_image(image, 2, 0)
29
+ Traceback (most recent call last):
30
+ ...
31
+ AssertionError: x must be between -1 and 1, got 2
32
+
33
+ >>> translate_image(image, 0, 2)
34
+ Traceback (most recent call last):
35
+ ...
36
+ AssertionError: y must be between -1 and 1, got 2
37
+
38
+ >>> translate_image("not an image", 0, 0)
39
+ Traceback (most recent call last):
40
+ ...
41
+ AssertionError: Image must be a numpy array
42
+
43
+ >>> translate_image(image, 0, 0, padding=-1)
44
+ Traceback (most recent call last):
45
+ ...
46
+ AssertionError: padding must be positive, got -1
47
+ """
48
+ # Check input data
49
+ check_image(image, ignore_dtype=ignore_dtype)
50
+ assert isinstance(x, float | int), f"x must be a number, got {type(x)}"
51
+ assert isinstance(y, float | int), f"y must be a number, got {type(y)}"
52
+ assert -1 <= x <= 1, f"x must be between -1 and 1, got {x}"
53
+ assert -1 <= y <= 1, f"y must be between -1 and 1, got {y}"
54
+ assert isinstance(padding, int), f"padding must be an integer, got {type(padding)}"
55
+ assert padding >= 0, f"padding must be positive, got {padding}"
56
+
57
+ # Get image dimensions
58
+ height, width = image.shape[:2]
59
+ original_width: int = width - 2 * padding
60
+ original_height: int = height - 2 * padding
61
+
62
+ # Convert relative translations to absolute pixels
63
+ x_pixels: int = int(x * original_width)
64
+ y_pixels: int = int(y * original_height)
65
+
66
+ # Create translation matrix
67
+ translation_matrix: NDArray[Any] = np.array([[1, 0, x_pixels], [0, 1, y_pixels]], dtype=np.float32)
68
+
69
+ # Apply affine transformation
70
+ return cv2.warpAffine(image, translation_matrix, (width, height))
71
+