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,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
+
@@ -0,0 +1,92 @@
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 clahe_image(
11
+ image: NDArray[Any],
12
+ clip_limit: float = 2.0,
13
+ tile_grid_size: int = 8,
14
+ ignore_dtype: bool = False,
15
+ ) -> NDArray[Any]:
16
+ """ Apply Contrast Limited Adaptive Histogram Equalization (CLAHE) to an image.
17
+
18
+ Args:
19
+ image (NDArray[Any]): Image to apply CLAHE to
20
+ clip_limit (float): Threshold for contrast limiting (1.0-4.0 recommended)
21
+ tile_grid_size (int): Size of grid for histogram equalization (2-16 recommended)
22
+ ignore_dtype (bool): Ignore the dtype check
23
+ Returns:
24
+ NDArray[Any]: Image with CLAHE applied
25
+
26
+ >>> ## Basic tests
27
+ >>> image = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
28
+ >>> clahed = clahe_image(image.astype(np.uint8), 2.0, 1)
29
+ >>> clahed.tolist()
30
+ [[28, 57, 85], [113, 142, 170], [198, 227, 255]]
31
+ >>> clahed.shape == image.shape
32
+ True
33
+
34
+ >>> img = np.full((10,10), 128, dtype=np.uint8)
35
+ >>> img[2:8, 2:8] = 200 # Create a bright region
36
+ >>> clahed = clahe_image(img, 2.0, 4)
37
+ >>> bool(np.mean(clahed) > np.mean(img)) # Should enhance contrast
38
+ True
39
+ >>> bool(np.std(clahed) > np.std(img)) # Should enhance contrast
40
+ True
41
+
42
+ >>> rgb = np.full((10,10,3), 128, dtype=np.uint8)
43
+ >>> rgb[2:8, 2:8, :] = 50 # Create a dark region
44
+ >>> clahed_rgb = clahe_image(rgb, 2.0, 4)
45
+ >>> bool(np.mean(clahed_rgb) > np.mean(rgb)) # Should enhance contrast
46
+ True
47
+ >>> bool(np.std(clahed_rgb) > np.std(rgb)) # Should enhance contrast
48
+ True
49
+ >>> clahed_rgb.shape == rgb.shape
50
+ True
51
+
52
+ >>> ## Test invalid inputs
53
+ >>> clahe_image("not an image", 2.0, 8)
54
+ Traceback (most recent call last):
55
+ ...
56
+ AssertionError: Image must be a numpy array
57
+
58
+ >>> clahe_image(image.astype(np.uint8), "2.0", 8)
59
+ Traceback (most recent call last):
60
+ ...
61
+ AssertionError: clip_limit must be a number, got <class 'str'>
62
+
63
+ >>> clahe_image(image.astype(np.uint8), 2.0, -1)
64
+ Traceback (most recent call last):
65
+ ...
66
+ AssertionError: tile_grid_size must be positive, got -1
67
+ """
68
+ # Check input data
69
+ check_image(image, ignore_dtype=ignore_dtype)
70
+ assert isinstance(clip_limit, float | int), f"clip_limit must be a number, got {type(clip_limit)}"
71
+ assert isinstance(tile_grid_size, int), f"tile_grid_size must be an integer, got {type(tile_grid_size)}"
72
+ assert tile_grid_size > 0, f"tile_grid_size must be positive, got {tile_grid_size}"
73
+
74
+ # Create CLAHE object
75
+ clahe: cv2.CLAHE = cv2.createCLAHE(clipLimit=clip_limit, tileGridSize=(tile_grid_size, tile_grid_size))
76
+
77
+ # Handle different image types
78
+ if len(image.shape) == 2:
79
+ # Grayscale image
80
+ return clahe.apply(image)
81
+ else:
82
+ # Color image - convert to LAB color space
83
+ lab: NDArray[Any] = cv2.cvtColor(image, cv2.COLOR_BGR2LAB)
84
+ channel_l, channel_a, channel_b = cv2.split(lab)
85
+
86
+ # Apply CLAHE to L channel
87
+ cl: NDArray[Any] = clahe.apply(channel_l)
88
+
89
+ # Merge channels and convert back to BGR
90
+ limg: NDArray[Any] = cv2.merge((cl, channel_a, channel_b))
91
+ return cv2.cvtColor(limg, cv2.COLOR_LAB2BGR)
92
+
@@ -0,0 +1,30 @@
1
+
2
+
3
+ # pyright: reportUnusedImport=false
4
+ # ruff: noqa: F401
5
+
6
+ # Imports
7
+ from typing import Any
8
+
9
+ import cv2
10
+ import numpy as np
11
+ from numpy.typing import NDArray
12
+
13
+
14
+ # Functions
15
+ def check_image(image: NDArray[Any], ignore_dtype: bool = False) -> None:
16
+ """ Check if the image is valid
17
+
18
+ Args:
19
+ image (NDArray[Any]): Image to check
20
+ ignore_dtype (bool): Ignore the dtype check
21
+ """
22
+ # Check input data
23
+ assert isinstance(image, np.ndarray), "Image must be a numpy array"
24
+ assert len(image.shape) >= 2, "Image must have at least 2 dimensions"
25
+
26
+ # Check dtype
27
+ if not ignore_dtype:
28
+ dtypes: list[type] = [np.uint8, np.int16, np.float32, np.float64]
29
+ assert image.dtype in dtypes, f"Image must be of type {dtypes}"
30
+
@@ -0,0 +1,53 @@
1
+
2
+ # Imports
3
+ from .common import Any, NDArray, check_image, cv2, np
4
+
5
+
6
+ # Functions
7
+ def contrast_image(image: NDArray[Any], factor: float, ignore_dtype: bool = False) -> NDArray[Any]:
8
+ """ Adjust the contrast of an image.
9
+
10
+ Args:
11
+ image (NDArray[Any]): Image to adjust contrast
12
+ factor (float): Contrast adjustment factor
13
+ ignore_dtype (bool): Ignore the dtype check
14
+ Returns:
15
+ NDArray[Any]: Image with adjusted contrast
16
+
17
+ >>> ## Basic tests
18
+ >>> image = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
19
+ >>> contrasted = contrast_image(image.astype(np.uint8), 1.5)
20
+ >>> contrasted.shape == image.shape
21
+ True
22
+
23
+ >>> img = np.array([[50, 100, 150]], dtype=np.uint8)
24
+ >>> high = contrast_image(img, 2.0)
25
+ >>> low = contrast_image(img, 0.5)
26
+ >>> bool(high.std() > img.std() > low.std()) # Higher contrast = higher std
27
+ True
28
+
29
+ >>> rgb = np.full((3,3,3), 128, dtype=np.uint8)
30
+ >>> rgb[1,1] = [50, 100, 150]
31
+ >>> cont_rgb = contrast_image(rgb, 1.5)
32
+ >>> cont_rgb.shape == (3,3,3)
33
+ True
34
+
35
+ >>> ## Test invalid inputs
36
+ >>> contrast_image("not an image", 1.5)
37
+ Traceback (most recent call last):
38
+ ...
39
+ AssertionError: Image must be a numpy array
40
+
41
+ >>> contrast_image(image.astype(np.uint8), "1.5")
42
+ Traceback (most recent call last):
43
+ ...
44
+ AssertionError: factor must be a number, got <class 'str'>
45
+ """
46
+ # Check input data
47
+ check_image(image, ignore_dtype=ignore_dtype)
48
+ assert isinstance(factor, float | int), f"factor must be a number, got {type(factor)}"
49
+
50
+ # Apply contrast adjustment
51
+ mean: float = float(np.mean(image))
52
+ return cv2.addWeighted(image, factor, image, 0, mean * (1 - factor))
53
+
@@ -0,0 +1,74 @@
1
+
2
+ # pyright: reportUnknownMemberType=false
3
+ # pyright: reportUnknownArgumentType=false
4
+ # pyright: reportUnknownVariableType=false
5
+ # pyright: reportUnusedImport=false
6
+ # ruff: noqa: F401
7
+
8
+ # Imports
9
+ import SimpleITK as Sitk
10
+
11
+ from .common import Any, NDArray, check_image, np
12
+
13
+
14
+ # Functions
15
+ def curvature_flow_filter_image(
16
+ image: NDArray[Any],
17
+ time_step: float = 0.05,
18
+ number_of_iterations: int = 5,
19
+ ignore_dtype: bool = False,
20
+ ) -> NDArray[Any]:
21
+ """ Apply a curvature flow filter to an image.
22
+
23
+ Args:
24
+ image (NDArray[Any]): Image to apply the curvature flow filter
25
+ time_step (float): Time step for the curvature flow filter
26
+ number_of_iterations (int): Number of iterations for the curvature flow filter
27
+ ignore_dtype (bool): Ignore the dtype check
28
+ Returns:
29
+ NDArray[Any]: Image with the curvature flow filter applied
30
+
31
+ >>> ## Basic tests
32
+ >>> image = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]], dtype=np.uint8)
33
+ >>> filtered = curvature_flow_filter_image(image, 0.05, 5)
34
+ >>> filtered.tolist()[0][0]
35
+ 1.2910538407309702
36
+ >>> filtered.shape == image.shape
37
+ True
38
+ >>> filtered.dtype == image.dtype
39
+ False
40
+
41
+ >>> rgb = np.full((3,3,3), 128, dtype=np.uint8)
42
+ >>> rgb[1,1] = [50, 100, 150]
43
+ >>> filtered_rgb = curvature_flow_filter_image(rgb, 0.05, 5)
44
+ >>> filtered_rgb.shape == (3,3,3)
45
+ True
46
+
47
+ >>> ## Test invalid inputs
48
+ >>> curvature_flow_filter_image("not an image")
49
+ Traceback (most recent call last):
50
+ ...
51
+ AssertionError: Image must be a numpy array
52
+
53
+ >>> curvature_flow_filter_image(image, time_step="not a float")
54
+ Traceback (most recent call last):
55
+ ...
56
+ AssertionError: time_step must be a float
57
+
58
+ >>> curvature_flow_filter_image(image, number_of_iterations="not an integer")
59
+ Traceback (most recent call last):
60
+ ...
61
+ AssertionError: number_of_iterations must be an integer
62
+ """
63
+ # Check input data
64
+ check_image(image, ignore_dtype=ignore_dtype)
65
+ assert isinstance(time_step, float), "time_step must be a float"
66
+ assert isinstance(number_of_iterations, int), "number_of_iterations must be an integer"
67
+ assert time_step > 0, "time_step must be greater than 0"
68
+ assert number_of_iterations > 0, "number_of_iterations must be greater than 0"
69
+
70
+ # Apply the curvature flow filter
71
+ image_Sitk: Sitk.Image = Sitk.GetImageFromArray(image)
72
+ image_Sitk = Sitk.CurvatureFlow(image_Sitk, timeStep=time_step, numberOfIterations=number_of_iterations)
73
+ return Sitk.GetArrayFromImage(image_Sitk)
74
+