ImagePRO-Python 1.2.2__py3-none-any.whl → 1.3.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. ImagePRO/__init__.py +2 -2
  2. ImagePRO/human_analysis/body_analysis/__init__.py +4 -0
  3. ImagePRO/human_analysis/body_analysis/body_pose_estimation.py +26 -5
  4. ImagePRO/human_analysis/body_analysis/hand_tracking.py +28 -6
  5. ImagePRO/human_analysis/face_analysis/__init__.py +5 -1
  6. ImagePRO/human_analysis/face_analysis/eye_status_analysis.py +22 -4
  7. ImagePRO/human_analysis/face_analysis/face_comparison.py +15 -3
  8. ImagePRO/human_analysis/face_analysis/face_detection.py +5 -2
  9. ImagePRO/human_analysis/face_analysis/face_mesh_analysis.py +36 -18
  10. ImagePRO/human_analysis/face_analysis/head_pose_estimation.py +14 -4
  11. ImagePRO/object_analysis/__init__.py +3 -0
  12. ImagePRO/object_analysis/object_detection.py +24 -13
  13. ImagePRO/pre_processing/__init__.py +4 -1
  14. ImagePRO/pre_processing/blur.py +10 -10
  15. ImagePRO/pre_processing/contrast.py +4 -7
  16. ImagePRO/pre_processing/crop.py +4 -5
  17. ImagePRO/pre_processing/dataset_generator.py +16 -10
  18. ImagePRO/pre_processing/histogram.py +36 -48
  19. ImagePRO/pre_processing/resize.py +2 -2
  20. ImagePRO/pre_processing/rotate.py +6 -7
  21. ImagePRO/pre_processing/sharpen.py +16 -13
  22. ImagePRO/utils/image.py +7 -6
  23. ImagePRO/utils/result.py +10 -7
  24. {imagepro_python-1.2.2.dist-info → imagepro_python-1.3.1.dist-info}/METADATA +46 -18
  25. imagepro_python-1.3.1.dist-info/RECORD +31 -0
  26. {imagepro_python-1.2.2.dist-info → imagepro_python-1.3.1.dist-info}/WHEEL +1 -1
  27. imagepro_python-1.2.2.dist-info/RECORD +0 -31
  28. {imagepro_python-1.2.2.dist-info → imagepro_python-1.3.1.dist-info}/licenses/LICENSE +0 -0
  29. {imagepro_python-1.2.2.dist-info → imagepro_python-1.3.1.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.0.0"
18
+ __version__ = "1.3.1"
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,5 +1,9 @@
1
1
  # Body Analysis Module
2
2
  # Provides body pose estimation and hand tracking capabilities
3
+ #
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.
3
7
 
4
8
  from . import body_pose_estimation
5
9
  from . import 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
- import mediapipe as mp
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
@@ -64,11 +66,21 @@ def detect_body_pose(
64
66
  raise ValueError("'min_confidence' must be between 0.0 and 1.0")
65
67
 
66
68
  if landmarks_idx is not None and (
67
- not isinstance(landmarks_idx, list)
69
+ not isinstance(landmarks_idx, list)
68
70
  or not all(isinstance(i, int) for i in landmarks_idx)
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(
@@ -97,7 +109,8 @@ def detect_body_pose(
97
109
  img_copy,
98
110
  result.pose_landmarks,
99
111
  mp_pose.POSE_CONNECTIONS,
100
- landmark_drawing_spec=mp.solutions.drawing_styles.get_default_pose_landmarks_style()
112
+ landmark_drawing_spec=mp.solutions.drawing_styles
113
+ .get_default_pose_landmarks_style()
101
114
  )
102
115
  else:
103
116
  # Draw only selected points
@@ -132,13 +145,21 @@ def detect_body_pose_live() -> None:
132
145
  Raises:
133
146
  RuntimeError: If camera cannot be accessed
134
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
+
135
156
  # Initialize camera
136
157
  cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)
137
158
  if not cap.isOpened():
138
159
  raise RuntimeError("Cannot access webcam")
139
160
 
140
161
  # Initialize pose detector in tracking mode
141
- pose_obj = mp_pose.Pose(
162
+ pose_obj = mp.solutions.pose.Pose(
142
163
  min_detection_confidence=DEFAULT_CONFIDENCE,
143
164
  static_image_mode=False
144
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
- import mediapipe as mp
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
@@ -74,11 +76,21 @@ def detect_hands(
74
76
  raise ValueError("'min_confidence' must be between 0.0 and 1.0")
75
77
 
76
78
  if landmarks_idx is not None and (
77
- not isinstance(landmarks_idx, list)
79
+ not isinstance(landmarks_idx, list)
78
80
  or not all(isinstance(i, int) for i in landmarks_idx)
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(
@@ -109,8 +121,10 @@ def detect_hands(
109
121
  image=img_copy,
110
122
  landmark_list=hand_landmarks,
111
123
  connections=mp_hands.HAND_CONNECTIONS,
112
- landmark_drawing_spec=mp.solutions.drawing_styles.get_default_hand_landmarks_style(),
113
- connection_drawing_spec=mp.solutions.drawing_styles.get_default_hand_connections_style()
124
+ landmark_drawing_spec=mp.solutions.drawing_styles
125
+ .get_default_hand_landmarks_style(),
126
+ connection_drawing_spec=mp.solutions.drawing_styles
127
+ .get_default_hand_connections_style()
114
128
  )
115
129
  else:
116
130
  # Draw only selected points
@@ -166,13 +180,21 @@ def detect_hands_live(
166
180
  if not isinstance(min_confidence, (int, float)) or not (0.0 <= min_confidence <= 1.0):
167
181
  raise ValueError("'min_confidence' must be between 0.0 and 1.0")
168
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
+
169
191
  # Initialize camera
170
192
  cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)
171
193
  if not cap.isOpened():
172
194
  raise RuntimeError("Cannot access webcam")
173
195
 
174
196
  # Initialize hand detector in tracking mode
175
- hands_obj = mp_hands.Hands(
197
+ hands_obj = mp.solutions.hands.Hands(
176
198
  min_detection_confidence=min_confidence,
177
199
  max_num_hands=max_hands,
178
200
  static_image_mode=False
@@ -1,5 +1,9 @@
1
1
  # Face Analysis Module
2
2
  # Provides facial landmark detection, pose estimation, and eye status analysis
3
+ #
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.
3
7
 
4
8
  from . import eye_status_analysis
5
9
  from . import face_comparison
@@ -10,7 +14,7 @@ from . import head_pose_estimation
10
14
  __all__ = [
11
15
  "eye_status_analysis",
12
16
  "face_comparison",
13
- "face_detection",
17
+ "face_detection",
14
18
  "face_mesh_analysis",
15
19
  "head_pose_estimation"
16
20
  ]
@@ -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
- import mediapipe as mp
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 = mp_face_mesh.FaceMesh(
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 = mp_face_mesh.FaceMesh(
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(
@@ -85,8 +97,8 @@ def compare_faces(
85
97
 
86
98
  try:
87
99
  # Save images to disk if needed
88
- path_1 = save_temp_image(image_1._data, 'tmp1.jpg')
89
- path_2 = save_temp_image(image_2._data, 'tmp2.jpg')
100
+ path_1 = save_temp_image(image_1, 'tmp1.jpg')
101
+ path_2 = save_temp_image(image_2, 'tmp2.jpg')
90
102
 
91
103
  # Load and preprocess images
92
104
  img1 = load_rgb_image(path_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()
@@ -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
@@ -29,7 +32,7 @@ FACE_OUTLINE_INDICES = [
29
32
 
30
33
 
31
34
  def detect_faces(
32
- image: Image,
35
+ image: Image,
33
36
  *,
34
37
  max_faces: int = DEFAULT_MAX_FACES,
35
38
  min_confidence: float = DEFAULT_MIN_CONFIDENCE,
@@ -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
- import mediapipe as mp
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")
@@ -83,11 +82,23 @@ def analyze_face_mesh(
83
82
  raise ValueError("'min_confidence' must be between 0 and 1")
84
83
 
85
84
  if landmarks_idx is not None and (
86
- not isinstance(landmarks_idx, list)
85
+ not isinstance(landmarks_idx, list)
87
86
  or not all(isinstance(i, int) for i in landmarks_idx)
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(
@@ -99,11 +110,8 @@ def analyze_face_mesh(
99
110
  else:
100
111
  face_mesh = face_mesh_obj
101
112
 
102
- # Prepare image
103
- img_copy = image._data.copy()
104
- img_rgb = cv2.cvtColor(img_copy, cv2.COLOR_BGR2RGB)
105
-
106
- # Detect facial landmarks
113
+ # Detect facial landmarks (cvtColor allocates a new RGB array)
114
+ img_rgb = cv2.cvtColor(image._data, cv2.COLOR_BGR2RGB)
107
115
  results = face_mesh.process(img_rgb)
108
116
 
109
117
  # Handle no detections
@@ -121,11 +129,14 @@ def analyze_face_mesh(
121
129
  }
122
130
  )
123
131
 
132
+ # Allocate the annotation canvas only when a face was found
133
+ img_copy = image._data.copy()
134
+
124
135
  # Use all landmarks if none specified
125
136
  landmarks_idx = landmarks_idx or list(range(TOTAL_FACE_LANDMARKS))
126
137
  # Process each detected face
127
138
  landmarks = []
128
-
139
+
129
140
  for face_id, face_landmarks in enumerate(results.multi_face_landmarks):
130
141
  # Draw landmarks
131
142
  if len(landmarks_idx) == TOTAL_FACE_LANDMARKS:
@@ -146,11 +157,10 @@ def analyze_face_mesh(
146
157
  cv2.circle(img_copy, (cx, cy), 3, (0, 0, 255), -1)
147
158
 
148
159
  # Extract coordinates
149
- face_data = [
150
- [face_id, idx, lm.x, lm.y, lm.z]
151
- for idx in landmarks_idx
152
- for lm in [face_landmarks.landmark[idx]]
153
- ]
160
+ face_data = []
161
+ for idx in landmarks_idx:
162
+ lm = face_landmarks.landmark[idx]
163
+ face_data.append([face_id, idx, lm.x, lm.y, lm.z])
154
164
  landmarks.append(face_data)
155
165
 
156
166
  return Result(
@@ -196,13 +206,21 @@ def analyze_face_mesh_live(
196
206
  if not isinstance(min_confidence, (int, float)) or not (0 <= min_confidence <= 1):
197
207
  raise ValueError("'min_confidence' must be between 0 and 1")
198
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
+
199
217
  # Initialize camera
200
218
  cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)
201
219
  if not cap.isOpened():
202
220
  raise RuntimeError("Cannot access webcam")
203
221
 
204
222
  # Initialize detector in tracking mode for better performance
205
- face_mesh = mp_face_mesh.FaceMesh(
223
+ face_mesh = mp.solutions.face_mesh.FaceMesh(
206
224
  max_num_faces=max_faces,
207
225
  min_detection_confidence=min_confidence,
208
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
- import mediapipe as mp
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
@@ -97,7 +99,7 @@ def estimate_head_pose(
97
99
  for face in landmarks:
98
100
  # Map landmarks by index
99
101
  points = {lm[1]: lm for lm in face}
100
-
102
+
101
103
  try:
102
104
  # Extract key points
103
105
  nose_x, nose_y = points[1][2:4] # Nose tip
@@ -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 = mp_face_mesh.FaceMesh(
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,5 +1,8 @@
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
7
  from . import object_detection
5
8
 
@@ -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,17 +10,17 @@ _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
- DEFAULT_CONFIDENCE = 0.5
20
21
  MODEL_MAPPING = {
21
22
  1: "yolo11n.pt",
22
- 2: "yolo11s.pt",
23
+ 2: "yolo11s.pt",
23
24
  3: "yolo11m.pt",
24
25
  4: "yolo11l.pt",
25
26
  5: "yolo11x.pt"
@@ -56,6 +57,7 @@ def detect_objects(
56
57
  Result object with detections and metadata:
57
58
  - image: Original with bounding boxes drawn
58
59
  - data: List of [class_id, [x1,y1,x2,y2], confidence]
60
+ with box coordinates normalized to [0, 1]
59
61
  - meta: Operation info and model used
60
62
 
61
63
  Raises:
@@ -65,20 +67,29 @@ 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
 
80
- # Run inference
81
- result = model(image._data.copy())[0]
91
+ # Run inference (ultralytics does not mutate the input array)
92
+ result = model(image._data)[0]
82
93
  boxes = result.boxes
83
94
 
84
95
  # Process detections
@@ -92,7 +103,7 @@ def detect_objects(
92
103
  # Show visualization if requested
93
104
  if show_result:
94
105
  result.show()
95
-
106
+
96
107
  return Result(
97
108
  image=result.plot(),
98
109
  data=detections,
@@ -1,5 +1,8 @@
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
@@ -12,7 +15,7 @@ from . import sharpen
12
15
 
13
16
  __all__ = [
14
17
  "blur",
15
- "contrast",
18
+ "contrast",
16
19
  "crop",
17
20
  "dataset_generator",
18
21
  "grayscale",