ImagePRO-Python 1.3.0__py3-none-any.whl → 1.4.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 +5 -2
- ImagePRO/human_analysis/body_analysis/__init__.py +5 -11
- ImagePRO/human_analysis/body_analysis/body_pose_estimation.py +23 -3
- ImagePRO/human_analysis/body_analysis/hand_tracking.py +23 -3
- ImagePRO/human_analysis/face_analysis/__init__.py +8 -27
- ImagePRO/human_analysis/face_analysis/eye_status_analysis.py +22 -4
- ImagePRO/human_analysis/face_analysis/face_comparison.py +13 -1
- ImagePRO/human_analysis/face_analysis/face_detection.py +4 -1
- ImagePRO/human_analysis/face_analysis/face_mesh_analysis.py +25 -6
- ImagePRO/human_analysis/face_analysis/head_pose_estimation.py +13 -3
- ImagePRO/object_analysis/__init__.py +4 -4
- ImagePRO/object_analysis/object_detection.py +19 -8
- ImagePRO/pipeline/__init__.py +9 -0
- ImagePRO/pipeline/pipeline.py +362 -0
- ImagePRO/pre_processing/__init__.py +4 -5
- ImagePRO/pre_processing/dataset_generator.py +8 -1
- {imagepro_python-1.3.0.dist-info → imagepro_python-1.4.0.dist-info}/METADATA +27 -1
- imagepro_python-1.4.0.dist-info/RECORD +33 -0
- imagepro_python-1.3.0.dist-info/RECORD +0 -31
- {imagepro_python-1.3.0.dist-info → imagepro_python-1.4.0.dist-info}/WHEEL +0 -0
- {imagepro_python-1.3.0.dist-info → imagepro_python-1.4.0.dist-info}/licenses/LICENSE +0 -0
- {imagepro_python-1.3.0.dist-info → imagepro_python-1.4.0.dist-info}/top_level.txt +0 -0
ImagePRO/__init__.py
CHANGED
|
@@ -12,10 +12,11 @@ Features:
|
|
|
12
12
|
- Pre-processing: resize, crop, rotate, blur, sharpen, contrast enhancement
|
|
13
13
|
- Human analysis: face mesh, pose estimation, hand tracking
|
|
14
14
|
- Object detection with YOLO models
|
|
15
|
+
- Reusable pipelines for chaining operations
|
|
15
16
|
- Real-time processing capabilities
|
|
16
17
|
"""
|
|
17
18
|
|
|
18
|
-
__version__ = "1.
|
|
19
|
+
__version__ = "1.4.0"
|
|
19
20
|
__author__ = "Parsa Safaie"
|
|
20
21
|
__email__ = "parsasafaie.2568@proton.me"
|
|
21
22
|
|
|
@@ -24,10 +25,12 @@ from . import utils
|
|
|
24
25
|
from . import pre_processing
|
|
25
26
|
from . import human_analysis
|
|
26
27
|
from . import object_analysis
|
|
28
|
+
from . import pipeline
|
|
27
29
|
|
|
28
30
|
__all__ = [
|
|
29
31
|
"utils",
|
|
30
32
|
"pre_processing",
|
|
31
33
|
"human_analysis",
|
|
32
|
-
"object_analysis"
|
|
34
|
+
"object_analysis",
|
|
35
|
+
"pipeline"
|
|
33
36
|
]
|
|
@@ -1,17 +1,11 @@
|
|
|
1
1
|
# Body Analysis Module
|
|
2
2
|
# Provides body pose estimation and hand tracking capabilities
|
|
3
3
|
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
4
|
+
# The mediapipe dependency is optional and imported lazily inside the
|
|
5
|
+
# functions that need it, so both submodules are safe to import without
|
|
6
|
+
# any AI extras installed.
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
|
|
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
|
|
8
|
+
from . import body_pose_estimation
|
|
9
|
+
from . import hand_tracking
|
|
16
10
|
|
|
17
11
|
__all__ = ["body_pose_estimation", "hand_tracking"]
|
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import sys
|
|
4
4
|
from pathlib import Path
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
5
6
|
|
|
6
7
|
# Add src directory to path for absolute imports
|
|
7
8
|
_file_path = Path(__file__).resolve()
|
|
@@ -13,10 +14,11 @@ from ImagePRO.utils.image import Image
|
|
|
13
14
|
from ImagePRO.utils.result import Result
|
|
14
15
|
|
|
15
16
|
import cv2
|
|
16
|
-
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING: # mediapipe is imported lazily inside the functions
|
|
19
|
+
import mediapipe as mp
|
|
17
20
|
|
|
18
21
|
# Constants
|
|
19
|
-
mp_pose = mp.solutions.pose
|
|
20
22
|
TOTAL_LANDMARKS = 33
|
|
21
23
|
DEFAULT_CONFIDENCE = 0.7
|
|
22
24
|
LANDMARK_RADIUS = 3
|
|
@@ -69,6 +71,16 @@ def detect_body_pose(
|
|
|
69
71
|
):
|
|
70
72
|
raise TypeError("'landmarks_idx' must be a list of integers")
|
|
71
73
|
|
|
74
|
+
try:
|
|
75
|
+
import mediapipe as mp
|
|
76
|
+
except ImportError as err:
|
|
77
|
+
raise ImportError(
|
|
78
|
+
"The optional 'mediapipe' dependency is required for body pose "
|
|
79
|
+
'detection. Install it with: pip install "ImagePRO-Python[mediapipe]"'
|
|
80
|
+
) from err
|
|
81
|
+
|
|
82
|
+
mp_pose = mp.solutions.pose
|
|
83
|
+
|
|
72
84
|
# Initialize detector if needed
|
|
73
85
|
if pose_obj is None:
|
|
74
86
|
pose_obj = mp_pose.Pose(
|
|
@@ -133,13 +145,21 @@ def detect_body_pose_live() -> None:
|
|
|
133
145
|
Raises:
|
|
134
146
|
RuntimeError: If camera cannot be accessed
|
|
135
147
|
"""
|
|
148
|
+
try:
|
|
149
|
+
import mediapipe as mp
|
|
150
|
+
except ImportError as err:
|
|
151
|
+
raise ImportError(
|
|
152
|
+
"The optional 'mediapipe' dependency is required for body pose "
|
|
153
|
+
'detection. Install it with: pip install "ImagePRO-Python[mediapipe]"'
|
|
154
|
+
) from err
|
|
155
|
+
|
|
136
156
|
# Initialize camera
|
|
137
157
|
cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)
|
|
138
158
|
if not cap.isOpened():
|
|
139
159
|
raise RuntimeError("Cannot access webcam")
|
|
140
160
|
|
|
141
161
|
# Initialize pose detector in tracking mode
|
|
142
|
-
pose_obj =
|
|
162
|
+
pose_obj = mp.solutions.pose.Pose(
|
|
143
163
|
min_detection_confidence=DEFAULT_CONFIDENCE,
|
|
144
164
|
static_image_mode=False
|
|
145
165
|
)
|
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import sys
|
|
4
4
|
from pathlib import Path
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
5
6
|
|
|
6
7
|
# Add src directory to path for absolute imports
|
|
7
8
|
_file_path = Path(__file__).resolve()
|
|
@@ -13,10 +14,11 @@ from ImagePRO.utils.image import Image
|
|
|
13
14
|
from ImagePRO.utils.result import Result
|
|
14
15
|
|
|
15
16
|
import cv2
|
|
16
|
-
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING: # mediapipe is imported lazily inside the functions
|
|
19
|
+
import mediapipe as mp
|
|
17
20
|
|
|
18
21
|
# Constants
|
|
19
|
-
mp_hands = mp.solutions.hands
|
|
20
22
|
DEFAULT_MAX_HANDS = 2
|
|
21
23
|
DEFAULT_MIN_CONFIDENCE = 0.7
|
|
22
24
|
TOTAL_HAND_LANDMARKS = 21
|
|
@@ -79,6 +81,16 @@ def detect_hands(
|
|
|
79
81
|
):
|
|
80
82
|
raise TypeError("'landmarks_idx' must be a list of integers")
|
|
81
83
|
|
|
84
|
+
try:
|
|
85
|
+
import mediapipe as mp
|
|
86
|
+
except ImportError as err:
|
|
87
|
+
raise ImportError(
|
|
88
|
+
"The optional 'mediapipe' dependency is required for hand "
|
|
89
|
+
'tracking. Install it with: pip install "ImagePRO-Python[mediapipe]"'
|
|
90
|
+
) from err
|
|
91
|
+
|
|
92
|
+
mp_hands = mp.solutions.hands
|
|
93
|
+
|
|
82
94
|
# Initialize detector if needed
|
|
83
95
|
if hands_obj is None:
|
|
84
96
|
hands_obj = mp_hands.Hands(
|
|
@@ -168,13 +180,21 @@ def detect_hands_live(
|
|
|
168
180
|
if not isinstance(min_confidence, (int, float)) or not (0.0 <= min_confidence <= 1.0):
|
|
169
181
|
raise ValueError("'min_confidence' must be between 0.0 and 1.0")
|
|
170
182
|
|
|
183
|
+
try:
|
|
184
|
+
import mediapipe as mp
|
|
185
|
+
except ImportError as err:
|
|
186
|
+
raise ImportError(
|
|
187
|
+
"The optional 'mediapipe' dependency is required for hand "
|
|
188
|
+
'tracking. Install it with: pip install "ImagePRO-Python[mediapipe]"'
|
|
189
|
+
) from err
|
|
190
|
+
|
|
171
191
|
# Initialize camera
|
|
172
192
|
cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)
|
|
173
193
|
if not cap.isOpened():
|
|
174
194
|
raise RuntimeError("Cannot access webcam")
|
|
175
195
|
|
|
176
196
|
# Initialize hand detector in tracking mode
|
|
177
|
-
hands_obj =
|
|
197
|
+
hands_obj = mp.solutions.hands.Hands(
|
|
178
198
|
min_detection_confidence=min_confidence,
|
|
179
199
|
max_num_hands=max_hands,
|
|
180
200
|
static_image_mode=False
|
|
@@ -1,34 +1,15 @@
|
|
|
1
1
|
# Face Analysis Module
|
|
2
2
|
# Provides facial landmark detection, pose estimation, and eye status analysis
|
|
3
3
|
#
|
|
4
|
-
# The mediapipe
|
|
5
|
-
#
|
|
6
|
-
#
|
|
4
|
+
# The mediapipe and insightface dependencies are optional and imported
|
|
5
|
+
# lazily inside the functions that need them, so every submodule is safe
|
|
6
|
+
# to import regardless of which extras are installed.
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
|
8
|
+
from . import eye_status_analysis
|
|
9
|
+
from . import face_comparison
|
|
10
|
+
from . import face_detection
|
|
11
|
+
from . import face_mesh_analysis
|
|
12
|
+
from . import head_pose_estimation
|
|
32
13
|
|
|
33
14
|
__all__ = [
|
|
34
15
|
"eye_status_analysis",
|
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import sys
|
|
4
4
|
from pathlib import Path
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
5
6
|
|
|
6
7
|
# Add src directory to path for absolute imports
|
|
7
8
|
_file_path = Path(__file__).resolve()
|
|
@@ -14,10 +15,11 @@ from ImagePRO.utils.image import Image
|
|
|
14
15
|
from ImagePRO.utils.result import Result
|
|
15
16
|
|
|
16
17
|
import cv2
|
|
17
|
-
|
|
18
|
+
|
|
19
|
+
if TYPE_CHECKING: # mediapipe is imported lazily inside the functions
|
|
20
|
+
import mediapipe as mp
|
|
18
21
|
|
|
19
22
|
# Constants
|
|
20
|
-
mp_face_mesh = mp.solutions.face_mesh
|
|
21
23
|
DEFAULT_MIN_CONFIDENCE = 0.7
|
|
22
24
|
DEFAULT_THRESHOLD = 0.2
|
|
23
25
|
RIGHT_EYE_INDICES = [386, 374, 263, 362] # MediaPipe right eye landmarks
|
|
@@ -65,12 +67,20 @@ def analyze_eye_status(
|
|
|
65
67
|
if not 0 <= min_confidence <= 1:
|
|
66
68
|
raise ValueError("'min_confidence' must be between 0 and 1")
|
|
67
69
|
|
|
70
|
+
try:
|
|
71
|
+
import mediapipe as mp
|
|
72
|
+
except ImportError as err:
|
|
73
|
+
raise ImportError(
|
|
74
|
+
"The optional 'mediapipe' dependency is required for eye status "
|
|
75
|
+
'analysis. Install it with: pip install "ImagePRO-Python[mediapipe]"'
|
|
76
|
+
) from err
|
|
77
|
+
|
|
68
78
|
# Get image dimensions
|
|
69
79
|
h, w = image.shape[:2]
|
|
70
80
|
|
|
71
81
|
# Initialize detector if needed
|
|
72
82
|
if face_mesh_obj is None:
|
|
73
|
-
face_mesh_obj =
|
|
83
|
+
face_mesh_obj = mp.solutions.face_mesh.FaceMesh(
|
|
74
84
|
min_detection_confidence=min_confidence,
|
|
75
85
|
refine_landmarks=True,
|
|
76
86
|
static_image_mode=True
|
|
@@ -173,13 +183,21 @@ def analyze_eye_status_live(
|
|
|
173
183
|
if not 0 <= min_confidence <= 1:
|
|
174
184
|
raise ValueError("'min_confidence' must be between 0 and 1")
|
|
175
185
|
|
|
186
|
+
try:
|
|
187
|
+
import mediapipe as mp
|
|
188
|
+
except ImportError as err:
|
|
189
|
+
raise ImportError(
|
|
190
|
+
"The optional 'mediapipe' dependency is required for eye status "
|
|
191
|
+
'analysis. Install it with: pip install "ImagePRO-Python[mediapipe]"'
|
|
192
|
+
) from err
|
|
193
|
+
|
|
176
194
|
# Initialize webcam
|
|
177
195
|
cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)
|
|
178
196
|
if not cap.isOpened():
|
|
179
197
|
raise RuntimeError("Failed to access webcam")
|
|
180
198
|
|
|
181
199
|
# Initialize face mesh detector for video
|
|
182
|
-
face_mesh =
|
|
200
|
+
face_mesh = mp.solutions.face_mesh.FaceMesh(
|
|
183
201
|
max_num_faces=1,
|
|
184
202
|
min_detection_confidence=min_confidence,
|
|
185
203
|
refine_landmarks=True,
|
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import sys
|
|
4
4
|
from pathlib import Path
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
5
6
|
|
|
6
7
|
# Add src directory to path for absolute imports
|
|
7
8
|
_file_path = Path(__file__).resolve()
|
|
@@ -13,11 +14,13 @@ import os
|
|
|
13
14
|
|
|
14
15
|
import cv2
|
|
15
16
|
import numpy as np
|
|
16
|
-
from insightface.app import FaceAnalysis
|
|
17
17
|
|
|
18
18
|
from ImagePRO.utils.image import Image
|
|
19
19
|
from ImagePRO.utils.result import Result
|
|
20
20
|
|
|
21
|
+
if TYPE_CHECKING: # insightface is imported lazily inside the function
|
|
22
|
+
from insightface.app import FaceAnalysis
|
|
23
|
+
|
|
21
24
|
# Constants
|
|
22
25
|
DEFAULT_SIMILARITY_THRESHOLD = 0.5
|
|
23
26
|
DEFAULT_MODEL_NAME = "buffalo_l"
|
|
@@ -62,6 +65,15 @@ def compare_faces(
|
|
|
62
65
|
if not isinstance(image_2, Image):
|
|
63
66
|
raise TypeError("'image_2' must be an Image instance")
|
|
64
67
|
|
|
68
|
+
try:
|
|
69
|
+
from insightface.app import FaceAnalysis
|
|
70
|
+
except ImportError as err:
|
|
71
|
+
raise ImportError(
|
|
72
|
+
"The optional 'insightface' dependency is required for face "
|
|
73
|
+
'comparison. Install it with: pip install '
|
|
74
|
+
'"ImagePRO-Python[insightface]"'
|
|
75
|
+
) from err
|
|
76
|
+
|
|
65
77
|
# Initialize model if needed
|
|
66
78
|
if app is None:
|
|
67
79
|
app = FaceAnalysis(
|
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import sys
|
|
4
4
|
from pathlib import Path
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
5
6
|
|
|
6
7
|
# Add src directory to path for absolute imports
|
|
7
8
|
_file_path = Path(__file__).resolve()
|
|
@@ -10,13 +11,15 @@ if str(_src_path) not in sys.path:
|
|
|
10
11
|
sys.path.insert(0, str(_src_path))
|
|
11
12
|
|
|
12
13
|
import cv2
|
|
13
|
-
import mediapipe as mp
|
|
14
14
|
import numpy as np
|
|
15
15
|
|
|
16
16
|
from ImagePRO.human_analysis.face_analysis.face_mesh_analysis import analyze_face_mesh
|
|
17
17
|
from ImagePRO.utils.image import Image
|
|
18
18
|
from ImagePRO.utils.result import Result
|
|
19
19
|
|
|
20
|
+
if TYPE_CHECKING: # mediapipe is imported lazily inside analyze_face_mesh
|
|
21
|
+
import mediapipe as mp
|
|
22
|
+
|
|
20
23
|
|
|
21
24
|
# Constants
|
|
22
25
|
DEFAULT_MAX_FACES = 1
|
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import sys
|
|
4
4
|
from pathlib import Path
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
5
6
|
|
|
6
7
|
# Add src directory to path for absolute imports
|
|
7
8
|
_file_path = Path(__file__).resolve()
|
|
@@ -13,12 +14,11 @@ from ImagePRO.utils.image import Image
|
|
|
13
14
|
from ImagePRO.utils.result import Result
|
|
14
15
|
|
|
15
16
|
import cv2
|
|
16
|
-
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING: # mediapipe is imported lazily inside the functions
|
|
19
|
+
import mediapipe as mp
|
|
17
20
|
|
|
18
21
|
# Constants
|
|
19
|
-
mp_face_mesh = mp.solutions.face_mesh
|
|
20
|
-
mp_drawing_utils = mp.solutions.drawing_utils
|
|
21
|
-
mp_drawing_styles = mp.solutions.drawing_styles
|
|
22
22
|
DEFAULT_MAX_FACES = 1
|
|
23
23
|
DEFAULT_MIN_CONFIDENCE = 0.7
|
|
24
24
|
TOTAL_FACE_LANDMARKS = 468
|
|
@@ -71,7 +71,6 @@ def analyze_face_mesh(
|
|
|
71
71
|
- Coordinates are normalized [0,1]. Multiply by width/height for pixels
|
|
72
72
|
- Full mesh (468 points) shows tessellation, specific points show dots
|
|
73
73
|
"""
|
|
74
|
-
|
|
75
74
|
# Validate inputs
|
|
76
75
|
if not isinstance(image, Image):
|
|
77
76
|
raise TypeError("'image' must be an Image instance")
|
|
@@ -88,6 +87,18 @@ def analyze_face_mesh(
|
|
|
88
87
|
):
|
|
89
88
|
raise TypeError("'landmarks_idx' must be a list of integers")
|
|
90
89
|
|
|
90
|
+
try:
|
|
91
|
+
import mediapipe as mp
|
|
92
|
+
except ImportError as err:
|
|
93
|
+
raise ImportError(
|
|
94
|
+
"The optional 'mediapipe' dependency is required for face mesh "
|
|
95
|
+
'analysis. Install it with: pip install "ImagePRO-Python[mediapipe]"'
|
|
96
|
+
) from err
|
|
97
|
+
|
|
98
|
+
mp_face_mesh = mp.solutions.face_mesh
|
|
99
|
+
mp_drawing_utils = mp.solutions.drawing_utils
|
|
100
|
+
mp_drawing_styles = mp.solutions.drawing_styles
|
|
101
|
+
|
|
91
102
|
# Initialize detector if needed
|
|
92
103
|
if face_mesh_obj is None:
|
|
93
104
|
face_mesh = mp_face_mesh.FaceMesh(
|
|
@@ -195,13 +206,21 @@ def analyze_face_mesh_live(
|
|
|
195
206
|
if not isinstance(min_confidence, (int, float)) or not (0 <= min_confidence <= 1):
|
|
196
207
|
raise ValueError("'min_confidence' must be between 0 and 1")
|
|
197
208
|
|
|
209
|
+
try:
|
|
210
|
+
import mediapipe as mp
|
|
211
|
+
except ImportError as err:
|
|
212
|
+
raise ImportError(
|
|
213
|
+
"The optional 'mediapipe' dependency is required for face mesh "
|
|
214
|
+
'analysis. Install it with: pip install "ImagePRO-Python[mediapipe]"'
|
|
215
|
+
) from err
|
|
216
|
+
|
|
198
217
|
# Initialize camera
|
|
199
218
|
cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)
|
|
200
219
|
if not cap.isOpened():
|
|
201
220
|
raise RuntimeError("Cannot access webcam")
|
|
202
221
|
|
|
203
222
|
# Initialize detector in tracking mode for better performance
|
|
204
|
-
face_mesh =
|
|
223
|
+
face_mesh = mp.solutions.face_mesh.FaceMesh(
|
|
205
224
|
max_num_faces=max_faces,
|
|
206
225
|
min_detection_confidence=min_confidence,
|
|
207
226
|
refine_landmarks=True,
|
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import sys
|
|
4
4
|
from pathlib import Path
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
5
6
|
|
|
6
7
|
# Add src directory to path for absolute imports
|
|
7
8
|
_file_path = Path(__file__).resolve()
|
|
@@ -14,10 +15,11 @@ from ImagePRO.utils.image import Image
|
|
|
14
15
|
from ImagePRO.utils.result import Result
|
|
15
16
|
|
|
16
17
|
import cv2
|
|
17
|
-
|
|
18
|
+
|
|
19
|
+
if TYPE_CHECKING: # mediapipe is imported lazily inside the functions
|
|
20
|
+
import mediapipe as mp
|
|
18
21
|
|
|
19
22
|
# Constants
|
|
20
|
-
mp_face_mesh = mp.solutions.face_mesh
|
|
21
23
|
DEFAULT_MAX_FACES = 1
|
|
22
24
|
DEFAULT_MIN_CONFIDENCE = 0.7
|
|
23
25
|
HEAD_POSE_INDICES = [1, 152, 33, 263, 168] # nose_tip, chin, left_eye, right_eye, nasion
|
|
@@ -169,13 +171,21 @@ def estimate_head_pose_live(
|
|
|
169
171
|
if not 0 <= min_confidence <= 1:
|
|
170
172
|
raise ValueError("'min_confidence' must be between 0 and 1")
|
|
171
173
|
|
|
174
|
+
try:
|
|
175
|
+
import mediapipe as mp
|
|
176
|
+
except ImportError as err:
|
|
177
|
+
raise ImportError(
|
|
178
|
+
"The optional 'mediapipe' dependency is required for head pose "
|
|
179
|
+
'estimation. Install it with: pip install "ImagePRO-Python[mediapipe]"'
|
|
180
|
+
) from err
|
|
181
|
+
|
|
172
182
|
# Initialize webcam
|
|
173
183
|
cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)
|
|
174
184
|
if not cap.isOpened():
|
|
175
185
|
raise RuntimeError("Failed to access webcam")
|
|
176
186
|
|
|
177
187
|
# Initialize face mesh detector for video
|
|
178
|
-
face_mesh =
|
|
188
|
+
face_mesh = mp.solutions.face_mesh.FaceMesh(
|
|
179
189
|
max_num_faces=max_faces,
|
|
180
190
|
min_detection_confidence=min_confidence,
|
|
181
191
|
refine_landmarks=True,
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# Object Analysis Module
|
|
2
2
|
# Provides object detection capabilities using YOLO models
|
|
3
|
+
#
|
|
4
|
+
# The ultralytics dependency is optional and imported lazily inside the
|
|
5
|
+
# functions that need it, so this package imports cleanly without it.
|
|
3
6
|
|
|
4
|
-
|
|
5
|
-
from . import object_detection
|
|
6
|
-
except ImportError:
|
|
7
|
-
object_detection = None
|
|
7
|
+
from . import object_detection
|
|
8
8
|
|
|
9
9
|
__all__ = ["object_detection"]
|
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import sys
|
|
4
4
|
from pathlib import Path
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
5
6
|
|
|
6
7
|
# Add src directory to path for absolute imports
|
|
7
8
|
_file_path = Path(__file__).resolve()
|
|
@@ -9,11 +10,12 @@ _src_path = _file_path.parents[2] # Go up to src directory
|
|
|
9
10
|
if str(_src_path) not in sys.path:
|
|
10
11
|
sys.path.insert(0, str(_src_path))
|
|
11
12
|
|
|
12
|
-
from ultralytics import YOLO
|
|
13
|
-
|
|
14
13
|
from ImagePRO.utils.image import Image
|
|
15
14
|
from ImagePRO.utils.result import Result
|
|
16
15
|
|
|
16
|
+
if TYPE_CHECKING: # ultralytics is imported lazily inside the function
|
|
17
|
+
from ultralytics import YOLO
|
|
18
|
+
|
|
17
19
|
# Constants
|
|
18
20
|
DEFAULT_ACCURACY_LEVEL = 1
|
|
19
21
|
MODEL_MAPPING = {
|
|
@@ -65,15 +67,24 @@ def detect_objects(
|
|
|
65
67
|
if not isinstance(image, Image):
|
|
66
68
|
raise TypeError("'image' must be an Image instance")
|
|
67
69
|
|
|
70
|
+
# accuracy_level only applies when no model is given
|
|
71
|
+
if model is None and accuracy_level not in MODEL_MAPPING:
|
|
72
|
+
raise ValueError(
|
|
73
|
+
f"'accuracy_level' must be in {list(MODEL_MAPPING.keys())}, "
|
|
74
|
+
f"got {accuracy_level}"
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
try:
|
|
78
|
+
from ultralytics import YOLO
|
|
79
|
+
except ImportError as err:
|
|
80
|
+
raise ImportError(
|
|
81
|
+
"The optional 'ultralytics' dependency is required for object "
|
|
82
|
+
'detection. Install it with: pip install "ImagePRO-Python[yolo]"'
|
|
83
|
+
) from err
|
|
84
|
+
|
|
68
85
|
# Initialize model
|
|
69
86
|
model_name = None
|
|
70
87
|
if model is None:
|
|
71
|
-
if accuracy_level not in MODEL_MAPPING:
|
|
72
|
-
raise ValueError(
|
|
73
|
-
f"'accuracy_level' must be in {list(MODEL_MAPPING.keys())}, "
|
|
74
|
-
f"got {accuracy_level}"
|
|
75
|
-
)
|
|
76
|
-
|
|
77
88
|
model_name = MODEL_MAPPING[accuracy_level]
|
|
78
89
|
model = YOLO(model=model_name)
|
|
79
90
|
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Pipeline Module
|
|
2
|
+
# Provides a reusable, ordered chain of image processing operations
|
|
3
|
+
#
|
|
4
|
+
# Uses only base dependencies (numpy); all operations handed to a Pipeline
|
|
5
|
+
# may come from any module, including those with optional backends.
|
|
6
|
+
|
|
7
|
+
from .pipeline import Pipeline, Step
|
|
8
|
+
|
|
9
|
+
__all__ = ["Pipeline", "Step"]
|
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any, Callable, Iterator
|
|
6
|
+
|
|
7
|
+
# Add src directory to path for absolute imports
|
|
8
|
+
_file_path = Path(__file__).resolve()
|
|
9
|
+
_src_path = _file_path.parents[2] # Go up to src directory
|
|
10
|
+
if str(_src_path) not in sys.path:
|
|
11
|
+
sys.path.insert(0, str(_src_path))
|
|
12
|
+
|
|
13
|
+
import numpy as np
|
|
14
|
+
|
|
15
|
+
from ImagePRO.utils.image import Image
|
|
16
|
+
from ImagePRO.utils.result import Result
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Step:
|
|
20
|
+
"""
|
|
21
|
+
A single operation in a Pipeline, with optional keyword arguments.
|
|
22
|
+
|
|
23
|
+
Wraps any callable that accepts an Image as its first positional argument
|
|
24
|
+
and returns a Result (or a numpy.ndarray, treated as the output image).
|
|
25
|
+
Keyword arguments given to the constructor are forwarded to the wrapped
|
|
26
|
+
function on every call, so a Step fully describes one stage of a pipeline.
|
|
27
|
+
|
|
28
|
+
Example:
|
|
29
|
+
>>> from ImagePRO.pre_processing.blur import apply_gaussian_blur
|
|
30
|
+
>>> step = Step(apply_gaussian_blur, kernel_size=(7, 7))
|
|
31
|
+
>>> result = step(image) # Same as apply_gaussian_blur(image, kernel_size=(7, 7))
|
|
32
|
+
|
|
33
|
+
Attributes:
|
|
34
|
+
function (Callable):
|
|
35
|
+
The wrapped function to execute.
|
|
36
|
+
kwargs (dict[str, Any]):
|
|
37
|
+
Keyword-only arguments forwarded to the function on each call.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
def __init__(self, function: Callable[..., Result], **kwargs: Any) -> None:
|
|
41
|
+
"""
|
|
42
|
+
Create a Step from a function and its keyword arguments.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
function (Callable):
|
|
46
|
+
Function to wrap. Must be callable and accept an Image as its
|
|
47
|
+
first positional argument.
|
|
48
|
+
**kwargs (Any):
|
|
49
|
+
Keyword-only arguments forwarded to the function on every call.
|
|
50
|
+
|
|
51
|
+
Raises:
|
|
52
|
+
TypeError: If function is not callable.
|
|
53
|
+
"""
|
|
54
|
+
if not callable(function):
|
|
55
|
+
raise TypeError("'function' must be a callable.")
|
|
56
|
+
self.function = function
|
|
57
|
+
self.kwargs: dict[str, Any] = dict(kwargs)
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def name(self) -> str:
|
|
61
|
+
"""
|
|
62
|
+
Human-readable name of the wrapped function, used in pipeline metadata.
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
str: The function's __name__ when available, otherwise the name
|
|
66
|
+
of its type (e.g. 'partial' for functools.partial objects).
|
|
67
|
+
"""
|
|
68
|
+
return getattr(self.function, "__name__", type(self.function).__name__)
|
|
69
|
+
|
|
70
|
+
def __call__(self, image: Image) -> Result:
|
|
71
|
+
"""
|
|
72
|
+
Execute the wrapped function on an image.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
image (Image):
|
|
76
|
+
Input image passed to the wrapped function.
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
Result: Whatever the wrapped function returns.
|
|
80
|
+
|
|
81
|
+
Raises:
|
|
82
|
+
TypeError: If image is not an Image instance (raised by the
|
|
83
|
+
wrapped function's own validation).
|
|
84
|
+
ValueError: If wrapped function arguments are invalid (raised by
|
|
85
|
+
the wrapped function's own validation).
|
|
86
|
+
"""
|
|
87
|
+
return self.function(image, **self.kwargs)
|
|
88
|
+
|
|
89
|
+
def __repr__(self) -> str:
|
|
90
|
+
"""
|
|
91
|
+
Return a readable representation of the step.
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
str: Representation including the function name and arguments.
|
|
95
|
+
"""
|
|
96
|
+
args = ", ".join(f"{key}={value!r}" for key, value in self.kwargs.items())
|
|
97
|
+
return f"Step({self.name}{', ' + args if args else ''})"
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class Pipeline:
|
|
101
|
+
"""
|
|
102
|
+
A reusable, ordered chain of image processing operations.
|
|
103
|
+
|
|
104
|
+
A Pipeline is defined once from a sequence of functions and can then be
|
|
105
|
+
run on any input as many times as needed. Each operation receives an
|
|
106
|
+
Image and its output image is automatically wrapped and fed to the next
|
|
107
|
+
operation; the final Result is returned with pipeline metadata attached.
|
|
108
|
+
|
|
109
|
+
Operations are the standard ImagePRO functions (which take an Image and
|
|
110
|
+
return a Result) or any custom callable following the same convention.
|
|
111
|
+
A custom function may also return a plain numpy.ndarray, which is treated
|
|
112
|
+
as the output image. Per-operation keyword arguments are configured with
|
|
113
|
+
Step, or fluently via apply().
|
|
114
|
+
|
|
115
|
+
Pipelines are immutable: apply() returns a new Pipeline and leaves the
|
|
116
|
+
original untouched, and run() never mutates the pipeline, so a single
|
|
117
|
+
Pipeline instance is safe to reuse across many inputs.
|
|
118
|
+
|
|
119
|
+
Example:
|
|
120
|
+
>>> from ImagePRO.pre_processing.blur import apply_gaussian_blur
|
|
121
|
+
>>> from ImagePRO.pre_processing.grayscale import convert_to_grayscale
|
|
122
|
+
>>> from ImagePRO.pre_processing.resize import resize_image
|
|
123
|
+
>>>
|
|
124
|
+
>>> pipeline = Pipeline([
|
|
125
|
+
... Step(apply_gaussian_blur, kernel_size=(7, 7)),
|
|
126
|
+
... convert_to_grayscale,
|
|
127
|
+
... Step(resize_image, new_size=(800, 600)),
|
|
128
|
+
... ])
|
|
129
|
+
>>> result = pipeline.run("photo.jpg") # Any Image, ndarray or path
|
|
130
|
+
>>> result.save_as_img("processed.jpg")
|
|
131
|
+
"""
|
|
132
|
+
|
|
133
|
+
def __init__(self, *operations: Any) -> None:
|
|
134
|
+
"""
|
|
135
|
+
Create a Pipeline from one or more operations.
|
|
136
|
+
|
|
137
|
+
Operations can be passed as separate arguments or as a single
|
|
138
|
+
list/tuple. Passing another Pipeline as an operation nests it: the
|
|
139
|
+
inner pipeline runs as a single step.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
*operations (Callable | Step | list | tuple):
|
|
143
|
+
Functions to execute in order. Each element must be callable;
|
|
144
|
+
Step instances carry their keyword arguments. A single
|
|
145
|
+
list/tuple of operations is also accepted.
|
|
146
|
+
|
|
147
|
+
Raises:
|
|
148
|
+
TypeError: If any operation is not callable.
|
|
149
|
+
"""
|
|
150
|
+
if len(operations) == 1 and isinstance(operations[0], (list, tuple)):
|
|
151
|
+
operations = tuple(operations[0])
|
|
152
|
+
|
|
153
|
+
steps: list[Step] = []
|
|
154
|
+
for operation in operations:
|
|
155
|
+
if not callable(operation):
|
|
156
|
+
raise TypeError(
|
|
157
|
+
"All pipeline operations must be callable; got "
|
|
158
|
+
f"{type(operation).__name__}. Wrap keyword arguments with "
|
|
159
|
+
"Step, e.g. Step(resize_image, new_size=(800, 600))."
|
|
160
|
+
)
|
|
161
|
+
steps.append(operation if isinstance(operation, Step) else Step(operation))
|
|
162
|
+
self._operations = steps
|
|
163
|
+
|
|
164
|
+
@property
|
|
165
|
+
def operations(self) -> tuple[Step, ...]:
|
|
166
|
+
"""
|
|
167
|
+
The ordered steps of this pipeline.
|
|
168
|
+
|
|
169
|
+
Returns:
|
|
170
|
+
tuple[Step, ...]: Step objects in execution order.
|
|
171
|
+
"""
|
|
172
|
+
return tuple(self._operations)
|
|
173
|
+
|
|
174
|
+
def apply(self, function: Callable[..., Result], **kwargs: Any) -> Pipeline:
|
|
175
|
+
"""
|
|
176
|
+
Return a new Pipeline with one more operation appended.
|
|
177
|
+
|
|
178
|
+
The current pipeline is not modified, following ImagePRO's
|
|
179
|
+
non-destructive design principle.
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
function (Callable):
|
|
183
|
+
Function to append. Must be callable and accept an Image as
|
|
184
|
+
its first positional argument.
|
|
185
|
+
**kwargs (Any):
|
|
186
|
+
Keyword-only arguments forwarded to the function on every run.
|
|
187
|
+
|
|
188
|
+
Returns:
|
|
189
|
+
Pipeline: New pipeline containing all existing steps plus this one.
|
|
190
|
+
|
|
191
|
+
Raises:
|
|
192
|
+
TypeError: If function is not callable.
|
|
193
|
+
|
|
194
|
+
Example:
|
|
195
|
+
>>> pipeline = Pipeline(apply_gaussian_blur).apply(
|
|
196
|
+
... resize_image, new_size=(800, 600)
|
|
197
|
+
... )
|
|
198
|
+
"""
|
|
199
|
+
return Pipeline(*self._operations, Step(function, **kwargs))
|
|
200
|
+
|
|
201
|
+
def run(self, image: Image | np.ndarray | str | Path) -> Result:
|
|
202
|
+
"""
|
|
203
|
+
Execute all pipeline operations in order on the given input.
|
|
204
|
+
|
|
205
|
+
The input is first normalized to an Image (file paths are loaded and
|
|
206
|
+
NumPy arrays are wrapped, assuming BGR for 3-channel and GRAY for 2D
|
|
207
|
+
arrays). Each operation then receives an Image built from the previous
|
|
208
|
+
operation's output; 2D intermediates are tagged as GRAY and
|
|
209
|
+
multi-channel ones keep the incoming colorspace.
|
|
210
|
+
|
|
211
|
+
The returned Result carries the final operation's image and data. Its
|
|
212
|
+
metadata contains the pipeline information (operation, steps,
|
|
213
|
+
step_meta, source) merged over the final operation's own metadata.
|
|
214
|
+
|
|
215
|
+
Args:
|
|
216
|
+
image (Image | np.ndarray | str | Path):
|
|
217
|
+
Input to process. Can be an Image instance, a NumPy array
|
|
218
|
+
(BGR for 3-channel, GRAY for 2D), or a path to an image file.
|
|
219
|
+
|
|
220
|
+
Returns:
|
|
221
|
+
Result: Result object of the final operation.
|
|
222
|
+
- image (np.ndarray | list[np.ndarray] | None): Final output image(s)
|
|
223
|
+
- data (Any): Structured data of the final operation
|
|
224
|
+
- meta (dict): Final operation metadata plus pipeline info:
|
|
225
|
+
'operation' ("pipeline"), 'steps' (list of step names),
|
|
226
|
+
'step_meta' (per-step metadata list), 'source' (input Image)
|
|
227
|
+
|
|
228
|
+
Raises:
|
|
229
|
+
TypeError: If image is not an Image, ndarray, or path.
|
|
230
|
+
TypeError: If an operation returns an unsupported type.
|
|
231
|
+
ValueError: If the pipeline has no operations.
|
|
232
|
+
ValueError: If a non-final operation returns no image, so the
|
|
233
|
+
chain cannot continue.
|
|
234
|
+
TypeError/ValueError: As raised by the operations themselves for
|
|
235
|
+
invalid inputs or arguments.
|
|
236
|
+
"""
|
|
237
|
+
# Normalize input to an Image (cheap validation before any step runs)
|
|
238
|
+
if isinstance(image, Image):
|
|
239
|
+
current = image
|
|
240
|
+
elif isinstance(image, np.ndarray):
|
|
241
|
+
colorspace = "GRAY" if image.ndim == 2 else "BGR"
|
|
242
|
+
current = Image.from_array(image, colorspace=colorspace)
|
|
243
|
+
elif isinstance(image, (str, Path)):
|
|
244
|
+
current = Image.from_path(image)
|
|
245
|
+
else:
|
|
246
|
+
raise TypeError(
|
|
247
|
+
"'image' must be an Image instance, a numpy.ndarray, or a "
|
|
248
|
+
"file path (str or pathlib.Path)."
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
if not self._operations:
|
|
252
|
+
raise ValueError(
|
|
253
|
+
"Cannot run an empty pipeline. Add operations at construction "
|
|
254
|
+
"or with apply()."
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
source = current
|
|
258
|
+
step_names: list[str] = []
|
|
259
|
+
step_metas: list[dict[str, Any]] = []
|
|
260
|
+
final_image: Any = None
|
|
261
|
+
final_data: Any = None
|
|
262
|
+
final_meta: dict[str, Any] = {}
|
|
263
|
+
|
|
264
|
+
for index, step in enumerate(self._operations):
|
|
265
|
+
is_last = index == len(self._operations) - 1
|
|
266
|
+
output = step(current)
|
|
267
|
+
|
|
268
|
+
# Normalize the step output (Result is the convention; a plain
|
|
269
|
+
# ndarray from custom functions is accepted as the output image)
|
|
270
|
+
if isinstance(output, Result):
|
|
271
|
+
final_image = output.image
|
|
272
|
+
final_data = output.data
|
|
273
|
+
final_meta = dict(output.meta)
|
|
274
|
+
elif isinstance(output, np.ndarray):
|
|
275
|
+
final_image = output
|
|
276
|
+
final_data = None
|
|
277
|
+
final_meta = {}
|
|
278
|
+
else:
|
|
279
|
+
raise TypeError(
|
|
280
|
+
f"Step {index} ('{step.name}') returned "
|
|
281
|
+
f"{type(output).__name__}; pipeline operations must "
|
|
282
|
+
"return a Result object or a numpy.ndarray."
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
step_names.append(step.name)
|
|
286
|
+
step_metas.append(final_meta)
|
|
287
|
+
|
|
288
|
+
if is_last:
|
|
289
|
+
break
|
|
290
|
+
|
|
291
|
+
# Prepare the input of the next operation
|
|
292
|
+
if final_image is None:
|
|
293
|
+
raise ValueError(
|
|
294
|
+
f"Step {index} ('{step.name}') returned no image, so the "
|
|
295
|
+
"pipeline cannot continue. Only the final step of a "
|
|
296
|
+
"pipeline may return a result without an image."
|
|
297
|
+
)
|
|
298
|
+
if not isinstance(final_image, np.ndarray):
|
|
299
|
+
raise ValueError(
|
|
300
|
+
f"Step {index} ('{step.name}') did not return a single "
|
|
301
|
+
"image array, so the pipeline cannot continue. Only the "
|
|
302
|
+
"final step of a pipeline may return multi-image results."
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
colorspace = "GRAY" if final_image.ndim == 2 else current.colorspace
|
|
306
|
+
current = Image.from_array(final_image, colorspace=colorspace)
|
|
307
|
+
|
|
308
|
+
meta = dict(final_meta)
|
|
309
|
+
meta.update({
|
|
310
|
+
"source": source,
|
|
311
|
+
"operation": "pipeline",
|
|
312
|
+
"steps": step_names,
|
|
313
|
+
"step_meta": step_metas,
|
|
314
|
+
})
|
|
315
|
+
return Result(image=final_image, data=final_data, meta=meta)
|
|
316
|
+
|
|
317
|
+
def __call__(self, image: Image | np.ndarray | str | Path) -> Result:
|
|
318
|
+
"""
|
|
319
|
+
Run the pipeline on an input; shorthand for run().
|
|
320
|
+
|
|
321
|
+
Args:
|
|
322
|
+
image (Image | np.ndarray | str | Path):
|
|
323
|
+
Input to process, as accepted by run().
|
|
324
|
+
|
|
325
|
+
Returns:
|
|
326
|
+
Result: Result object of the final operation.
|
|
327
|
+
|
|
328
|
+
Raises:
|
|
329
|
+
TypeError: If image is not an Image, ndarray, or path.
|
|
330
|
+
ValueError: If the pipeline has no operations or a non-final
|
|
331
|
+
operation returns no image.
|
|
332
|
+
"""
|
|
333
|
+
return self.run(image)
|
|
334
|
+
|
|
335
|
+
def __len__(self) -> int:
|
|
336
|
+
"""
|
|
337
|
+
Return the number of operations in the pipeline.
|
|
338
|
+
|
|
339
|
+
Returns:
|
|
340
|
+
int: Number of steps.
|
|
341
|
+
"""
|
|
342
|
+
return len(self._operations)
|
|
343
|
+
|
|
344
|
+
def __iter__(self) -> Iterator[Step]:
|
|
345
|
+
"""
|
|
346
|
+
Iterate over the pipeline's steps in execution order.
|
|
347
|
+
|
|
348
|
+
Returns:
|
|
349
|
+
Iterator[Step]: Iterator of Step objects.
|
|
350
|
+
"""
|
|
351
|
+
return iter(self._operations)
|
|
352
|
+
|
|
353
|
+
def __repr__(self) -> str:
|
|
354
|
+
"""
|
|
355
|
+
Return a readable representation of the pipeline.
|
|
356
|
+
|
|
357
|
+
Returns:
|
|
358
|
+
str: Representation listing the step names in order.
|
|
359
|
+
"""
|
|
360
|
+
if not self._operations:
|
|
361
|
+
return "Pipeline(<empty>)"
|
|
362
|
+
return f"Pipeline({' -> '.join(step.name for step in self._operations)})"
|
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
# Pre-processing Module
|
|
2
2
|
# Provides image manipulation, filtering, and enhancement capabilities
|
|
3
|
+
#
|
|
4
|
+
# dataset_generator uses the optional mediapipe dependency, imported
|
|
5
|
+
# lazily inside its functions, so it is safe to import here regardless.
|
|
3
6
|
|
|
4
7
|
from . import blur
|
|
5
8
|
from . import contrast
|
|
6
9
|
from . import crop
|
|
10
|
+
from . import dataset_generator
|
|
7
11
|
from . import grayscale
|
|
8
12
|
from . import resize
|
|
9
13
|
from . import rotate
|
|
10
14
|
from . import sharpen
|
|
11
15
|
|
|
12
|
-
try: # dataset_generator needs the optional mediapipe dependency
|
|
13
|
-
from . import dataset_generator
|
|
14
|
-
except ImportError:
|
|
15
|
-
dataset_generator = None
|
|
16
|
-
|
|
17
16
|
__all__ = [
|
|
18
17
|
"blur",
|
|
19
18
|
"contrast",
|
|
@@ -12,7 +12,6 @@ if str(_src_path) not in sys.path:
|
|
|
12
12
|
sys.path.insert(0, str(_src_path))
|
|
13
13
|
|
|
14
14
|
import cv2
|
|
15
|
-
import mediapipe as mp
|
|
16
15
|
|
|
17
16
|
from ImagePRO.human_analysis.face_analysis.face_detection import detect_faces
|
|
18
17
|
from ImagePRO.pre_processing import blur, grayscale, resize, rotate, sharpen
|
|
@@ -95,6 +94,14 @@ def capture_bulk_pictures(
|
|
|
95
94
|
if not isinstance(delay, (int, float)) or delay < 0:
|
|
96
95
|
raise ValueError("'delay' must be a non-negative number")
|
|
97
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
|
+
|
|
98
105
|
# Setup output directory structure
|
|
99
106
|
base_dir = Path(folder_path)
|
|
100
107
|
face_folder = base_dir / str(face_id)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ImagePRO-Python
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.4.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
|
|
@@ -76,6 +76,11 @@ Whether you're working on computer vision pipelines, preprocessing images for AI
|
|
|
76
76
|
- **YOLO Integration**: Multiple accuracy levels (nano to extra-large)
|
|
77
77
|
- **Flexible Models**: Pre-trained or custom model support
|
|
78
78
|
|
|
79
|
+
### **Pipelines**
|
|
80
|
+
- **Reusable Operation Chains**: Define a sequence of functions once, then run it on any input (Image, NumPy array, or file path)
|
|
81
|
+
- **Flexible Steps**: Any ImagePRO function or custom callable; per-step keyword arguments via `Step`
|
|
82
|
+
- **Pipeline Metadata**: Every result lists the executed steps and each step's parameters
|
|
83
|
+
|
|
79
84
|
## Installation
|
|
80
85
|
|
|
81
86
|
### From PyPI
|
|
@@ -90,6 +95,8 @@ pip install "ImagePRO-Python[full]" # Everything
|
|
|
90
95
|
pip install "ImagePRO-Python[dev]" # Test tooling (pytest)
|
|
91
96
|
```
|
|
92
97
|
|
|
98
|
+
> **Note**: The base install is lightweight — AI backends (MediaPipe, Ultralytics, InsightFace) are optional and imported lazily, only when you call a function that actually needs them. `import ImagePRO` never loads these dependencies. If a backend is missing, the function you call raises an `ImportError` telling you exactly which extra to install.
|
|
99
|
+
|
|
93
100
|
### From Source
|
|
94
101
|
```bash
|
|
95
102
|
git clone https://github.com/parsasafaie/ImagePRO.git
|
|
@@ -152,6 +159,22 @@ object_detection_result = detect_objects(
|
|
|
152
159
|
)
|
|
153
160
|
print(f"Detected {len(object_detection_result.data)} objects")
|
|
154
161
|
object_detection_result.save_as_img("detections.jpg")
|
|
162
|
+
|
|
163
|
+
# Chain operations into a reusable pipeline
|
|
164
|
+
from ImagePRO.pipeline import Pipeline, Step
|
|
165
|
+
from ImagePRO.pre_processing.grayscale import convert_to_grayscale
|
|
166
|
+
from ImagePRO.pre_processing.resize import resize_image
|
|
167
|
+
|
|
168
|
+
pipeline = Pipeline([
|
|
169
|
+
Step(apply_gaussian_blur, kernel_size=(7, 7)),
|
|
170
|
+
convert_to_grayscale,
|
|
171
|
+
Step(resize_image, new_size=(800, 600)),
|
|
172
|
+
])
|
|
173
|
+
|
|
174
|
+
# Run it on any input: an Image, a NumPy array, or a file path
|
|
175
|
+
pipeline_result = pipeline.run("person_and_objects.jpg")
|
|
176
|
+
pipeline_result.save_as_img("processed_output.jpg")
|
|
177
|
+
print(pipeline_result.meta["steps"]) # Executed step names
|
|
155
178
|
```
|
|
156
179
|
|
|
157
180
|
> **Note**: These are basic examples. Each module contains many more functions with extensive customization options. Explore the module-specific README files for detailed documentation.
|
|
@@ -165,6 +188,7 @@ Each module includes comprehensive documentation with detailed examples:
|
|
|
165
188
|
- [Face Analysis](src/ImagePRO/human_analysis/face_analysis/README.md): Face mesh, pose estimation, eye status, comparison
|
|
166
189
|
- [Body Analysis](src/ImagePRO/human_analysis/body_analysis/README.md): Body pose and hand tracking
|
|
167
190
|
- **[Object Analysis](src/ImagePRO/object_analysis/README.md)**: YOLO-based object detection
|
|
191
|
+
- **[Pipeline](src/ImagePRO/pipeline/README.md)**: Reusable chains of image processing operations
|
|
168
192
|
- **[Utils](src/ImagePRO/utils/README.md)**: Shared utilities and I/O handling
|
|
169
193
|
|
|
170
194
|
For detailed project structure and development guidelines, see [PROJECT_STRUCTURE.md](PROJECT_STRUCTURE.md).
|
|
@@ -176,6 +200,8 @@ ImagePRO is built with a modular architecture designed for extensibility and mai
|
|
|
176
200
|
- **Clean Separation of Concerns**: Each module handles a specific domain
|
|
177
201
|
- **Consistent API Patterns**: All functions follow the same input/output conventions
|
|
178
202
|
- **Shared Utilities**: Common `Image` and `Result` classes for unified I/O
|
|
203
|
+
- **Reusable Pipelines**: Chain any operations into a `Pipeline` and run it on any input
|
|
204
|
+
- **Lazy Optional Imports**: Heavy AI dependencies (MediaPipe, Ultralytics, InsightFace) are only imported inside the functions that use them, keeping `import ImagePRO` fast and independent of installed extras
|
|
179
205
|
- **Professional Error Handling**: Comprehensive validation with clear error messages
|
|
180
206
|
- **Type Safety**: Full type hints throughout the codebase
|
|
181
207
|
- **Documentation**: Google-style docstrings for all functions
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
ImagePRO/__init__.py,sha256=3B8cQH4zhV6EEAvVMKYMsJHok_hpGLTgu4QfP33DV08,1081
|
|
2
|
+
ImagePRO/human_analysis/__init__.py,sha256=uSU0lM1p1HM6TNGv6u0nPHWy9QY505v43lqTkhClddM,190
|
|
3
|
+
ImagePRO/human_analysis/body_analysis/__init__.py,sha256=uUWG5eOxLQjZoYddmNkRlF66l6vSVyJpUFz1wDddXkk,374
|
|
4
|
+
ImagePRO/human_analysis/body_analysis/body_pose_estimation.py,sha256=yqEV18ZEZwV__Oqt1Hi72EGmbTNes-mrY068SBT9RbU,6245
|
|
5
|
+
ImagePRO/human_analysis/body_analysis/hand_tracking.py,sha256=SpdIZNgvaHzINpWu7rgNawSHvuuawzFtQ7JiHHQ0fqA,7578
|
|
6
|
+
ImagePRO/human_analysis/face_analysis/__init__.py,sha256=297IQuoO2Wb_PyigS_2NOuVnM7FpNPceu5jhJp6YdG0,604
|
|
7
|
+
ImagePRO/human_analysis/face_analysis/eye_status_analysis.py,sha256=Q_EUNPpXoF7Vi-Kd0gURNxs9X3U5J4Yi3coL_6lkeb8,7773
|
|
8
|
+
ImagePRO/human_analysis/face_analysis/face_comparison.py,sha256=lIr490PAf8R6C_Zhv-zBN1Un1EPiwxi4W7ahgkqXAVc,4724
|
|
9
|
+
ImagePRO/human_analysis/face_analysis/face_detection.py,sha256=F3Bq_0rAcQklmNAu-JvxGBIxUnWmyM-SHuZawHXJeac,4009
|
|
10
|
+
ImagePRO/human_analysis/face_analysis/face_mesh_analysis.py,sha256=rv08Mx_IBB-hjTh09bFaaPjaOm0tFbBs0brc4mE66mg,8723
|
|
11
|
+
ImagePRO/human_analysis/face_analysis/head_pose_estimation.py,sha256=2_ZVpwavyIzCIH4x3d2bvCPvv-X5gpmSYxtk5jlgCYw,7688
|
|
12
|
+
ImagePRO/object_analysis/__init__.py,sha256=eVQpYlHO89RYKT0CHjPfJjjif5X_Jx3AtAUpR5m0Yms,292
|
|
13
|
+
ImagePRO/object_analysis/object_detection.py,sha256=1qPd3Xfo2Vi4JnBs5wD8-k3WR6ZkYNdT9tco2Ze1ICQ,3385
|
|
14
|
+
ImagePRO/pipeline/__init__.py,sha256=-32hbR492qwxutXau7EfnysXbhW8fKKMywkV0ejIMFE,301
|
|
15
|
+
ImagePRO/pipeline/pipeline.py,sha256=-CRWfWXB_lFdjrqg3gdyYFFvq0vZAednrJnod6b4WYA,13802
|
|
16
|
+
ImagePRO/pre_processing/__init__.py,sha256=pw6-J5sdbkdBG4RlTxOzQKaspGnlPIGo3m_NXH2bVCE,559
|
|
17
|
+
ImagePRO/pre_processing/blur.py,sha256=3zNTNpvLZPdrtRtBziB6X8ys8M7cIvyPDhojTqR63jo,7950
|
|
18
|
+
ImagePRO/pre_processing/contrast.py,sha256=4UT78ggW1rh_iaEI8FObDB4kpEmRRFWjdJES60SnHDM,5064
|
|
19
|
+
ImagePRO/pre_processing/crop.py,sha256=3Nwerzo57kx1sZPr9m355P-qf1NuZSZEA5iUn2Q1704,2406
|
|
20
|
+
ImagePRO/pre_processing/dataset_generator.py,sha256=i7UM4o8hznFUfUgbYI6H01EkQJOqt4IsI9IrjzzN8gQ,7921
|
|
21
|
+
ImagePRO/pre_processing/grayscale.py,sha256=MRCsFRIaL45sYVHzhuNyZBXsP7PF-yorn0Wt7xONvpY,1781
|
|
22
|
+
ImagePRO/pre_processing/histogram.py,sha256=sNTWWZXr5VLpOrF_PTTJ6TD8yv1zR1l9cMyXHe7JFIM,2596
|
|
23
|
+
ImagePRO/pre_processing/resize.py,sha256=Rnde8l_PXck5OWQBEMOHgLeW2YkTz9Xfr6fGmz0sdm8,1760
|
|
24
|
+
ImagePRO/pre_processing/rotate.py,sha256=wQj3nQpN-ilM0HHwovN--UdGEJkPCY0BJ0zAdyBxv-o,4174
|
|
25
|
+
ImagePRO/pre_processing/sharpen.py,sha256=hOkY7QhzgMp9LXQwt56AP4enn24ntXlmFLe57Bxssvw,3682
|
|
26
|
+
ImagePRO/utils/__init__.py,sha256=McVd-o9KtKOpAVYbLi19KxsatPPFFF-Qbt0gdgY3Ln8,170
|
|
27
|
+
ImagePRO/utils/image.py,sha256=Y64mD97rTiJb8_uNYlyG227VZodeb4U1aGezMkj7MnM,4025
|
|
28
|
+
ImagePRO/utils/result.py,sha256=ToQaOmUlTtobJkC65BG0YO_6BkBleMBn-0Qydko7cZc,6138
|
|
29
|
+
imagepro_python-1.4.0.dist-info/licenses/LICENSE,sha256=sehkHN7dbe5umrINZJyJjIf9VTemZSgTfJlq6iIsbJQ,1069
|
|
30
|
+
imagepro_python-1.4.0.dist-info/METADATA,sha256=POgVUYD-3fKBCKxkjSgKTZqCt5BWjvtfDBqlrn7Qg9I,11121
|
|
31
|
+
imagepro_python-1.4.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
32
|
+
imagepro_python-1.4.0.dist-info/top_level.txt,sha256=UOuPzsoWrVKXgUupOSI96shJ6wE8Fhz-8bm8oMlLC3s,9
|
|
33
|
+
imagepro_python-1.4.0.dist-info/RECORD,,
|
|
@@ -1,31 +0,0 @@
|
|
|
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,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|