ImagePRO-Python 1.2.2__py3-none-any.whl → 1.3.1__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.
- ImagePRO/__init__.py +2 -2
- ImagePRO/human_analysis/body_analysis/__init__.py +4 -0
- ImagePRO/human_analysis/body_analysis/body_pose_estimation.py +26 -5
- ImagePRO/human_analysis/body_analysis/hand_tracking.py +28 -6
- ImagePRO/human_analysis/face_analysis/__init__.py +5 -1
- ImagePRO/human_analysis/face_analysis/eye_status_analysis.py +22 -4
- ImagePRO/human_analysis/face_analysis/face_comparison.py +15 -3
- ImagePRO/human_analysis/face_analysis/face_detection.py +5 -2
- ImagePRO/human_analysis/face_analysis/face_mesh_analysis.py +36 -18
- ImagePRO/human_analysis/face_analysis/head_pose_estimation.py +14 -4
- ImagePRO/object_analysis/__init__.py +3 -0
- ImagePRO/object_analysis/object_detection.py +24 -13
- ImagePRO/pre_processing/__init__.py +4 -1
- ImagePRO/pre_processing/blur.py +10 -10
- ImagePRO/pre_processing/contrast.py +4 -7
- ImagePRO/pre_processing/crop.py +4 -5
- ImagePRO/pre_processing/dataset_generator.py +16 -10
- ImagePRO/pre_processing/histogram.py +36 -48
- ImagePRO/pre_processing/resize.py +2 -2
- ImagePRO/pre_processing/rotate.py +6 -7
- ImagePRO/pre_processing/sharpen.py +16 -13
- ImagePRO/utils/image.py +7 -6
- ImagePRO/utils/result.py +10 -7
- {imagepro_python-1.2.2.dist-info → imagepro_python-1.3.1.dist-info}/METADATA +46 -18
- imagepro_python-1.3.1.dist-info/RECORD +31 -0
- {imagepro_python-1.2.2.dist-info → imagepro_python-1.3.1.dist-info}/WHEEL +1 -1
- imagepro_python-1.2.2.dist-info/RECORD +0 -31
- {imagepro_python-1.2.2.dist-info → imagepro_python-1.3.1.dist-info}/licenses/LICENSE +0 -0
- {imagepro_python-1.2.2.dist-info → imagepro_python-1.3.1.dist-info}/top_level.txt +0 -0
ImagePRO/pre_processing/blur.py
CHANGED
|
@@ -60,9 +60,9 @@ def apply_average_blur(
|
|
|
60
60
|
):
|
|
61
61
|
raise ValueError("'kernel_size' must be a tuple of two positive integers.")
|
|
62
62
|
|
|
63
|
-
# Apply box filter blur
|
|
64
|
-
blurred = cv2.blur(image._data
|
|
65
|
-
|
|
63
|
+
# Apply box filter blur (cv2.blur never mutates its input)
|
|
64
|
+
blurred = cv2.blur(image._data, kernel_size)
|
|
65
|
+
|
|
66
66
|
return Result(
|
|
67
67
|
image=blurred,
|
|
68
68
|
meta={
|
|
@@ -112,8 +112,8 @@ def apply_gaussian_blur(
|
|
|
112
112
|
raise ValueError("'kernel_size' must be a tuple of two odd positive integers.")
|
|
113
113
|
|
|
114
114
|
# Apply Gaussian blur with automatic sigma calculation
|
|
115
|
-
blurred = cv2.GaussianBlur(image._data
|
|
116
|
-
|
|
115
|
+
blurred = cv2.GaussianBlur(image._data, kernel_size, 0)
|
|
116
|
+
|
|
117
117
|
return Result(
|
|
118
118
|
image=blurred,
|
|
119
119
|
meta={
|
|
@@ -160,8 +160,8 @@ def apply_median_blur(
|
|
|
160
160
|
raise ValueError("'filter_size' must be an odd integer greater than 1.")
|
|
161
161
|
|
|
162
162
|
# Apply median filtering
|
|
163
|
-
blurred = cv2.medianBlur(image._data
|
|
164
|
-
|
|
163
|
+
blurred = cv2.medianBlur(image._data, filter_size)
|
|
164
|
+
|
|
165
165
|
return Result(
|
|
166
166
|
image=blurred,
|
|
167
167
|
meta={
|
|
@@ -203,7 +203,7 @@ def apply_bilateral_blur(
|
|
|
203
203
|
Result: Result object with smoothed image.
|
|
204
204
|
- image (np.ndarray): Edge-preserving smoothed output
|
|
205
205
|
- data (None): No additional data
|
|
206
|
-
- meta (dict): Contains filter
|
|
206
|
+
- meta (dict): Contains filter parameters and operation info
|
|
207
207
|
|
|
208
208
|
Raises:
|
|
209
209
|
TypeError: If image is not an Image instance
|
|
@@ -221,12 +221,12 @@ def apply_bilateral_blur(
|
|
|
221
221
|
|
|
222
222
|
# Apply bilateral filter
|
|
223
223
|
blurred = cv2.bilateralFilter(
|
|
224
|
-
image._data
|
|
224
|
+
image._data,
|
|
225
225
|
filter_size,
|
|
226
226
|
sigma_color,
|
|
227
227
|
sigma_space
|
|
228
228
|
)
|
|
229
|
-
|
|
229
|
+
|
|
230
230
|
return Result(
|
|
231
231
|
image=blurred,
|
|
232
232
|
meta={
|
|
@@ -13,14 +13,11 @@ import cv2
|
|
|
13
13
|
|
|
14
14
|
from ImagePRO.utils.image import Image
|
|
15
15
|
from ImagePRO.utils.result import Result
|
|
16
|
-
from ImagePRO.pre_processing.grayscale import convert_to_grayscale
|
|
17
16
|
|
|
18
17
|
|
|
19
18
|
# Constants for contrast enhancement
|
|
20
19
|
DEFAULT_CLIP_LIMIT = 2.0 # For CLAHE
|
|
21
20
|
DEFAULT_TILE_GRID_SIZE = (8, 8) # For CLAHE
|
|
22
|
-
DEFAULT_ALPHA = 1.5 # For linear stretching
|
|
23
|
-
DEFAULT_BETA = 10 # For linear stretching
|
|
24
21
|
|
|
25
22
|
|
|
26
23
|
def apply_clahe_contrast(
|
|
@@ -65,8 +62,8 @@ def apply_clahe_contrast(
|
|
|
65
62
|
):
|
|
66
63
|
raise TypeError("'tile_grid_size' must be a tuple of two positive integers")
|
|
67
64
|
|
|
68
|
-
# Convert and apply CLAHE
|
|
69
|
-
grayscale =
|
|
65
|
+
# Convert and apply CLAHE (cvtColor returns a new array; input untouched)
|
|
66
|
+
grayscale = cv2.cvtColor(image._data, cv2.COLOR_BGR2GRAY)
|
|
70
67
|
clahe = cv2.createCLAHE(clipLimit=clip_limit, tileGridSize=tile_grid_size)
|
|
71
68
|
enhanced = clahe.apply(grayscale)
|
|
72
69
|
|
|
@@ -105,7 +102,7 @@ def apply_histogram_equalization(
|
|
|
105
102
|
raise TypeError("'image' must be an Image instance")
|
|
106
103
|
|
|
107
104
|
# Convert and apply global histogram equalization
|
|
108
|
-
grayscale =
|
|
105
|
+
grayscale = cv2.cvtColor(image._data, cv2.COLOR_BGR2GRAY)
|
|
109
106
|
enhanced = cv2.equalizeHist(grayscale)
|
|
110
107
|
|
|
111
108
|
return Result(
|
|
@@ -156,7 +153,7 @@ def apply_contrast_stretching(
|
|
|
156
153
|
raise ValueError("'beta' must be an integer between 0 and 255")
|
|
157
154
|
|
|
158
155
|
# Convert and apply linear stretching
|
|
159
|
-
grayscale =
|
|
156
|
+
grayscale = cv2.cvtColor(image._data, cv2.COLOR_BGR2GRAY)
|
|
160
157
|
enhanced = cv2.convertScaleAbs(grayscale, alpha=alpha, beta=beta)
|
|
161
158
|
|
|
162
159
|
return Result(
|
ImagePRO/pre_processing/crop.py
CHANGED
|
@@ -12,10 +12,6 @@ if str(_src_path) not in sys.path:
|
|
|
12
12
|
from ImagePRO.utils.image import Image
|
|
13
13
|
from ImagePRO.utils.result import Result
|
|
14
14
|
|
|
15
|
-
# Constants
|
|
16
|
-
DEFAULT_START_POINT = (0, 0)
|
|
17
|
-
DEFAULT_END_POINT = (100, 100)
|
|
18
|
-
|
|
19
15
|
|
|
20
16
|
def crop_image(
|
|
21
17
|
image: Image,
|
|
@@ -59,7 +55,10 @@ def crop_image(
|
|
|
59
55
|
x2, y2 = end_point
|
|
60
56
|
|
|
61
57
|
if x1 < 0 or y1 < 0 or x2 <= x1 or y2 <= y1:
|
|
62
|
-
raise ValueError(
|
|
58
|
+
raise ValueError(
|
|
59
|
+
"Invalid crop coordinates: ensure (x1, y1) is top-left "
|
|
60
|
+
"and (x2, y2) is bottom-right"
|
|
61
|
+
)
|
|
63
62
|
|
|
64
63
|
height, width = image.shape[:2]
|
|
65
64
|
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import random
|
|
3
4
|
import sys
|
|
5
|
+
import time
|
|
4
6
|
from pathlib import Path
|
|
5
7
|
|
|
6
8
|
# Add src directory to path for absolute imports
|
|
@@ -9,16 +11,12 @@ _src_path = _file_path.parents[2] # Go up to src directory
|
|
|
9
11
|
if str(_src_path) not in sys.path:
|
|
10
12
|
sys.path.insert(0, str(_src_path))
|
|
11
13
|
|
|
14
|
+
import cv2
|
|
15
|
+
|
|
12
16
|
from ImagePRO.human_analysis.face_analysis.face_detection import detect_faces
|
|
13
|
-
from ImagePRO.pre_processing import blur, grayscale, rotate, sharpen
|
|
17
|
+
from ImagePRO.pre_processing import blur, grayscale, resize, rotate, sharpen
|
|
14
18
|
from ImagePRO.utils.image import Image
|
|
15
19
|
|
|
16
|
-
import random
|
|
17
|
-
import time
|
|
18
|
-
|
|
19
|
-
import cv2
|
|
20
|
-
import mediapipe as mp
|
|
21
|
-
|
|
22
20
|
# Constants
|
|
23
21
|
DEFAULT_NUM_IMAGES = 200
|
|
24
22
|
DEFAULT_START_INDEX = 0
|
|
@@ -40,7 +38,7 @@ def capture_bulk_pictures(
|
|
|
40
38
|
apply_grayscale: bool = False,
|
|
41
39
|
apply_sharpen: bool = False,
|
|
42
40
|
apply_rotate: bool = False,
|
|
43
|
-
apply_resize: tuple = False,
|
|
41
|
+
apply_resize: tuple[int, int] | bool = False,
|
|
44
42
|
delay: float = DEFAULT_DELAY
|
|
45
43
|
) -> None:
|
|
46
44
|
"""Generate a dataset by capturing faces from webcam.
|
|
@@ -50,7 +48,7 @@ def capture_bulk_pictures(
|
|
|
50
48
|
face recognition datasets with data augmentation.
|
|
51
49
|
|
|
52
50
|
Processing pipeline (if enabled):
|
|
53
|
-
|
|
51
|
+
median blur → laplacian sharpen → grayscale → resize → random rotate
|
|
54
52
|
|
|
55
53
|
Args:
|
|
56
54
|
folder_path: Base directory for dataset.
|
|
@@ -96,6 +94,14 @@ def capture_bulk_pictures(
|
|
|
96
94
|
if not isinstance(delay, (int, float)) or delay < 0:
|
|
97
95
|
raise ValueError("'delay' must be a non-negative number")
|
|
98
96
|
|
|
97
|
+
try:
|
|
98
|
+
import mediapipe as mp
|
|
99
|
+
except ImportError as err:
|
|
100
|
+
raise ImportError(
|
|
101
|
+
"The optional 'mediapipe' dependency is required for dataset "
|
|
102
|
+
'generation. Install it with: pip install "ImagePRO-Python[mediapipe]"'
|
|
103
|
+
) from err
|
|
104
|
+
|
|
99
105
|
# Setup output directory structure
|
|
100
106
|
base_dir = Path(folder_path)
|
|
101
107
|
face_folder = base_dir / str(face_id)
|
|
@@ -135,7 +141,7 @@ def capture_bulk_pictures(
|
|
|
135
141
|
|
|
136
142
|
if apply_blur:
|
|
137
143
|
# Reduce noise while preserving facial features
|
|
138
|
-
processed = blur.
|
|
144
|
+
processed = blur.apply_median_blur(
|
|
139
145
|
image=Image.from_array(processed),
|
|
140
146
|
filter_size=3
|
|
141
147
|
).image
|
|
@@ -15,73 +15,61 @@ import matplotlib.pyplot as plt
|
|
|
15
15
|
from ImagePRO.utils.image import Image
|
|
16
16
|
from ImagePRO.utils.result import Result
|
|
17
17
|
|
|
18
|
+
|
|
18
19
|
def show_histogram(image: Image) -> Result:
|
|
19
20
|
"""
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
This function
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
Builds a matplotlib histogram figure for an image.
|
|
22
|
+
|
|
23
|
+
This function creates the histogram plot of the intensity distribution of
|
|
24
|
+
an image. It handles both grayscale and color images, with automatic
|
|
25
|
+
detection of image type. For color images, it plots a histogram for each
|
|
26
|
+
color channel (BGR or RGB) with appropriate colors. The pyplot module is
|
|
27
|
+
returned in ``data``; call ``matplotlib.pyplot.show()`` to display the
|
|
28
|
+
figure.
|
|
25
29
|
|
|
26
30
|
Args:
|
|
27
31
|
image (Image):
|
|
28
|
-
Input image to
|
|
32
|
+
Input image to analyze. Must be BGR, RGB or Grayscale format.
|
|
29
33
|
|
|
30
34
|
Returns:
|
|
31
35
|
Result: Result object with histogram plot.
|
|
32
|
-
- image
|
|
33
|
-
- data
|
|
36
|
+
- image: None
|
|
37
|
+
- data: The matplotlib.pyplot module (figure is created, not shown)
|
|
34
38
|
- meta (dict): Contains source object and operation info
|
|
35
39
|
|
|
36
40
|
Raises:
|
|
37
41
|
TypeError: If image is not an Image instance
|
|
38
|
-
ValueError: If image colorspace is not
|
|
42
|
+
ValueError: If image colorspace is not supported.
|
|
39
43
|
"""
|
|
40
44
|
if not isinstance(image, Image):
|
|
41
45
|
raise TypeError("'image' must be an Image instance.")
|
|
42
46
|
|
|
43
|
-
if image.colorspace
|
|
44
|
-
|
|
45
|
-
|
|
47
|
+
if image.colorspace in ("BGR", "RGB"):
|
|
48
|
+
if image.colorspace == "BGR":
|
|
49
|
+
colors = ("blue", "green", "red")
|
|
50
|
+
labels = ("Blue Channel", "Green Channel", "Red Channel")
|
|
51
|
+
else:
|
|
52
|
+
colors = ("red", "green", "blue")
|
|
53
|
+
labels = ("Red Channel", "Green Channel", "Blue Channel")
|
|
54
|
+
channels = list(enumerate(colors))
|
|
55
|
+
elif image.colorspace == "GRAY":
|
|
56
|
+
channels = [(0, "black")]
|
|
57
|
+
labels = None
|
|
58
|
+
else:
|
|
59
|
+
raise ValueError(f"Unknown colorspace: {image.colorspace}")
|
|
46
60
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
plt.title('Histogram of BGR Channels')
|
|
54
|
-
plt.xlabel('Pixel Intensity')
|
|
55
|
-
plt.ylabel('Frequency')
|
|
56
|
-
plt.legend(['Blue Channel', 'Green Channel', 'Red Channel'])
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
elif image.colorspace == "RGB":
|
|
60
|
-
colors = ('red', 'green', 'blue')
|
|
61
|
-
plt.figure(figsize=(10, 6))
|
|
61
|
+
plt.figure(figsize=(10, 6))
|
|
62
|
+
for channel, color in channels:
|
|
63
|
+
hist = cv2.calcHist([image._data], [channel], None, [256], [0, 256])
|
|
64
|
+
plt.plot(hist, color=color)
|
|
62
65
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
plt.title('Histogram of RGB Channels')
|
|
70
|
-
plt.xlabel('Pixel Intensity')
|
|
71
|
-
plt.ylabel('Frequency')
|
|
72
|
-
plt.legend(['Red Channel', 'Green Channel', 'Blue Channel'])
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
elif image.colorspace == 'GRAY':
|
|
76
|
-
plt.figure(figsize=(10, 6))
|
|
66
|
+
if labels is not None:
|
|
67
|
+
plt.title(f"Histogram of {image.colorspace} Channels")
|
|
68
|
+
plt.legend(labels)
|
|
69
|
+
plt.xlabel("Pixel Intensity")
|
|
70
|
+
plt.ylabel("Frequency")
|
|
77
71
|
|
|
78
|
-
|
|
79
|
-
plt.plot(hist, color='black')
|
|
80
|
-
plt.xlim([0, 256])
|
|
81
|
-
|
|
82
|
-
else:
|
|
83
|
-
return ValueError('Unknown colorspace')
|
|
84
|
-
|
|
72
|
+
plt.xlim([0, 256])
|
|
85
73
|
|
|
86
74
|
return Result(
|
|
87
75
|
image=None,
|
|
@@ -22,7 +22,7 @@ def resize_image(
|
|
|
22
22
|
) -> Result:
|
|
23
23
|
"""Resize an image to specified dimensions.
|
|
24
24
|
|
|
25
|
-
Changes image size
|
|
25
|
+
Changes image size to the exact (width, height) given.
|
|
26
26
|
Uses bilinear interpolation for smooth resizing.
|
|
27
27
|
|
|
28
28
|
Args:
|
|
@@ -52,7 +52,7 @@ def resize_image(
|
|
|
52
52
|
|
|
53
53
|
# Resize using bilinear interpolation
|
|
54
54
|
resized = cv2.resize(image._data, new_size, interpolation=cv2.INTER_LINEAR)
|
|
55
|
-
|
|
55
|
+
|
|
56
56
|
return Result(
|
|
57
57
|
image=resized,
|
|
58
58
|
meta={
|
|
@@ -10,7 +10,6 @@ if str(_src_path) not in sys.path:
|
|
|
10
10
|
sys.path.insert(0, str(_src_path))
|
|
11
11
|
|
|
12
12
|
import cv2
|
|
13
|
-
import numpy as np
|
|
14
13
|
|
|
15
14
|
from ImagePRO.utils.image import Image
|
|
16
15
|
from ImagePRO.utils.result import Result
|
|
@@ -18,7 +17,6 @@ from ImagePRO.utils.result import Result
|
|
|
18
17
|
|
|
19
18
|
# Constants for rotation operations
|
|
20
19
|
DEFAULT_SCALE = 1.0
|
|
21
|
-
DEFAULT_ANGLE = 45.0
|
|
22
20
|
|
|
23
21
|
|
|
24
22
|
def rotate_image_90(image: Image) -> Result:
|
|
@@ -39,7 +37,7 @@ def rotate_image_90(image: Image) -> Result:
|
|
|
39
37
|
if not isinstance(image, Image):
|
|
40
38
|
raise TypeError("'image' must be an Image instance")
|
|
41
39
|
|
|
42
|
-
rotated = cv2.rotate(image._data
|
|
40
|
+
rotated = cv2.rotate(image._data, cv2.ROTATE_90_CLOCKWISE)
|
|
43
41
|
return Result(
|
|
44
42
|
image=rotated,
|
|
45
43
|
meta={
|
|
@@ -68,7 +66,7 @@ def rotate_image_180(image: Image) -> Result:
|
|
|
68
66
|
if not isinstance(image, Image):
|
|
69
67
|
raise TypeError("'image' must be an Image instance")
|
|
70
68
|
|
|
71
|
-
rotated = cv2.rotate(image._data
|
|
69
|
+
rotated = cv2.rotate(image._data, cv2.ROTATE_180)
|
|
72
70
|
return Result(
|
|
73
71
|
image=rotated,
|
|
74
72
|
meta={
|
|
@@ -97,7 +95,7 @@ def rotate_image_270(image: Image) -> Result:
|
|
|
97
95
|
if not isinstance(image, Image):
|
|
98
96
|
raise TypeError("'image' must be an Image instance")
|
|
99
97
|
|
|
100
|
-
rotated = cv2.rotate(image._data
|
|
98
|
+
rotated = cv2.rotate(image._data, cv2.ROTATE_90_COUNTERCLOCKWISE)
|
|
101
99
|
return Result(
|
|
102
100
|
image=rotated,
|
|
103
101
|
meta={
|
|
@@ -117,7 +115,8 @@ def rotate_image_custom(
|
|
|
117
115
|
"""Rotate image by custom angle with optional scaling.
|
|
118
116
|
|
|
119
117
|
Rotates around image center. Positive angles are counter-clockwise.
|
|
120
|
-
|
|
118
|
+
The output canvas keeps the original image dimensions, so corners of
|
|
119
|
+
the rotated image may be clipped.
|
|
121
120
|
|
|
122
121
|
Args:
|
|
123
122
|
image: Input image to rotate.
|
|
@@ -144,7 +143,7 @@ def rotate_image_custom(
|
|
|
144
143
|
|
|
145
144
|
h, w = image.shape[:2]
|
|
146
145
|
matrix = cv2.getRotationMatrix2D((w/2, h/2), angle, scale)
|
|
147
|
-
rotated = cv2.warpAffine(image._data
|
|
146
|
+
rotated = cv2.warpAffine(image._data, matrix, (w, h))
|
|
148
147
|
|
|
149
148
|
return Result(
|
|
150
149
|
image=rotated,
|
|
@@ -14,7 +14,7 @@ import numpy as np
|
|
|
14
14
|
|
|
15
15
|
from ImagePRO.utils.image import Image
|
|
16
16
|
from ImagePRO.utils.result import Result
|
|
17
|
-
from ImagePRO.pre_processing.blur import
|
|
17
|
+
from ImagePRO.pre_processing.blur import DEFAULT_KERNEL_SIZE
|
|
18
18
|
|
|
19
19
|
# Constants
|
|
20
20
|
DEFAULT_LAPLACIAN_COEFFICIENT = 3.0
|
|
@@ -52,13 +52,16 @@ def apply_laplacian_sharpening(
|
|
|
52
52
|
if not isinstance(coefficient, (int, float)) or coefficient < 0:
|
|
53
53
|
raise ValueError("'coefficient' must be a non-negative number")
|
|
54
54
|
|
|
55
|
-
# Apply Laplacian edge detection
|
|
56
|
-
|
|
57
|
-
laplacian =
|
|
55
|
+
# Apply Laplacian edge detection, then enhance edges while keeping
|
|
56
|
+
# float arithmetic identical to: image + coefficient * |laplacian|
|
|
57
|
+
laplacian = cv2.Laplacian(image._data, cv2.CV_64F)
|
|
58
|
+
np.absolute(laplacian, out=laplacian)
|
|
59
|
+
laplacian = laplacian.astype(np.uint8)
|
|
58
60
|
|
|
59
|
-
|
|
60
|
-
sharpened
|
|
61
|
-
|
|
61
|
+
sharpened = laplacian * coefficient
|
|
62
|
+
sharpened += image._data
|
|
63
|
+
np.clip(sharpened, 0, 255, out=sharpened)
|
|
64
|
+
sharpened = sharpened.astype(np.uint8)
|
|
62
65
|
|
|
63
66
|
return Result(
|
|
64
67
|
image=sharpened,
|
|
@@ -102,15 +105,15 @@ def apply_unsharp_masking(
|
|
|
102
105
|
raise ValueError("'coefficient' must be a non-negative number")
|
|
103
106
|
|
|
104
107
|
# Create the mask from original vs blurred difference
|
|
105
|
-
blurred =
|
|
108
|
+
blurred = cv2.blur(image._data, DEFAULT_KERNEL_SIZE)
|
|
106
109
|
mask = cv2.subtract(image._data, blurred)
|
|
107
|
-
|
|
110
|
+
|
|
108
111
|
# Apply unsharp masking
|
|
109
112
|
sharpened = cv2.addWeighted(
|
|
110
|
-
image._data,
|
|
111
|
-
1 + coefficient,
|
|
112
|
-
mask,
|
|
113
|
-
-coefficient,
|
|
113
|
+
image._data,
|
|
114
|
+
1 + coefficient,
|
|
115
|
+
mask,
|
|
116
|
+
-coefficient,
|
|
114
117
|
0
|
|
115
118
|
)
|
|
116
119
|
|
ImagePRO/utils/image.py
CHANGED
|
@@ -18,8 +18,9 @@ class Image:
|
|
|
18
18
|
"""
|
|
19
19
|
Lightweight, immutable image wrapper for ImagePRO.
|
|
20
20
|
|
|
21
|
-
Use factory constructors to create instances
|
|
22
|
-
|
|
21
|
+
Use factory constructors to create instances; instances are lightweight
|
|
22
|
+
wrappers and are not modified by ImagePRO operations. Images are handled
|
|
23
|
+
in BGR format by default to match OpenCV conventions.
|
|
23
24
|
|
|
24
25
|
Attributes:
|
|
25
26
|
_data (np.ndarray):
|
|
@@ -35,7 +36,7 @@ class Image:
|
|
|
35
36
|
>>> # Load from file (default BGR)
|
|
36
37
|
>>> img = Image.from_path('input.jpg', colorspace="BGR")
|
|
37
38
|
>>> print(img.shape) # (H, W, 3)
|
|
38
|
-
>>>
|
|
39
|
+
>>>
|
|
39
40
|
>>> # Or wrap a NumPy array
|
|
40
41
|
>>> img = Image.from_array(np_array, colorspace="RGB")
|
|
41
42
|
"""
|
|
@@ -47,7 +48,7 @@ class Image:
|
|
|
47
48
|
|
|
48
49
|
@classmethod
|
|
49
50
|
def from_path(
|
|
50
|
-
cls,
|
|
51
|
+
cls,
|
|
51
52
|
path: str | Path,
|
|
52
53
|
colorspace: Colorspace = "BGR"
|
|
53
54
|
) -> Image:
|
|
@@ -58,7 +59,8 @@ class Image:
|
|
|
58
59
|
path (str | Path):
|
|
59
60
|
Path to the image file.
|
|
60
61
|
colorspace (Colorspace, optional):
|
|
61
|
-
Colorspace of the image array in that path
|
|
62
|
+
Colorspace of the image array in that path
|
|
63
|
+
("BGR", "RGB", or "GRAY"). Defaults to "BGR".
|
|
62
64
|
|
|
63
65
|
Returns:
|
|
64
66
|
Image: New Image instance loaded from disk.
|
|
@@ -124,4 +126,3 @@ class Image:
|
|
|
124
126
|
np.dtype: Data type of the image array.
|
|
125
127
|
"""
|
|
126
128
|
return self._data.dtype
|
|
127
|
-
|
ImagePRO/utils/result.py
CHANGED
|
@@ -143,22 +143,25 @@ class Result:
|
|
|
143
143
|
raise IOError(f"Failed to write CSV to {out_path}: {exc}") from exc
|
|
144
144
|
return self
|
|
145
145
|
|
|
146
|
-
|
|
146
|
+
|
|
147
|
+
def flatten_rows(data: Any) -> list[list[Any]]:
|
|
147
148
|
"""
|
|
148
149
|
Flatten nested lists/tuples into rows suitable for CSV writing.
|
|
149
|
-
|
|
150
|
+
|
|
150
151
|
Recursively processes nested data structures to create a flat list of rows
|
|
151
152
|
that can be written to a CSV file. Handles mixed nesting levels gracefully.
|
|
152
|
-
|
|
153
|
+
|
|
153
154
|
Args:
|
|
154
155
|
data: Nested list/tuple structure containing data to flatten.
|
|
155
|
-
|
|
156
|
+
|
|
156
157
|
Returns:
|
|
157
158
|
list[list[Any]]: Flattened list of rows, where each row is a list of values.
|
|
158
|
-
|
|
159
|
+
|
|
159
160
|
Example:
|
|
160
|
-
>>> flatten_rows([[1, 2], [3,
|
|
161
|
-
[[1, 2], [3, 4
|
|
161
|
+
>>> flatten_rows([[1, 2], [3, 4]])
|
|
162
|
+
[[1, 2], [3, 4]]
|
|
163
|
+
>>> flatten_rows([[[1, 2], [3, 4]], [5]])
|
|
164
|
+
[[1, 2], [3, 4], [5]]
|
|
162
165
|
"""
|
|
163
166
|
rows = []
|
|
164
167
|
for item in data:
|