ImagePRO-Python 1.2.1__py3-none-any.whl → 1.3.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.
- ImagePRO/__init__.py +2 -2
- ImagePRO/human_analysis/body_analysis/__init__.py +12 -2
- ImagePRO/human_analysis/body_analysis/body_pose_estimation.py +3 -2
- ImagePRO/human_analysis/body_analysis/hand_tracking.py +5 -3
- ImagePRO/human_analysis/face_analysis/__init__.py +29 -6
- ImagePRO/human_analysis/face_analysis/face_comparison.py +2 -2
- ImagePRO/human_analysis/face_analysis/face_detection.py +1 -1
- ImagePRO/human_analysis/face_analysis/face_mesh_analysis.py +11 -12
- ImagePRO/human_analysis/face_analysis/head_pose_estimation.py +1 -1
- ImagePRO/object_analysis/__init__.py +4 -1
- ImagePRO/object_analysis/object_detection.py +6 -6
- ImagePRO/pre_processing/__init__.py +6 -2
- 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 +9 -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.1.dist-info → imagepro_python-1.3.0.dist-info}/METADATA +43 -18
- imagepro_python-1.3.0.dist-info/RECORD +31 -0
- {imagepro_python-1.2.1.dist-info → imagepro_python-1.3.0.dist-info}/WHEEL +1 -1
- imagepro_python-1.2.1.dist-info/RECORD +0 -31
- {imagepro_python-1.2.1.dist-info → imagepro_python-1.3.0.dist-info}/licenses/LICENSE +0 -0
- {imagepro_python-1.2.1.dist-info → imagepro_python-1.3.0.dist-info}/top_level.txt +0 -0
ImagePRO/__init__.py
CHANGED
|
@@ -15,7 +15,7 @@ Features:
|
|
|
15
15
|
- Real-time processing capabilities
|
|
16
16
|
"""
|
|
17
17
|
|
|
18
|
-
__version__ = "1.
|
|
18
|
+
__version__ = "1.3.0"
|
|
19
19
|
__author__ = "Parsa Safaie"
|
|
20
20
|
__email__ = "parsasafaie.2568@proton.me"
|
|
21
21
|
|
|
@@ -27,7 +27,7 @@ from . import object_analysis
|
|
|
27
27
|
|
|
28
28
|
__all__ = [
|
|
29
29
|
"utils",
|
|
30
|
-
"pre_processing",
|
|
30
|
+
"pre_processing",
|
|
31
31
|
"human_analysis",
|
|
32
32
|
"object_analysis"
|
|
33
33
|
]
|
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
# Body Analysis Module
|
|
2
2
|
# Provides body pose estimation and hand tracking capabilities
|
|
3
|
+
#
|
|
4
|
+
# Both modules need the optional mediapipe dependency; they are imported
|
|
5
|
+
# guardedly so the package stays importable without it.
|
|
3
6
|
|
|
4
|
-
|
|
5
|
-
from . import
|
|
7
|
+
try: # requires mediapipe
|
|
8
|
+
from . import body_pose_estimation
|
|
9
|
+
except ImportError:
|
|
10
|
+
body_pose_estimation = None
|
|
11
|
+
|
|
12
|
+
try: # requires mediapipe
|
|
13
|
+
from . import hand_tracking
|
|
14
|
+
except ImportError:
|
|
15
|
+
hand_tracking = None
|
|
6
16
|
|
|
7
17
|
__all__ = ["body_pose_estimation", "hand_tracking"]
|
|
@@ -64,7 +64,7 @@ def detect_body_pose(
|
|
|
64
64
|
raise ValueError("'min_confidence' must be between 0.0 and 1.0")
|
|
65
65
|
|
|
66
66
|
if landmarks_idx is not None and (
|
|
67
|
-
not isinstance(landmarks_idx, list)
|
|
67
|
+
not isinstance(landmarks_idx, list)
|
|
68
68
|
or not all(isinstance(i, int) for i in landmarks_idx)
|
|
69
69
|
):
|
|
70
70
|
raise TypeError("'landmarks_idx' must be a list of integers")
|
|
@@ -97,7 +97,8 @@ def detect_body_pose(
|
|
|
97
97
|
img_copy,
|
|
98
98
|
result.pose_landmarks,
|
|
99
99
|
mp_pose.POSE_CONNECTIONS,
|
|
100
|
-
landmark_drawing_spec=mp.solutions.drawing_styles
|
|
100
|
+
landmark_drawing_spec=mp.solutions.drawing_styles
|
|
101
|
+
.get_default_pose_landmarks_style()
|
|
101
102
|
)
|
|
102
103
|
else:
|
|
103
104
|
# Draw only selected points
|
|
@@ -74,7 +74,7 @@ def detect_hands(
|
|
|
74
74
|
raise ValueError("'min_confidence' must be between 0.0 and 1.0")
|
|
75
75
|
|
|
76
76
|
if landmarks_idx is not None and (
|
|
77
|
-
not isinstance(landmarks_idx, list)
|
|
77
|
+
not isinstance(landmarks_idx, list)
|
|
78
78
|
or not all(isinstance(i, int) for i in landmarks_idx)
|
|
79
79
|
):
|
|
80
80
|
raise TypeError("'landmarks_idx' must be a list of integers")
|
|
@@ -109,8 +109,10 @@ def detect_hands(
|
|
|
109
109
|
image=img_copy,
|
|
110
110
|
landmark_list=hand_landmarks,
|
|
111
111
|
connections=mp_hands.HAND_CONNECTIONS,
|
|
112
|
-
landmark_drawing_spec=mp.solutions.drawing_styles
|
|
113
|
-
|
|
112
|
+
landmark_drawing_spec=mp.solutions.drawing_styles
|
|
113
|
+
.get_default_hand_landmarks_style(),
|
|
114
|
+
connection_drawing_spec=mp.solutions.drawing_styles
|
|
115
|
+
.get_default_hand_connections_style()
|
|
114
116
|
)
|
|
115
117
|
else:
|
|
116
118
|
# Draw only selected points
|
|
@@ -1,16 +1,39 @@
|
|
|
1
1
|
# Face Analysis Module
|
|
2
2
|
# Provides facial landmark detection, pose estimation, and eye status analysis
|
|
3
|
+
#
|
|
4
|
+
# The mediapipe-based modules and the insightface-based module are imported
|
|
5
|
+
# guardedly so that importing the package works with only the dependencies
|
|
6
|
+
# a caller actually needs; unavailable modules are set to None.
|
|
3
7
|
|
|
4
|
-
|
|
5
|
-
from . import
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
8
|
+
try: # requires mediapipe
|
|
9
|
+
from . import eye_status_analysis
|
|
10
|
+
except ImportError:
|
|
11
|
+
eye_status_analysis = None
|
|
12
|
+
|
|
13
|
+
try: # requires insightface
|
|
14
|
+
from . import face_comparison
|
|
15
|
+
except ImportError:
|
|
16
|
+
face_comparison = None
|
|
17
|
+
|
|
18
|
+
try: # requires mediapipe
|
|
19
|
+
from . import face_detection
|
|
20
|
+
except ImportError:
|
|
21
|
+
face_detection = None
|
|
22
|
+
|
|
23
|
+
try: # requires mediapipe
|
|
24
|
+
from . import face_mesh_analysis
|
|
25
|
+
except ImportError:
|
|
26
|
+
face_mesh_analysis = None
|
|
27
|
+
|
|
28
|
+
try: # requires mediapipe
|
|
29
|
+
from . import head_pose_estimation
|
|
30
|
+
except ImportError:
|
|
31
|
+
head_pose_estimation = None
|
|
9
32
|
|
|
10
33
|
__all__ = [
|
|
11
34
|
"eye_status_analysis",
|
|
12
35
|
"face_comparison",
|
|
13
|
-
"face_detection",
|
|
36
|
+
"face_detection",
|
|
14
37
|
"face_mesh_analysis",
|
|
15
38
|
"head_pose_estimation"
|
|
16
39
|
]
|
|
@@ -85,8 +85,8 @@ def compare_faces(
|
|
|
85
85
|
|
|
86
86
|
try:
|
|
87
87
|
# Save images to disk if needed
|
|
88
|
-
path_1 = save_temp_image(image_1
|
|
89
|
-
path_2 = save_temp_image(image_2
|
|
88
|
+
path_1 = save_temp_image(image_1, 'tmp1.jpg')
|
|
89
|
+
path_2 = save_temp_image(image_2, 'tmp2.jpg')
|
|
90
90
|
|
|
91
91
|
# Load and preprocess images
|
|
92
92
|
img1 = load_rgb_image(path_1)
|
|
@@ -83,7 +83,7 @@ def analyze_face_mesh(
|
|
|
83
83
|
raise ValueError("'min_confidence' must be between 0 and 1")
|
|
84
84
|
|
|
85
85
|
if landmarks_idx is not None and (
|
|
86
|
-
not isinstance(landmarks_idx, list)
|
|
86
|
+
not isinstance(landmarks_idx, list)
|
|
87
87
|
or not all(isinstance(i, int) for i in landmarks_idx)
|
|
88
88
|
):
|
|
89
89
|
raise TypeError("'landmarks_idx' must be a list of integers")
|
|
@@ -99,11 +99,8 @@ def analyze_face_mesh(
|
|
|
99
99
|
else:
|
|
100
100
|
face_mesh = face_mesh_obj
|
|
101
101
|
|
|
102
|
-
#
|
|
103
|
-
|
|
104
|
-
img_rgb = cv2.cvtColor(img_copy, cv2.COLOR_BGR2RGB)
|
|
105
|
-
|
|
106
|
-
# Detect facial landmarks
|
|
102
|
+
# Detect facial landmarks (cvtColor allocates a new RGB array)
|
|
103
|
+
img_rgb = cv2.cvtColor(image._data, cv2.COLOR_BGR2RGB)
|
|
107
104
|
results = face_mesh.process(img_rgb)
|
|
108
105
|
|
|
109
106
|
# Handle no detections
|
|
@@ -121,11 +118,14 @@ def analyze_face_mesh(
|
|
|
121
118
|
}
|
|
122
119
|
)
|
|
123
120
|
|
|
121
|
+
# Allocate the annotation canvas only when a face was found
|
|
122
|
+
img_copy = image._data.copy()
|
|
123
|
+
|
|
124
124
|
# Use all landmarks if none specified
|
|
125
125
|
landmarks_idx = landmarks_idx or list(range(TOTAL_FACE_LANDMARKS))
|
|
126
126
|
# Process each detected face
|
|
127
127
|
landmarks = []
|
|
128
|
-
|
|
128
|
+
|
|
129
129
|
for face_id, face_landmarks in enumerate(results.multi_face_landmarks):
|
|
130
130
|
# Draw landmarks
|
|
131
131
|
if len(landmarks_idx) == TOTAL_FACE_LANDMARKS:
|
|
@@ -146,11 +146,10 @@ def analyze_face_mesh(
|
|
|
146
146
|
cv2.circle(img_copy, (cx, cy), 3, (0, 0, 255), -1)
|
|
147
147
|
|
|
148
148
|
# Extract coordinates
|
|
149
|
-
face_data = [
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
]
|
|
149
|
+
face_data = []
|
|
150
|
+
for idx in landmarks_idx:
|
|
151
|
+
lm = face_landmarks.landmark[idx]
|
|
152
|
+
face_data.append([face_id, idx, lm.x, lm.y, lm.z])
|
|
154
153
|
landmarks.append(face_data)
|
|
155
154
|
|
|
156
155
|
return Result(
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
# Object Analysis Module
|
|
2
2
|
# Provides object detection capabilities using YOLO models
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
try: # object_detection needs the optional ultralytics dependency
|
|
5
|
+
from . import object_detection
|
|
6
|
+
except ImportError:
|
|
7
|
+
object_detection = None
|
|
5
8
|
|
|
6
9
|
__all__ = ["object_detection"]
|
|
@@ -16,10 +16,9 @@ from ImagePRO.utils.result import Result
|
|
|
16
16
|
|
|
17
17
|
# Constants
|
|
18
18
|
DEFAULT_ACCURACY_LEVEL = 1
|
|
19
|
-
DEFAULT_CONFIDENCE = 0.5
|
|
20
19
|
MODEL_MAPPING = {
|
|
21
20
|
1: "yolo11n.pt",
|
|
22
|
-
2: "yolo11s.pt",
|
|
21
|
+
2: "yolo11s.pt",
|
|
23
22
|
3: "yolo11m.pt",
|
|
24
23
|
4: "yolo11l.pt",
|
|
25
24
|
5: "yolo11x.pt"
|
|
@@ -56,6 +55,7 @@ def detect_objects(
|
|
|
56
55
|
Result object with detections and metadata:
|
|
57
56
|
- image: Original with bounding boxes drawn
|
|
58
57
|
- data: List of [class_id, [x1,y1,x2,y2], confidence]
|
|
58
|
+
with box coordinates normalized to [0, 1]
|
|
59
59
|
- meta: Operation info and model used
|
|
60
60
|
|
|
61
61
|
Raises:
|
|
@@ -73,12 +73,12 @@ def detect_objects(
|
|
|
73
73
|
f"'accuracy_level' must be in {list(MODEL_MAPPING.keys())}, "
|
|
74
74
|
f"got {accuracy_level}"
|
|
75
75
|
)
|
|
76
|
-
|
|
76
|
+
|
|
77
77
|
model_name = MODEL_MAPPING[accuracy_level]
|
|
78
78
|
model = YOLO(model=model_name)
|
|
79
79
|
|
|
80
|
-
# Run inference
|
|
81
|
-
result = model(image._data
|
|
80
|
+
# Run inference (ultralytics does not mutate the input array)
|
|
81
|
+
result = model(image._data)[0]
|
|
82
82
|
boxes = result.boxes
|
|
83
83
|
|
|
84
84
|
# Process detections
|
|
@@ -92,7 +92,7 @@ def detect_objects(
|
|
|
92
92
|
# Show visualization if requested
|
|
93
93
|
if show_result:
|
|
94
94
|
result.show()
|
|
95
|
-
|
|
95
|
+
|
|
96
96
|
return Result(
|
|
97
97
|
image=result.plot(),
|
|
98
98
|
data=detections,
|
|
@@ -4,15 +4,19 @@
|
|
|
4
4
|
from . import blur
|
|
5
5
|
from . import contrast
|
|
6
6
|
from . import crop
|
|
7
|
-
from . import dataset_generator
|
|
8
7
|
from . import grayscale
|
|
9
8
|
from . import resize
|
|
10
9
|
from . import rotate
|
|
11
10
|
from . import sharpen
|
|
12
11
|
|
|
12
|
+
try: # dataset_generator needs the optional mediapipe dependency
|
|
13
|
+
from . import dataset_generator
|
|
14
|
+
except ImportError:
|
|
15
|
+
dataset_generator = None
|
|
16
|
+
|
|
13
17
|
__all__ = [
|
|
14
18
|
"blur",
|
|
15
|
-
"contrast",
|
|
19
|
+
"contrast",
|
|
16
20
|
"crop",
|
|
17
21
|
"dataset_generator",
|
|
18
22
|
"grayscale",
|
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,13 @@ _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
|
|
|
12
|
-
from ImagePRO.human_analysis.face_analysis.face_detection import detect_faces
|
|
13
|
-
from ImagePRO.pre_processing import blur, grayscale, rotate, sharpen, resize
|
|
14
|
-
from ImagePRO.utils.image import Image
|
|
15
|
-
|
|
16
|
-
import random
|
|
17
|
-
import time
|
|
18
|
-
|
|
19
14
|
import cv2
|
|
20
15
|
import mediapipe as mp
|
|
21
16
|
|
|
17
|
+
from ImagePRO.human_analysis.face_analysis.face_detection import detect_faces
|
|
18
|
+
from ImagePRO.pre_processing import blur, grayscale, resize, rotate, sharpen
|
|
19
|
+
from ImagePRO.utils.image import Image
|
|
20
|
+
|
|
22
21
|
# Constants
|
|
23
22
|
DEFAULT_NUM_IMAGES = 200
|
|
24
23
|
DEFAULT_START_INDEX = 0
|
|
@@ -40,7 +39,7 @@ def capture_bulk_pictures(
|
|
|
40
39
|
apply_grayscale: bool = False,
|
|
41
40
|
apply_sharpen: bool = False,
|
|
42
41
|
apply_rotate: bool = False,
|
|
43
|
-
apply_resize: tuple = False,
|
|
42
|
+
apply_resize: tuple[int, int] | bool = False,
|
|
44
43
|
delay: float = DEFAULT_DELAY
|
|
45
44
|
) -> None:
|
|
46
45
|
"""Generate a dataset by capturing faces from webcam.
|
|
@@ -50,7 +49,7 @@ def capture_bulk_pictures(
|
|
|
50
49
|
face recognition datasets with data augmentation.
|
|
51
50
|
|
|
52
51
|
Processing pipeline (if enabled):
|
|
53
|
-
|
|
52
|
+
median blur → laplacian sharpen → grayscale → resize → random rotate
|
|
54
53
|
|
|
55
54
|
Args:
|
|
56
55
|
folder_path: Base directory for dataset.
|
|
@@ -135,7 +134,7 @@ def capture_bulk_pictures(
|
|
|
135
134
|
|
|
136
135
|
if apply_blur:
|
|
137
136
|
# Reduce noise while preserving facial features
|
|
138
|
-
processed = blur.
|
|
137
|
+
processed = blur.apply_median_blur(
|
|
139
138
|
image=Image.from_array(processed),
|
|
140
139
|
filter_size=3
|
|
141
140
|
).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:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ImagePRO-Python
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.0
|
|
4
4
|
Summary: Professional & modular image processing library with OpenCV, MediaPipe, and YOLO integration.
|
|
5
5
|
Author-email: Parsa Safaie <parsasafaie.2568@proton.me>
|
|
6
6
|
License: MIT
|
|
@@ -32,14 +32,19 @@ Provides-Extra: yolo
|
|
|
32
32
|
Requires-Dist: ultralytics<9.0,>=8.2; extra == "yolo"
|
|
33
33
|
Provides-Extra: mediapipe
|
|
34
34
|
Requires-Dist: mediapipe<=0.10.11,>=0.10.0; extra == "mediapipe"
|
|
35
|
+
Requires-Dist: numpy<2.0,>=1.24; extra == "mediapipe"
|
|
35
36
|
Provides-Extra: insightface
|
|
36
37
|
Requires-Dist: insightface<0.8,>=0.7; extra == "insightface"
|
|
37
38
|
Requires-Dist: onnxruntime<2.0,>=1.16; extra == "insightface"
|
|
39
|
+
Requires-Dist: numpy<2.0,>=1.24; extra == "insightface"
|
|
38
40
|
Provides-Extra: full
|
|
39
41
|
Requires-Dist: mediapipe<=0.10.11,>=0.10.0; extra == "full"
|
|
40
42
|
Requires-Dist: ultralytics<9.0,>=8.2; extra == "full"
|
|
41
43
|
Requires-Dist: insightface<0.8,>=0.7; extra == "full"
|
|
42
44
|
Requires-Dist: onnxruntime<2.0,>=1.16; extra == "full"
|
|
45
|
+
Requires-Dist: numpy<2.0,>=1.24; extra == "full"
|
|
46
|
+
Provides-Extra: dev
|
|
47
|
+
Requires-Dist: pytest<9.0,>=8.0; extra == "dev"
|
|
43
48
|
Dynamic: license-file
|
|
44
49
|
|
|
45
50
|
# ImagePRO
|
|
@@ -50,11 +55,10 @@ Dynamic: license-file
|
|
|
50
55
|
|
|
51
56
|
Whether you're working on computer vision pipelines, preprocessing images for AI models, or simply automating batch image edits — **ImagePRO** gives you powerful tools with minimal effort.
|
|
52
57
|
|
|
53
|
-
##
|
|
58
|
+
## Features
|
|
54
59
|
|
|
55
60
|
### **Image I/O & Management**
|
|
56
61
|
- Flexible input/output handling (file paths or numpy arrays)
|
|
57
|
-
- Batch processing capabilities
|
|
58
62
|
- Multiple format support (JPEG, PNG, CSV, etc.)
|
|
59
63
|
|
|
60
64
|
### **Pre-processing & Enhancement**
|
|
@@ -71,13 +75,19 @@ Whether you're working on computer vision pipelines, preprocessing images for AI
|
|
|
71
75
|
### **Object Detection**
|
|
72
76
|
- **YOLO Integration**: Multiple accuracy levels (nano to extra-large)
|
|
73
77
|
- **Flexible Models**: Pre-trained or custom model support
|
|
74
|
-
- **Batch Processing**: Efficient handling of multiple images
|
|
75
78
|
|
|
76
|
-
##
|
|
79
|
+
## Installation
|
|
77
80
|
|
|
78
81
|
### From PyPI
|
|
79
82
|
```bash
|
|
80
83
|
pip install ImagePRO-Python
|
|
84
|
+
|
|
85
|
+
# Or with optional extras:
|
|
86
|
+
pip install "ImagePRO-Python[yolo]" # YOLO object detection
|
|
87
|
+
pip install "ImagePRO-Python[mediapipe]" # MediaPipe human analysis
|
|
88
|
+
pip install "ImagePRO-Python[insightface]" # InsightFace face comparison
|
|
89
|
+
pip install "ImagePRO-Python[full]" # Everything
|
|
90
|
+
pip install "ImagePRO-Python[dev]" # Test tooling (pytest)
|
|
81
91
|
```
|
|
82
92
|
|
|
83
93
|
### From Source
|
|
@@ -109,7 +119,7 @@ pip install -r requirements/full.txt
|
|
|
109
119
|
|
|
110
120
|
See the [Directory Structure](/PROJECT_STRUCTURE.md#directory-structure) section in [PROJECT_STRUCTURE.md](/PROJECT_STRUCTURE.md) for details on which modules need which requirements.
|
|
111
121
|
|
|
112
|
-
##
|
|
122
|
+
## Quick Start
|
|
113
123
|
|
|
114
124
|
```python
|
|
115
125
|
from ImagePRO.pre_processing.blur import apply_average_blur
|
|
@@ -126,9 +136,9 @@ image = Image.from_path("person_and_objects.jpg")
|
|
|
126
136
|
blur_result = apply_average_blur(image=image)
|
|
127
137
|
blur_result.save_as_img("blurred_output.jpg")
|
|
128
138
|
|
|
129
|
-
# Analyze face mesh (468 landmarks)
|
|
139
|
+
# Analyze face mesh (468 landmarks per detected face)
|
|
130
140
|
face_mesh_result = analyze_face_mesh(image=image)
|
|
131
|
-
print(f"Detected {len(face_mesh_result.data)} face
|
|
141
|
+
print(f"Detected {len(face_mesh_result.data)} face(s)")
|
|
132
142
|
face_mesh_result.save_as_csv("face_landmarks.csv")
|
|
133
143
|
|
|
134
144
|
# Detect body pose (33 landmarks)
|
|
@@ -138,8 +148,7 @@ print(f"Body pose data: {body_pose_result.data}")
|
|
|
138
148
|
# Detect objects with YOLO
|
|
139
149
|
object_detection_result = detect_objects(
|
|
140
150
|
image=image,
|
|
141
|
-
accuracy_level=3
|
|
142
|
-
confidence=0.5
|
|
151
|
+
accuracy_level=3 # 1=nano, 2=small, 3=medium, 4=large, 5=extra-large
|
|
143
152
|
)
|
|
144
153
|
print(f"Detected {len(object_detection_result.data)} objects")
|
|
145
154
|
object_detection_result.save_as_img("detections.jpg")
|
|
@@ -147,7 +156,7 @@ object_detection_result.save_as_img("detections.jpg")
|
|
|
147
156
|
|
|
148
157
|
> **Note**: These are basic examples. Each module contains many more functions with extensive customization options. Explore the module-specific README files for detailed documentation.
|
|
149
158
|
|
|
150
|
-
##
|
|
159
|
+
## Documentation
|
|
151
160
|
|
|
152
161
|
Each module includes comprehensive documentation with detailed examples:
|
|
153
162
|
|
|
@@ -160,7 +169,7 @@ Each module includes comprehensive documentation with detailed examples:
|
|
|
160
169
|
|
|
161
170
|
For detailed project structure and development guidelines, see [PROJECT_STRUCTURE.md](PROJECT_STRUCTURE.md).
|
|
162
171
|
|
|
163
|
-
##
|
|
172
|
+
## Architecture
|
|
164
173
|
|
|
165
174
|
ImagePRO is built with a modular architecture designed for extensibility and maintainability:
|
|
166
175
|
|
|
@@ -172,12 +181,27 @@ ImagePRO is built with a modular architecture designed for extensibility and mai
|
|
|
172
181
|
- **Documentation**: Google-style docstrings for all functions
|
|
173
182
|
|
|
174
183
|
### Key Design Principles
|
|
175
|
-
- **
|
|
184
|
+
- **Non-Destructive Operations**: Operations never write into the input image; they return results in new `Result` objects (note: `crop_image` returns a view into the source array)
|
|
176
185
|
- **Functional Style**: Stateless functions that can be easily composed
|
|
177
186
|
- **Result Objects**: Unified return type containing image, data, and metadata
|
|
178
187
|
- **Keyword Arguments**: All optional parameters use keyword-only syntax
|
|
179
188
|
|
|
180
|
-
##
|
|
189
|
+
## Testing
|
|
190
|
+
|
|
191
|
+
The test suite uses pytest:
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
# Install with test tooling (editable install + dev extra)
|
|
195
|
+
pip install -e ".[dev]"
|
|
196
|
+
|
|
197
|
+
# Run the suite (optional heavy dependencies are mocked; base deps are enough)
|
|
198
|
+
pytest
|
|
199
|
+
|
|
200
|
+
# Include performance smoke guards
|
|
201
|
+
pytest --run-performance
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
## Contributing
|
|
181
205
|
|
|
182
206
|
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
183
207
|
|
|
@@ -185,9 +209,10 @@ Contributions are welcome! Please feel free to submit a Pull Request.
|
|
|
185
209
|
1. Fork the repository
|
|
186
210
|
2. Create a virtual environment: `python -m venv .venv`
|
|
187
211
|
3. Install dependencies: `pip install -r requirements/full.txt`
|
|
188
|
-
4.
|
|
189
|
-
5.
|
|
190
|
-
6.
|
|
212
|
+
4. Install for development with test tooling: `pip install -e ".[dev]"`
|
|
213
|
+
5. Follow the coding standards outlined in [PROJECT_STRUCTURE.md](PROJECT_STRUCTURE.md)
|
|
214
|
+
6. Add tests for new features and run `pytest`
|
|
215
|
+
7. Update documentation as needed
|
|
191
216
|
|
|
192
217
|
### Reporting Issues
|
|
193
218
|
If you encounter any bugs or have feature requests, please open an issue on the GitHub repository with:
|
|
@@ -196,5 +221,5 @@ If you encounter any bugs or have feature requests, please open an issue on the
|
|
|
196
221
|
- Expected vs. actual behavior
|
|
197
222
|
- Environment details (OS, Python version, etc.)
|
|
198
223
|
|
|
199
|
-
##
|
|
224
|
+
## License
|
|
200
225
|
This project is licensed under the MIT License – see the [LICENSE](LICENSE) file for details.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
ImagePRO/__init__.py,sha256=yY-6-5p9OyeNlmPnYMeht-2yJJun8DJvH7yi0b-tMOE,997
|
|
2
|
+
ImagePRO/human_analysis/__init__.py,sha256=uSU0lM1p1HM6TNGv6u0nPHWy9QY505v43lqTkhClddM,190
|
|
3
|
+
ImagePRO/human_analysis/body_analysis/__init__.py,sha256=ezFnUJNwQXW47n9Bfxcmk4BNgvt_Bqs3Y5VESxgz2Sg,494
|
|
4
|
+
ImagePRO/human_analysis/body_analysis/body_pose_estimation.py,sha256=QkqF27YOHYoOVq5KMz0AaU455CQcRVfTbFStPZGYUA8,5565
|
|
5
|
+
ImagePRO/human_analysis/body_analysis/hand_tracking.py,sha256=fYOQGu5mcrYgqtWu5wW0xApx76fOXGDxz3hZRUhvW9k,6910
|
|
6
|
+
ImagePRO/human_analysis/face_analysis/__init__.py,sha256=nABUIg9Z-yL6JfAxMb2fqwfxmSfJmvSxfwnQuKSIOGg,1028
|
|
7
|
+
ImagePRO/human_analysis/face_analysis/eye_status_analysis.py,sha256=9ydxEj4EhVCNf954gdFBQemS1uRNA-XGCMzyliI2AsQ,7126
|
|
8
|
+
ImagePRO/human_analysis/face_analysis/face_comparison.py,sha256=vHYqWTuXEadoeZn-z81ZazXlpBri702Gz37XdWPJirw,4303
|
|
9
|
+
ImagePRO/human_analysis/face_analysis/face_detection.py,sha256=9LFMotGJ0KlAUb5yTfrXaAH_Ztr3OO3vbXLhXTN8M9M,3896
|
|
10
|
+
ImagePRO/human_analysis/face_analysis/face_mesh_analysis.py,sha256=TBSZELe_CdXcXDRRiaAYkDqHHpktRhi_koC-2iwk2Gk,8038
|
|
11
|
+
ImagePRO/human_analysis/face_analysis/head_pose_estimation.py,sha256=CovQITnmLNiLlEWffuL0idAJNBOC_nnZlC-4G9pFXxs,7328
|
|
12
|
+
ImagePRO/object_analysis/__init__.py,sha256=KcFRxVKnC0qU82zS31P4CriZIKqZdyGqQwdrMy9N4pc,267
|
|
13
|
+
ImagePRO/object_analysis/object_detection.py,sha256=REfu73vNSvUIbIzel24jygyCumZKvYr7_d6YyLDhdwY,2943
|
|
14
|
+
ImagePRO/pre_processing/__init__.py,sha256=d10ZfQzWM0JRJrFrMHOr-mxF4XYnObj6GYPTLaqzqDA,536
|
|
15
|
+
ImagePRO/pre_processing/blur.py,sha256=3zNTNpvLZPdrtRtBziB6X8ys8M7cIvyPDhojTqR63jo,7950
|
|
16
|
+
ImagePRO/pre_processing/contrast.py,sha256=4UT78ggW1rh_iaEI8FObDB4kpEmRRFWjdJES60SnHDM,5064
|
|
17
|
+
ImagePRO/pre_processing/crop.py,sha256=3Nwerzo57kx1sZPr9m355P-qf1NuZSZEA5iUn2Q1704,2406
|
|
18
|
+
ImagePRO/pre_processing/dataset_generator.py,sha256=7sL9uI_i272eOO1U_XFAaAoo7pfnU5ryuRuLERilzgo,7667
|
|
19
|
+
ImagePRO/pre_processing/grayscale.py,sha256=MRCsFRIaL45sYVHzhuNyZBXsP7PF-yorn0Wt7xONvpY,1781
|
|
20
|
+
ImagePRO/pre_processing/histogram.py,sha256=sNTWWZXr5VLpOrF_PTTJ6TD8yv1zR1l9cMyXHe7JFIM,2596
|
|
21
|
+
ImagePRO/pre_processing/resize.py,sha256=Rnde8l_PXck5OWQBEMOHgLeW2YkTz9Xfr6fGmz0sdm8,1760
|
|
22
|
+
ImagePRO/pre_processing/rotate.py,sha256=wQj3nQpN-ilM0HHwovN--UdGEJkPCY0BJ0zAdyBxv-o,4174
|
|
23
|
+
ImagePRO/pre_processing/sharpen.py,sha256=hOkY7QhzgMp9LXQwt56AP4enn24ntXlmFLe57Bxssvw,3682
|
|
24
|
+
ImagePRO/utils/__init__.py,sha256=McVd-o9KtKOpAVYbLi19KxsatPPFFF-Qbt0gdgY3Ln8,170
|
|
25
|
+
ImagePRO/utils/image.py,sha256=Y64mD97rTiJb8_uNYlyG227VZodeb4U1aGezMkj7MnM,4025
|
|
26
|
+
ImagePRO/utils/result.py,sha256=ToQaOmUlTtobJkC65BG0YO_6BkBleMBn-0Qydko7cZc,6138
|
|
27
|
+
imagepro_python-1.3.0.dist-info/licenses/LICENSE,sha256=sehkHN7dbe5umrINZJyJjIf9VTemZSgTfJlq6iIsbJQ,1069
|
|
28
|
+
imagepro_python-1.3.0.dist-info/METADATA,sha256=8Rl5PyW1UcO7Zc_nZuzHCYtU7u-lUbhY8d-J2qR6B1A,9435
|
|
29
|
+
imagepro_python-1.3.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
30
|
+
imagepro_python-1.3.0.dist-info/top_level.txt,sha256=UOuPzsoWrVKXgUupOSI96shJ6wE8Fhz-8bm8oMlLC3s,9
|
|
31
|
+
imagepro_python-1.3.0.dist-info/RECORD,,
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
ImagePRO/__init__.py,sha256=LUqZaUWVUISpR6bm5K2cO6KjiVP3ZDPADZGZW051xGk,998
|
|
2
|
-
ImagePRO/human_analysis/__init__.py,sha256=uSU0lM1p1HM6TNGv6u0nPHWy9QY505v43lqTkhClddM,190
|
|
3
|
-
ImagePRO/human_analysis/body_analysis/__init__.py,sha256=czQoJJIrDAWz0UUY0h0mwxymSQ6L6CaZYYHhYFHyf4U,203
|
|
4
|
-
ImagePRO/human_analysis/body_analysis/body_pose_estimation.py,sha256=69ybYkrbFl9EkuAc4ejCu2TW4m9TFLTn2Ae5fm_92lE,5549
|
|
5
|
-
ImagePRO/human_analysis/body_analysis/hand_tracking.py,sha256=T7yM_VZN6mBZsDFJV6C-amCVzgklatvsZrtgcBrzVSg,6869
|
|
6
|
-
ImagePRO/human_analysis/face_analysis/__init__.py,sha256=dKVZ6pq-0aQHL9vhMSt0BHs3WV_cTW65QPLJGnSiUgM,405
|
|
7
|
-
ImagePRO/human_analysis/face_analysis/eye_status_analysis.py,sha256=9ydxEj4EhVCNf954gdFBQemS1uRNA-XGCMzyliI2AsQ,7126
|
|
8
|
-
ImagePRO/human_analysis/face_analysis/face_comparison.py,sha256=RU-eSjFwpRIrV-QX8po8Mr-RhGwYEsCo3_Nt-IiA5Us,4315
|
|
9
|
-
ImagePRO/human_analysis/face_analysis/face_detection.py,sha256=__s-lI6Egf-JgruncL_MfAkmwCq8Oe3F5Vvk9x9S_2Q,3897
|
|
10
|
-
ImagePRO/human_analysis/face_analysis/face_mesh_analysis.py,sha256=8kLyEEnhMfdEALteltSHu2L2zbiFSNfzC73EuV0I5iQ,7964
|
|
11
|
-
ImagePRO/human_analysis/face_analysis/head_pose_estimation.py,sha256=IidB4U0lQryw9T4J4_T-QmC-DgANUEgueto06RkKi2Q,7336
|
|
12
|
-
ImagePRO/object_analysis/__init__.py,sha256=pj1vjY8uVUqeVONpaCacWufUzHF7rS5-nmy_C5IfI2w,148
|
|
13
|
-
ImagePRO/object_analysis/object_detection.py,sha256=ZhxOpX5Zkiu-gq-2CRYPNzSo7ai1PRFZ3qopQeODIkQ,2890
|
|
14
|
-
ImagePRO/pre_processing/__init__.py,sha256=nXUhE8RnDyW7T93fKsSDPfqzGHIW50OvgQvznY5UH0E,417
|
|
15
|
-
ImagePRO/pre_processing/blur.py,sha256=zPWUKLEoKiL6kYwnZOtGRWBHM-dgEQ8WWdDt8_wGc0s,7961
|
|
16
|
-
ImagePRO/pre_processing/contrast.py,sha256=XG69VsX3EKWIPDcQYm4D9qT7sA_BSP-sppSAa8qRUhA,5247
|
|
17
|
-
ImagePRO/pre_processing/crop.py,sha256=p0esuZcn4-YsqcniDUmsVfPoWasxVH1OqzNBra8Pw4M,2442
|
|
18
|
-
ImagePRO/pre_processing/dataset_generator.py,sha256=ekd_TnyflWKjJ6sYCiWc3vN2Q14cKiwZ1InOCslxoFM,7653
|
|
19
|
-
ImagePRO/pre_processing/grayscale.py,sha256=MRCsFRIaL45sYVHzhuNyZBXsP7PF-yorn0Wt7xONvpY,1781
|
|
20
|
-
ImagePRO/pre_processing/histogram.py,sha256=t1ykGUwB9ZbvO6J3bGPh4fDvrk4AJua1dN2BWzHIRIQ,2888
|
|
21
|
-
ImagePRO/pre_processing/resize.py,sha256=GH3ObPe67ypPQG3a7ncrNR5WNDqGibXebAiuRXr6L10,1760
|
|
22
|
-
ImagePRO/pre_processing/rotate.py,sha256=wqV2Qm7mIyvKTSFdwzr1tNAGnMG7rl5M3BRqC5m2oZE,4185
|
|
23
|
-
ImagePRO/pre_processing/sharpen.py,sha256=C5AOxQNBL80JenkztlyRBV7yuCHqxG5dTGbRhtVRT9M,3544
|
|
24
|
-
ImagePRO/utils/__init__.py,sha256=McVd-o9KtKOpAVYbLi19KxsatPPFFF-Qbt0gdgY3Ln8,170
|
|
25
|
-
ImagePRO/utils/image.py,sha256=x93cBNycNOgET20a__TxZ9ZQo_2G84uB7QjXjBo9f8M,3973
|
|
26
|
-
ImagePRO/utils/result.py,sha256=rGZX0FJ-dpA4v_m_GUO6ni0kCfcNJRnOjztbyRX9d88,6065
|
|
27
|
-
imagepro_python-1.2.1.dist-info/licenses/LICENSE,sha256=sehkHN7dbe5umrINZJyJjIf9VTemZSgTfJlq6iIsbJQ,1069
|
|
28
|
-
imagepro_python-1.2.1.dist-info/METADATA,sha256=KVaRtAoER1Ss2HS1R8w6YrIrfwt8zH5bw5u_29sMjGA,8513
|
|
29
|
-
imagepro_python-1.2.1.dist-info/WHEEL,sha256=YCfwYGOYMi5Jhw2fU4yNgwErybb2IX5PEwBKV4ZbdBo,91
|
|
30
|
-
imagepro_python-1.2.1.dist-info/top_level.txt,sha256=UOuPzsoWrVKXgUupOSI96shJ6wE8Fhz-8bm8oMlLC3s,9
|
|
31
|
-
imagepro_python-1.2.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|