stouputils 1.14.3__py3-none-any.whl → 1.15.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 (78) hide show
  1. stouputils/data_science/config/get.py +51 -51
  2. stouputils/data_science/data_processing/image/__init__.py +66 -66
  3. stouputils/data_science/data_processing/image/auto_contrast.py +79 -79
  4. stouputils/data_science/data_processing/image/axis_flip.py +58 -58
  5. stouputils/data_science/data_processing/image/bias_field_correction.py +74 -74
  6. stouputils/data_science/data_processing/image/binary_threshold.py +73 -73
  7. stouputils/data_science/data_processing/image/blur.py +59 -59
  8. stouputils/data_science/data_processing/image/brightness.py +54 -54
  9. stouputils/data_science/data_processing/image/canny.py +110 -110
  10. stouputils/data_science/data_processing/image/clahe.py +92 -92
  11. stouputils/data_science/data_processing/image/common.py +30 -30
  12. stouputils/data_science/data_processing/image/contrast.py +53 -53
  13. stouputils/data_science/data_processing/image/curvature_flow_filter.py +74 -74
  14. stouputils/data_science/data_processing/image/denoise.py +378 -378
  15. stouputils/data_science/data_processing/image/histogram_equalization.py +123 -123
  16. stouputils/data_science/data_processing/image/invert.py +64 -64
  17. stouputils/data_science/data_processing/image/laplacian.py +60 -60
  18. stouputils/data_science/data_processing/image/median_blur.py +52 -52
  19. stouputils/data_science/data_processing/image/noise.py +59 -59
  20. stouputils/data_science/data_processing/image/normalize.py +65 -65
  21. stouputils/data_science/data_processing/image/random_erase.py +66 -66
  22. stouputils/data_science/data_processing/image/resize.py +69 -69
  23. stouputils/data_science/data_processing/image/rotation.py +80 -80
  24. stouputils/data_science/data_processing/image/salt_pepper.py +68 -68
  25. stouputils/data_science/data_processing/image/sharpening.py +55 -55
  26. stouputils/data_science/data_processing/image/shearing.py +64 -64
  27. stouputils/data_science/data_processing/image/threshold.py +64 -64
  28. stouputils/data_science/data_processing/image/translation.py +71 -71
  29. stouputils/data_science/data_processing/image/zoom.py +83 -83
  30. stouputils/data_science/data_processing/image_augmentation.py +118 -118
  31. stouputils/data_science/data_processing/image_preprocess.py +183 -183
  32. stouputils/data_science/data_processing/prosthesis_detection.py +359 -359
  33. stouputils/data_science/data_processing/technique.py +481 -481
  34. stouputils/data_science/dataset/__init__.py +45 -45
  35. stouputils/data_science/dataset/dataset.py +292 -292
  36. stouputils/data_science/dataset/dataset_loader.py +135 -135
  37. stouputils/data_science/dataset/grouping_strategy.py +296 -296
  38. stouputils/data_science/dataset/image_loader.py +100 -100
  39. stouputils/data_science/dataset/xy_tuple.py +696 -696
  40. stouputils/data_science/metric_dictionnary.py +106 -106
  41. stouputils/data_science/mlflow_utils.py +206 -206
  42. stouputils/data_science/models/abstract_model.py +149 -149
  43. stouputils/data_science/models/all.py +85 -85
  44. stouputils/data_science/models/keras/all.py +38 -38
  45. stouputils/data_science/models/keras/convnext.py +62 -62
  46. stouputils/data_science/models/keras/densenet.py +50 -50
  47. stouputils/data_science/models/keras/efficientnet.py +60 -60
  48. stouputils/data_science/models/keras/mobilenet.py +56 -56
  49. stouputils/data_science/models/keras/resnet.py +52 -52
  50. stouputils/data_science/models/keras/squeezenet.py +233 -233
  51. stouputils/data_science/models/keras/vgg.py +42 -42
  52. stouputils/data_science/models/keras/xception.py +38 -38
  53. stouputils/data_science/models/keras_utils/callbacks/__init__.py +20 -20
  54. stouputils/data_science/models/keras_utils/callbacks/colored_progress_bar.py +219 -219
  55. stouputils/data_science/models/keras_utils/callbacks/learning_rate_finder.py +148 -148
  56. stouputils/data_science/models/keras_utils/callbacks/model_checkpoint_v2.py +31 -31
  57. stouputils/data_science/models/keras_utils/callbacks/progressive_unfreezing.py +249 -249
  58. stouputils/data_science/models/keras_utils/callbacks/warmup_scheduler.py +66 -66
  59. stouputils/data_science/models/keras_utils/losses/__init__.py +12 -12
  60. stouputils/data_science/models/keras_utils/losses/next_generation_loss.py +56 -56
  61. stouputils/data_science/models/keras_utils/visualizations.py +416 -416
  62. stouputils/data_science/models/sandbox.py +116 -116
  63. stouputils/data_science/range_tuple.py +234 -234
  64. stouputils/data_science/utils.py +285 -285
  65. stouputils/decorators.py +53 -39
  66. stouputils/decorators.pyi +2 -2
  67. stouputils/installer/__init__.py +18 -18
  68. stouputils/installer/linux.py +144 -144
  69. stouputils/installer/main.py +223 -223
  70. stouputils/installer/windows.py +136 -136
  71. stouputils/io.py +16 -9
  72. stouputils/print.py +229 -2
  73. stouputils/print.pyi +90 -1
  74. stouputils/py.typed +1 -1
  75. {stouputils-1.14.3.dist-info → stouputils-1.15.0.dist-info}/METADATA +1 -1
  76. {stouputils-1.14.3.dist-info → stouputils-1.15.0.dist-info}/RECORD +78 -78
  77. {stouputils-1.14.3.dist-info → stouputils-1.15.0.dist-info}/WHEEL +1 -1
  78. {stouputils-1.14.3.dist-info → stouputils-1.15.0.dist-info}/entry_points.txt +0 -0
@@ -1,80 +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
-
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
+
@@ -1,68 +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
-
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
+
@@ -1,55 +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
-
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
+
@@ -1,64 +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
-
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
+
@@ -1,64 +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
-
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
+