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.
- stouputils/__init__.py +40 -0
- stouputils/__main__.py +86 -0
- stouputils/_deprecated.py +37 -0
- stouputils/all_doctests.py +160 -0
- stouputils/applications/__init__.py +22 -0
- stouputils/applications/automatic_docs.py +634 -0
- stouputils/applications/upscaler/__init__.py +39 -0
- stouputils/applications/upscaler/config.py +128 -0
- stouputils/applications/upscaler/image.py +247 -0
- stouputils/applications/upscaler/video.py +287 -0
- stouputils/archive.py +344 -0
- stouputils/backup.py +488 -0
- stouputils/collections.py +244 -0
- stouputils/continuous_delivery/__init__.py +27 -0
- stouputils/continuous_delivery/cd_utils.py +243 -0
- stouputils/continuous_delivery/github.py +522 -0
- stouputils/continuous_delivery/pypi.py +130 -0
- stouputils/continuous_delivery/pyproject.py +147 -0
- stouputils/continuous_delivery/stubs.py +86 -0
- stouputils/ctx.py +408 -0
- stouputils/data_science/config/get.py +51 -0
- stouputils/data_science/config/set.py +125 -0
- stouputils/data_science/data_processing/image/__init__.py +66 -0
- stouputils/data_science/data_processing/image/auto_contrast.py +79 -0
- stouputils/data_science/data_processing/image/axis_flip.py +58 -0
- stouputils/data_science/data_processing/image/bias_field_correction.py +74 -0
- stouputils/data_science/data_processing/image/binary_threshold.py +73 -0
- stouputils/data_science/data_processing/image/blur.py +59 -0
- stouputils/data_science/data_processing/image/brightness.py +54 -0
- stouputils/data_science/data_processing/image/canny.py +110 -0
- stouputils/data_science/data_processing/image/clahe.py +92 -0
- stouputils/data_science/data_processing/image/common.py +30 -0
- stouputils/data_science/data_processing/image/contrast.py +53 -0
- stouputils/data_science/data_processing/image/curvature_flow_filter.py +74 -0
- stouputils/data_science/data_processing/image/denoise.py +378 -0
- stouputils/data_science/data_processing/image/histogram_equalization.py +123 -0
- stouputils/data_science/data_processing/image/invert.py +64 -0
- stouputils/data_science/data_processing/image/laplacian.py +60 -0
- stouputils/data_science/data_processing/image/median_blur.py +52 -0
- stouputils/data_science/data_processing/image/noise.py +59 -0
- stouputils/data_science/data_processing/image/normalize.py +65 -0
- stouputils/data_science/data_processing/image/random_erase.py +66 -0
- stouputils/data_science/data_processing/image/resize.py +69 -0
- stouputils/data_science/data_processing/image/rotation.py +80 -0
- stouputils/data_science/data_processing/image/salt_pepper.py +68 -0
- stouputils/data_science/data_processing/image/sharpening.py +55 -0
- stouputils/data_science/data_processing/image/shearing.py +64 -0
- stouputils/data_science/data_processing/image/threshold.py +64 -0
- stouputils/data_science/data_processing/image/translation.py +71 -0
- stouputils/data_science/data_processing/image/zoom.py +83 -0
- stouputils/data_science/data_processing/image_augmentation.py +118 -0
- stouputils/data_science/data_processing/image_preprocess.py +183 -0
- stouputils/data_science/data_processing/prosthesis_detection.py +359 -0
- stouputils/data_science/data_processing/technique.py +481 -0
- stouputils/data_science/dataset/__init__.py +45 -0
- stouputils/data_science/dataset/dataset.py +292 -0
- stouputils/data_science/dataset/dataset_loader.py +135 -0
- stouputils/data_science/dataset/grouping_strategy.py +296 -0
- stouputils/data_science/dataset/image_loader.py +100 -0
- stouputils/data_science/dataset/xy_tuple.py +696 -0
- stouputils/data_science/metric_dictionnary.py +106 -0
- stouputils/data_science/metric_utils.py +847 -0
- stouputils/data_science/mlflow_utils.py +206 -0
- stouputils/data_science/models/abstract_model.py +149 -0
- stouputils/data_science/models/all.py +85 -0
- stouputils/data_science/models/base_keras.py +765 -0
- stouputils/data_science/models/keras/all.py +38 -0
- stouputils/data_science/models/keras/convnext.py +62 -0
- stouputils/data_science/models/keras/densenet.py +50 -0
- stouputils/data_science/models/keras/efficientnet.py +60 -0
- stouputils/data_science/models/keras/mobilenet.py +56 -0
- stouputils/data_science/models/keras/resnet.py +52 -0
- stouputils/data_science/models/keras/squeezenet.py +233 -0
- stouputils/data_science/models/keras/vgg.py +42 -0
- stouputils/data_science/models/keras/xception.py +38 -0
- stouputils/data_science/models/keras_utils/callbacks/__init__.py +20 -0
- stouputils/data_science/models/keras_utils/callbacks/colored_progress_bar.py +219 -0
- stouputils/data_science/models/keras_utils/callbacks/learning_rate_finder.py +148 -0
- stouputils/data_science/models/keras_utils/callbacks/model_checkpoint_v2.py +31 -0
- stouputils/data_science/models/keras_utils/callbacks/progressive_unfreezing.py +249 -0
- stouputils/data_science/models/keras_utils/callbacks/warmup_scheduler.py +66 -0
- stouputils/data_science/models/keras_utils/losses/__init__.py +12 -0
- stouputils/data_science/models/keras_utils/losses/next_generation_loss.py +56 -0
- stouputils/data_science/models/keras_utils/visualizations.py +416 -0
- stouputils/data_science/models/model_interface.py +939 -0
- stouputils/data_science/models/sandbox.py +116 -0
- stouputils/data_science/range_tuple.py +234 -0
- stouputils/data_science/scripts/augment_dataset.py +77 -0
- stouputils/data_science/scripts/exhaustive_process.py +133 -0
- stouputils/data_science/scripts/preprocess_dataset.py +70 -0
- stouputils/data_science/scripts/routine.py +168 -0
- stouputils/data_science/utils.py +285 -0
- stouputils/decorators.py +605 -0
- stouputils/image.py +441 -0
- stouputils/installer/__init__.py +18 -0
- stouputils/installer/common.py +67 -0
- stouputils/installer/downloader.py +101 -0
- stouputils/installer/linux.py +144 -0
- stouputils/installer/main.py +223 -0
- stouputils/installer/windows.py +136 -0
- stouputils/io.py +486 -0
- stouputils/parallel.py +483 -0
- stouputils/print.py +482 -0
- stouputils/py.typed +1 -0
- stouputils/stouputils/__init__.pyi +15 -0
- stouputils/stouputils/_deprecated.pyi +12 -0
- stouputils/stouputils/all_doctests.pyi +46 -0
- stouputils/stouputils/applications/__init__.pyi +2 -0
- stouputils/stouputils/applications/automatic_docs.pyi +106 -0
- stouputils/stouputils/applications/upscaler/__init__.pyi +3 -0
- stouputils/stouputils/applications/upscaler/config.pyi +18 -0
- stouputils/stouputils/applications/upscaler/image.pyi +109 -0
- stouputils/stouputils/applications/upscaler/video.pyi +60 -0
- stouputils/stouputils/archive.pyi +67 -0
- stouputils/stouputils/backup.pyi +109 -0
- stouputils/stouputils/collections.pyi +86 -0
- stouputils/stouputils/continuous_delivery/__init__.pyi +5 -0
- stouputils/stouputils/continuous_delivery/cd_utils.pyi +129 -0
- stouputils/stouputils/continuous_delivery/github.pyi +162 -0
- stouputils/stouputils/continuous_delivery/pypi.pyi +53 -0
- stouputils/stouputils/continuous_delivery/pyproject.pyi +67 -0
- stouputils/stouputils/continuous_delivery/stubs.pyi +39 -0
- stouputils/stouputils/ctx.pyi +211 -0
- stouputils/stouputils/decorators.pyi +252 -0
- stouputils/stouputils/image.pyi +172 -0
- stouputils/stouputils/installer/__init__.pyi +5 -0
- stouputils/stouputils/installer/common.pyi +39 -0
- stouputils/stouputils/installer/downloader.pyi +24 -0
- stouputils/stouputils/installer/linux.pyi +39 -0
- stouputils/stouputils/installer/main.pyi +57 -0
- stouputils/stouputils/installer/windows.pyi +31 -0
- stouputils/stouputils/io.pyi +213 -0
- stouputils/stouputils/parallel.pyi +216 -0
- stouputils/stouputils/print.pyi +136 -0
- stouputils/stouputils/version_pkg.pyi +15 -0
- stouputils/version_pkg.py +189 -0
- stouputils-1.14.0.dist-info/METADATA +178 -0
- stouputils-1.14.0.dist-info/RECORD +140 -0
- stouputils-1.14.0.dist-info/WHEEL +4 -0
- 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
|
+
|