ImagePRO-Python 0.5.2__tar.gz → 1.0.1__tar.gz

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 (51) hide show
  1. {imagepro_python-0.5.2/src/ImagePRO_Python.egg-info → imagepro_python-1.0.1}/PKG-INFO +2 -1
  2. {imagepro_python-0.5.2 → imagepro_python-1.0.1}/pyproject.toml +3 -1
  3. {imagepro_python-0.5.2 → imagepro_python-1.0.1}/src/ImagePRO/__init__.py +1 -1
  4. imagepro_python-1.0.1/src/ImagePRO/human_analysis/body_analysis/body_pose_estimation.py +170 -0
  5. imagepro_python-1.0.1/src/ImagePRO/human_analysis/body_analysis/hand_tracking.py +202 -0
  6. imagepro_python-1.0.1/src/ImagePRO/human_analysis/face_analysis/eye_status_analysis.py +226 -0
  7. imagepro_python-1.0.1/src/ImagePRO/human_analysis/face_analysis/face_comparison.py +129 -0
  8. imagepro_python-1.0.1/src/ImagePRO/human_analysis/face_analysis/face_detection.py +121 -0
  9. imagepro_python-1.0.1/src/ImagePRO/human_analysis/face_analysis/face_mesh_analysis.py +237 -0
  10. imagepro_python-1.0.1/src/ImagePRO/human_analysis/face_analysis/head_pose_estimation.py +221 -0
  11. imagepro_python-1.0.1/src/ImagePRO/object_analysis/object_detection.py +95 -0
  12. imagepro_python-1.0.1/src/ImagePRO/pre_processing/blur.py +230 -0
  13. imagepro_python-1.0.1/src/ImagePRO/pre_processing/contrast.py +161 -0
  14. imagepro_python-1.0.1/src/ImagePRO/pre_processing/crop.py +72 -0
  15. imagepro_python-1.0.1/src/ImagePRO/pre_processing/dataset_generator.py +198 -0
  16. imagepro_python-1.0.1/src/ImagePRO/pre_processing/grayscale.py +50 -0
  17. imagepro_python-1.0.1/src/ImagePRO/pre_processing/resize.py +54 -0
  18. imagepro_python-1.0.1/src/ImagePRO/pre_processing/rotate.py +148 -0
  19. imagepro_python-1.0.1/src/ImagePRO/pre_processing/sharpen.py +115 -0
  20. {imagepro_python-0.5.2 → imagepro_python-1.0.1}/src/ImagePRO/utils/__init__.py +3 -2
  21. imagepro_python-1.0.1/src/ImagePRO/utils/image.py +118 -0
  22. imagepro_python-1.0.1/src/ImagePRO/utils/result.py +146 -0
  23. {imagepro_python-0.5.2 → imagepro_python-1.0.1/src/ImagePRO_Python.egg-info}/PKG-INFO +2 -1
  24. {imagepro_python-0.5.2 → imagepro_python-1.0.1}/src/ImagePRO_Python.egg-info/SOURCES.txt +2 -1
  25. {imagepro_python-0.5.2 → imagepro_python-1.0.1}/src/ImagePRO_Python.egg-info/requires.txt +1 -0
  26. imagepro_python-0.5.2/src/ImagePRO/human_analysis/body_analysis/body_pose_estimation.py +0 -138
  27. imagepro_python-0.5.2/src/ImagePRO/human_analysis/body_analysis/hand_tracking.py +0 -158
  28. imagepro_python-0.5.2/src/ImagePRO/human_analysis/face_analysis/eye_status_analysis.py +0 -186
  29. imagepro_python-0.5.2/src/ImagePRO/human_analysis/face_analysis/face_comparison.py +0 -66
  30. imagepro_python-0.5.2/src/ImagePRO/human_analysis/face_analysis/face_detection.py +0 -114
  31. imagepro_python-0.5.2/src/ImagePRO/human_analysis/face_analysis/face_mesh_analysis.py +0 -229
  32. imagepro_python-0.5.2/src/ImagePRO/human_analysis/face_analysis/head_pose_estimation.py +0 -181
  33. imagepro_python-0.5.2/src/ImagePRO/object_analysis/object_detection.py +0 -100
  34. imagepro_python-0.5.2/src/ImagePRO/pre_processing/blur.py +0 -212
  35. imagepro_python-0.5.2/src/ImagePRO/pre_processing/contrast.py +0 -159
  36. imagepro_python-0.5.2/src/ImagePRO/pre_processing/crop.py +0 -80
  37. imagepro_python-0.5.2/src/ImagePRO/pre_processing/dataset_generator.py +0 -186
  38. imagepro_python-0.5.2/src/ImagePRO/pre_processing/grayscale.py +0 -50
  39. imagepro_python-0.5.2/src/ImagePRO/pre_processing/resize.py +0 -62
  40. imagepro_python-0.5.2/src/ImagePRO/pre_processing/rotate.py +0 -119
  41. imagepro_python-0.5.2/src/ImagePRO/pre_processing/sharpen.py +0 -94
  42. imagepro_python-0.5.2/src/ImagePRO/utils/io_handler.py +0 -180
  43. {imagepro_python-0.5.2 → imagepro_python-1.0.1}/LICENSE +0 -0
  44. {imagepro_python-0.5.2 → imagepro_python-1.0.1}/setup.cfg +0 -0
  45. {imagepro_python-0.5.2 → imagepro_python-1.0.1}/src/ImagePRO/human_analysis/__init__.py +0 -0
  46. {imagepro_python-0.5.2 → imagepro_python-1.0.1}/src/ImagePRO/human_analysis/body_analysis/__init__.py +0 -0
  47. {imagepro_python-0.5.2 → imagepro_python-1.0.1}/src/ImagePRO/human_analysis/face_analysis/__init__.py +0 -0
  48. {imagepro_python-0.5.2 → imagepro_python-1.0.1}/src/ImagePRO/object_analysis/__init__.py +0 -0
  49. {imagepro_python-0.5.2 → imagepro_python-1.0.1}/src/ImagePRO/pre_processing/__init__.py +0 -0
  50. {imagepro_python-0.5.2 → imagepro_python-1.0.1}/src/ImagePRO_Python.egg-info/dependency_links.txt +0 -0
  51. {imagepro_python-0.5.2 → imagepro_python-1.0.1}/src/ImagePRO_Python.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ImagePRO-Python
3
- Version: 0.5.2
3
+ Version: 1.0.1
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
@@ -27,4 +27,5 @@ Requires-Dist: opencv-python<5.0,>=4.8
27
27
  Requires-Dist: ultralytics<9.0,>=8.2.0
28
28
  Requires-Dist: mediapipe<1.0,>=0.10.0
29
29
  Requires-Dist: insightface<1.0,>=0.7
30
+ Requires-Dist: onnxruntime<2.0,>=1.16
30
31
  Dynamic: license-file
@@ -7,7 +7,7 @@ where = ["src"]
7
7
 
8
8
  [project]
9
9
  name = "ImagePRO-Python"
10
- version = "0.5.2"
10
+ version = "1.0.1"
11
11
  description = "Professional & modular image processing library with OpenCV, MediaPipe, and YOLO integration."
12
12
  readme = "README.md"
13
13
  requires-python = "==3.10.*"
@@ -33,8 +33,10 @@ dependencies = [
33
33
  "ultralytics>=8.2.0,<9.0",
34
34
  "mediapipe>=0.10.0,<1.0",
35
35
  "insightface>=0.7,<1.0",
36
+ "onnxruntime>=1.16,<2.0",
36
37
  ]
37
38
 
39
+
38
40
  [project.urls]
39
41
  Homepage = "https://github.com/parsasafaie/ImagePRO"
40
42
  Repository = "https://github.com/parsasafaie/ImagePRO.git"
@@ -15,7 +15,7 @@ Features:
15
15
  - Real-time processing capabilities
16
16
  """
17
17
 
18
- __version__ = "0.5.2"
18
+ __version__ = "1.0.0"
19
19
  __author__ = "Parsa Safaie"
20
20
  __email__ = "parsasafaie.2568@proton.me"
21
21
 
@@ -0,0 +1,170 @@
1
+ from __future__ import annotations
2
+
3
+ import cv2
4
+ import mediapipe as mp
5
+
6
+ from ImagePRO.utils.image import Image
7
+ from ImagePRO.utils.result import Result
8
+
9
+ # Constants
10
+ mp_pose = mp.solutions.pose
11
+ TOTAL_LANDMARKS = 33
12
+ DEFAULT_CONFIDENCE = 0.7
13
+ LANDMARK_RADIUS = 3
14
+ LANDMARK_COLOR = (0, 0, 255) # Red color for landmarks
15
+
16
+ def detect_body_pose(
17
+ image: Image,
18
+ *,
19
+ min_confidence: float = DEFAULT_CONFIDENCE,
20
+ landmarks_idx: list[int] | None = None,
21
+ pose_obj: mp.solutions.pose.Pose | None = None
22
+ ) -> Result:
23
+ """Detect body landmarks in an image using MediaPipe Pose.
24
+
25
+ Extracts 3D body landmark coordinates and optionally draws them.
26
+ Can focus on specific landmarks or detect all 33 points.
27
+
28
+ Args:
29
+ image: Input image to process.
30
+ min_confidence: Detection confidence threshold.
31
+ Must be between 0 and 1.
32
+ Default: 0.7
33
+ landmarks_idx: Specific landmark indices to detect.
34
+ If None, detects all 33 landmarks.
35
+ Default: None
36
+ pose_obj: Pre-initialized pose detector.
37
+ If None, creates new instance.
38
+ Default: None
39
+
40
+ Returns:
41
+ Result object with detections and visualization:
42
+ - image: Input image with landmarks drawn
43
+ - data: List of [idx, x, y, z] coordinates
44
+ - meta: Operation info and parameters
45
+
46
+ Raises:
47
+ TypeError: If image is not an Image instance
48
+ ValueError: If min_confidence not in [0,1]
49
+ TypeError: If landmarks_idx is not list[int]
50
+ """
51
+ if not isinstance(image, Image):
52
+ raise TypeError("'image' must be an Image instance")
53
+
54
+ if not isinstance(min_confidence, (int, float)) or not (0.0 <= min_confidence <= 1.0):
55
+ raise ValueError("'min_confidence' must be between 0.0 and 1.0")
56
+
57
+ if landmarks_idx is not None and (
58
+ not isinstance(landmarks_idx, list)
59
+ or not all(isinstance(i, int) for i in landmarks_idx)
60
+ ):
61
+ raise TypeError("'landmarks_idx' must be a list of integers")
62
+
63
+ # Initialize detector if needed
64
+ if pose_obj is None:
65
+ pose_obj = mp_pose.Pose(
66
+ min_detection_confidence=min_confidence,
67
+ static_image_mode=True
68
+ )
69
+
70
+ # Use all landmarks if none specified
71
+ if landmarks_idx is None:
72
+ landmarks_idx = list(range(TOTAL_LANDMARKS))
73
+
74
+ # Prepare image
75
+ h, w = image.shape[:2]
76
+ img_copy = image._data.copy()
77
+ img_rgb = cv2.cvtColor(img_copy, cv2.COLOR_BGR2RGB)
78
+
79
+ # Detect pose
80
+ result = pose_obj.process(img_rgb)
81
+ landmarks = []
82
+
83
+ if result.pose_landmarks:
84
+ # Visualize landmarks
85
+ if len(landmarks_idx) == TOTAL_LANDMARKS:
86
+ # Draw full skeleton
87
+ mp.solutions.drawing_utils.draw_landmarks(
88
+ img_copy,
89
+ result.pose_landmarks,
90
+ mp_pose.POSE_CONNECTIONS,
91
+ landmark_drawing_spec=mp.solutions.drawing_styles.get_default_pose_landmarks_style()
92
+ )
93
+ else:
94
+ # Draw only selected points
95
+ for idx in landmarks_idx:
96
+ lm = result.pose_landmarks.landmark[idx]
97
+ x, y = int(w * lm.x), int(h * lm.y)
98
+ cv2.circle(img_copy, (x, y), LANDMARK_RADIUS, LANDMARK_COLOR, -1)
99
+
100
+ # Extract coordinates
101
+ for idx in landmarks_idx:
102
+ lm = result.pose_landmarks.landmark[idx]
103
+ landmarks.append([idx, lm.x, lm.y, lm.z])
104
+
105
+ return Result(
106
+ image=img_copy,
107
+ data=landmarks,
108
+ meta={
109
+ "source": image,
110
+ "operation": "detect_body_pose",
111
+ "min_confidence": min_confidence,
112
+ "landmarks_idx": landmarks_idx
113
+ }
114
+ )
115
+
116
+
117
+ def detect_body_pose_live() -> None:
118
+ """Start live webcam feed with real-time body pose detection.
119
+
120
+ Opens the default camera and shows pose landmarks in real-time.
121
+ Press ESC to exit the application.
122
+
123
+ Raises:
124
+ RuntimeError: If camera cannot be accessed
125
+ """
126
+ # Initialize camera
127
+ cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)
128
+ if not cap.isOpened():
129
+ raise RuntimeError("Cannot access webcam")
130
+
131
+ # Initialize pose detector in tracking mode
132
+ pose_obj = mp_pose.Pose(
133
+ min_detection_confidence=DEFAULT_CONFIDENCE,
134
+ static_image_mode=False
135
+ )
136
+
137
+ try:
138
+ while True:
139
+ # Get frame
140
+ success, frame = cap.read()
141
+ if not success:
142
+ print("Failed to capture frame, skipping...")
143
+ continue
144
+
145
+ # Process frame
146
+ try:
147
+ result = detect_body_pose(
148
+ image=Image.from_array(frame),
149
+ min_confidence=DEFAULT_CONFIDENCE,
150
+ pose_obj=pose_obj
151
+ )
152
+ display_frame = result.image
153
+ except (TypeError, ValueError):
154
+ # Fall back to raw frame if detection fails
155
+ display_frame = frame
156
+
157
+ # Show result
158
+ cv2.imshow('ImagePRO - Live Body Pose Detection', display_frame)
159
+
160
+ # Check for ESC key
161
+ if cv2.waitKey(5) & 0xFF == 27:
162
+ break
163
+ finally:
164
+ # Clean up resources
165
+ cap.release()
166
+ cv2.destroyAllWindows()
167
+
168
+
169
+ if __name__ == "__main__":
170
+ detect_body_pose_live()
@@ -0,0 +1,202 @@
1
+ from __future__ import annotations
2
+
3
+ import cv2
4
+ import mediapipe as mp
5
+
6
+ from ImagePRO.utils.image import Image
7
+ from ImagePRO.utils.result import Result
8
+
9
+ # Constants
10
+ mp_hands = mp.solutions.hands
11
+ DEFAULT_MAX_HANDS = 2
12
+ DEFAULT_MIN_CONFIDENCE = 0.7
13
+ TOTAL_HAND_LANDMARKS = 21
14
+ LANDMARK_RADIUS = 3
15
+ LANDMARK_COLOR = (0, 0, 255) # Red color for landmarks
16
+
17
+
18
+ def detect_hands(
19
+ image: Image,
20
+ *,
21
+ max_hands: int = DEFAULT_MAX_HANDS,
22
+ min_confidence: float = DEFAULT_MIN_CONFIDENCE,
23
+ landmarks_idx: list[int] | None = None,
24
+ hands_obj: mp.solutions.hands.Hands | None = None
25
+ ) -> Result:
26
+ """Detect hand landmarks in an image using MediaPipe Hands.
27
+
28
+ Identifies hand positions and extracts 3D coordinates of
29
+ key points (joints, fingertips, etc.).
30
+
31
+ Args:
32
+ image: Input image to process.
33
+ max_hands: Maximum number of hands to detect.
34
+ Must be positive.
35
+ Default: 2
36
+ min_confidence: Detection confidence threshold.
37
+ Must be between 0 and 1.
38
+ Default: 0.7
39
+ landmarks_idx: Specific landmark indices to detect.
40
+ If None, detects all 21 points per hand.
41
+ Default: None
42
+ hands_obj: Pre-initialized hand detector.
43
+ If None, creates new instance.
44
+ Default: None
45
+
46
+ Returns:
47
+ Result object with detections and visualization:
48
+ - image: Input image with landmarks drawn
49
+ - data: List of [hand_id, point_id, x, y, z]
50
+ - meta: Operation info and parameters
51
+
52
+ Raises:
53
+ TypeError: If image is not an Image instance
54
+ ValueError: If max_hands is not positive
55
+ ValueError: If min_confidence not in [0,1]
56
+ TypeError: If landmarks_idx is not list[int]
57
+ """
58
+ if not isinstance(image, Image):
59
+ raise TypeError("'image' must be an Image instance")
60
+
61
+ if not isinstance(max_hands, int) or max_hands <= 0:
62
+ raise ValueError("'max_hands' must be positive")
63
+
64
+ if not isinstance(min_confidence, (int, float)) or not (0.0 <= min_confidence <= 1.0):
65
+ raise ValueError("'min_confidence' must be between 0.0 and 1.0")
66
+
67
+ if landmarks_idx is not None and (
68
+ not isinstance(landmarks_idx, list)
69
+ or not all(isinstance(i, int) for i in landmarks_idx)
70
+ ):
71
+ raise TypeError("'landmarks_idx' must be a list of integers")
72
+
73
+ # Initialize detector if needed
74
+ if hands_obj is None:
75
+ hands_obj = mp_hands.Hands(
76
+ min_detection_confidence=min_confidence,
77
+ max_num_hands=max_hands,
78
+ static_image_mode=True
79
+ )
80
+
81
+ # Use all landmarks if none specified
82
+ if landmarks_idx is None:
83
+ landmarks_idx = list(range(TOTAL_HAND_LANDMARKS))
84
+
85
+ # Prepare image
86
+ img_copy = image._data.copy()
87
+ img_rgb = cv2.cvtColor(img_copy, cv2.COLOR_BGR2RGB)
88
+
89
+ # Detect hands
90
+ results = hands_obj.process(img_rgb)
91
+ landmarks = []
92
+
93
+ # Process detections
94
+ if results.multi_hand_landmarks:
95
+ for hand_id, hand_landmarks in enumerate(results.multi_hand_landmarks):
96
+ # Visualize landmarks
97
+ if len(landmarks_idx) == TOTAL_HAND_LANDMARKS:
98
+ # Draw full hand skeleton
99
+ mp.solutions.drawing_utils.draw_landmarks(
100
+ image=img_copy,
101
+ landmark_list=hand_landmarks,
102
+ connections=mp_hands.HAND_CONNECTIONS,
103
+ landmark_drawing_spec=mp.solutions.drawing_styles.get_default_hand_landmarks_style(),
104
+ connection_drawing_spec=mp.solutions.drawing_styles.get_default_hand_connections_style()
105
+ )
106
+ else:
107
+ # Draw only selected points
108
+ h, w, _ = img_copy.shape
109
+ for idx in landmarks_idx:
110
+ lm = hand_landmarks.landmark[idx]
111
+ x, y = int(w * lm.x), int(h * lm.y)
112
+ cv2.circle(img_copy, (x, y), LANDMARK_RADIUS, LANDMARK_COLOR, -1)
113
+
114
+ # Extract coordinates
115
+ for idx in landmarks_idx:
116
+ lm = hand_landmarks.landmark[idx]
117
+ landmarks.append([hand_id, idx, lm.x, lm.y, lm.z])
118
+
119
+ return Result(
120
+ image=img_copy,
121
+ data=landmarks,
122
+ meta={
123
+ "source": image,
124
+ "operation": "detect_hands",
125
+ "max_hands": max_hands,
126
+ "min_confidence": min_confidence,
127
+ "landmarks_idx": landmarks_idx
128
+ }
129
+ )
130
+
131
+
132
+ def detect_hands_live(
133
+ max_hands: int = DEFAULT_MAX_HANDS,
134
+ min_confidence: float = DEFAULT_MIN_CONFIDENCE
135
+ ) -> None:
136
+ """Start live webcam feed with real-time hand detection.
137
+
138
+ Opens the default camera and shows hand landmarks in real-time.
139
+ Press ESC to exit.
140
+
141
+ Args:
142
+ max_hands: Maximum number of hands to track.
143
+ Must be positive.
144
+ Default: 2
145
+ min_confidence: Detection confidence threshold.
146
+ Must be between 0 and 1.
147
+ Default: 0.7
148
+
149
+ Raises:
150
+ ValueError: If parameters are invalid
151
+ RuntimeError: If camera cannot be accessed
152
+ """
153
+ # Validate parameters
154
+ if not isinstance(max_hands, int) or max_hands <= 0:
155
+ raise ValueError("'max_hands' must be positive")
156
+
157
+ if not isinstance(min_confidence, (int, float)) or not (0.0 <= min_confidence <= 1.0):
158
+ raise ValueError("'min_confidence' must be between 0.0 and 1.0")
159
+
160
+ # Initialize camera
161
+ cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)
162
+ if not cap.isOpened():
163
+ raise RuntimeError("Cannot access webcam")
164
+
165
+ # Initialize hand detector in tracking mode
166
+ hands_obj = mp_hands.Hands(
167
+ min_detection_confidence=min_confidence,
168
+ max_num_hands=max_hands,
169
+ static_image_mode=False
170
+ )
171
+
172
+ try:
173
+ while True:
174
+ # Get frame
175
+ success, frame = cap.read()
176
+ if not success:
177
+ print("Failed to capture frame, skipping...")
178
+ continue
179
+
180
+ # Process frame
181
+ result = detect_hands(
182
+ image=Image.from_array(frame),
183
+ max_hands=max_hands,
184
+ min_confidence=min_confidence,
185
+ hands_obj=hands_obj
186
+ )
187
+ display_frame = result.image if result.image is not None else frame
188
+
189
+ # Show result
190
+ cv2.imshow('ImagePRO - Live Hand Detection', display_frame)
191
+
192
+ # Check for ESC key
193
+ if cv2.waitKey(5) & 0xFF == 27:
194
+ break
195
+ finally:
196
+ # Clean up resources
197
+ cap.release()
198
+ cv2.destroyAllWindows()
199
+
200
+
201
+ if __name__ == "__main__":
202
+ detect_hands_live()
@@ -0,0 +1,226 @@
1
+ from __future__ import annotations
2
+
3
+ import cv2
4
+ import mediapipe as mp
5
+
6
+ from ImagePRO.human_analysis.face_analysis.face_mesh_analysis import analyze_face_mesh
7
+ from ImagePRO.utils.image import Image
8
+ from ImagePRO.utils.result import Result
9
+
10
+ # Constants
11
+ mp_face_mesh = mp.solutions.face_mesh
12
+ DEFAULT_MIN_CONFIDENCE = 0.7
13
+ DEFAULT_THRESHOLD = 0.2
14
+ RIGHT_EYE_INDICES = [386, 374, 263, 362] # MediaPipe right eye landmarks
15
+
16
+
17
+ def analyze_eye_status(
18
+ image: Image,
19
+ *,
20
+ min_confidence: float = DEFAULT_MIN_CONFIDENCE,
21
+ threshold: float = DEFAULT_THRESHOLD,
22
+ face_mesh_obj: mp.solutions.face_mesh.FaceMesh | None = None,
23
+ ) -> Result:
24
+ """Analyze if the right eye is open using Eye Aspect Ratio (EAR).
25
+
26
+ Calculates eye openness by comparing vertical to horizontal distance
27
+ between key eye landmarks. Uses FaceMesh landmark detection.
28
+
29
+ Args:
30
+ image: Input image to process.
31
+ min_confidence: Detection confidence threshold.
32
+ Must be between 0 and 1.
33
+ Default: 0.7
34
+ threshold: EAR threshold for open vs closed.
35
+ Eye considered open if EAR > threshold.
36
+ Default: 0.2
37
+ face_mesh_obj: Pre-initialized face mesh detector.
38
+ If None, creates new instance in static mode.
39
+ Default: None
40
+
41
+ Returns:
42
+ Result object with eye status:
43
+ - data: True if eye is open, False if closed
44
+ None if detection fails
45
+ - meta: Operation info and error details if failed
46
+
47
+ Raises:
48
+ TypeError: If image is not an Image instance
49
+ ValueError: If min_confidence not in [0,1]
50
+ """
51
+ if not isinstance(image, Image):
52
+ raise TypeError("'image' must be an Image instance")
53
+
54
+ if not isinstance(min_confidence, (int, float)):
55
+ raise TypeError("'min_confidence' must be a number")
56
+ if not 0 <= min_confidence <= 1:
57
+ raise ValueError("'min_confidence' must be between 0 and 1")
58
+
59
+ # Get image dimensions
60
+ h, w = image.shape[:2]
61
+
62
+ # Initialize detector if needed
63
+ if face_mesh_obj is None:
64
+ face_mesh_obj = mp_face_mesh.FaceMesh(
65
+ min_detection_confidence=min_confidence,
66
+ refine_landmarks=True,
67
+ static_image_mode=True
68
+ )
69
+
70
+ # Get face landmarks
71
+ mesh_result = analyze_face_mesh(
72
+ image=image,
73
+ max_faces=1,
74
+ min_confidence=min_confidence,
75
+ landmarks_idx=RIGHT_EYE_INDICES,
76
+ face_mesh_obj=face_mesh_obj
77
+ )
78
+ landmarks = mesh_result.data
79
+
80
+ # Handle no detections
81
+ if not landmarks:
82
+ return Result(
83
+ image=None,
84
+ data=None,
85
+ meta={
86
+ "source": image,
87
+ "operation": "analyze_eye_status",
88
+ "min_confidence": min_confidence,
89
+ "threshold": threshold,
90
+ "error": "No face landmarks detected"
91
+ }
92
+ )
93
+
94
+ # Map landmarks by index
95
+ eye_points = {lm[1]: lm for lm in landmarks[0]}
96
+
97
+ try:
98
+ # Extract key points (scale to image dimensions)
99
+ top_y = eye_points[386][3] * h # Top of eye
100
+ bottom_y = eye_points[374][3] * h # Bottom of eye
101
+ left_x = eye_points[263][2] * w # Left corner
102
+ right_x = eye_points[362][2] * w # Right corner
103
+ except KeyError as e:
104
+ return Result(
105
+ image=None,
106
+ data=None,
107
+ meta={
108
+ "source": image,
109
+ "operation": "analyze_eye_status",
110
+ "min_confidence": min_confidence,
111
+ "threshold": threshold,
112
+ "error": f"Missing landmark: {e}"
113
+ }
114
+ )
115
+
116
+ # Calculate Eye Aspect Ratio (EAR)
117
+ vertical_dist = abs(bottom_y - top_y)
118
+ horizontal_dist = abs(right_x - left_x)
119
+
120
+ # Determine eye state
121
+ is_open = False
122
+ if horizontal_dist > 0: # Avoid division by zero
123
+ ear = vertical_dist / horizontal_dist
124
+ is_open = ear > threshold
125
+
126
+ return Result(
127
+ image=None,
128
+ data=is_open,
129
+ meta={
130
+ "source": image,
131
+ "operation": "analyze_eye_status",
132
+ "min_confidence": min_confidence,
133
+ "threshold": threshold
134
+ }
135
+ )
136
+
137
+
138
+ def analyze_eye_status_live(
139
+ *,
140
+ min_confidence: float = DEFAULT_MIN_CONFIDENCE,
141
+ threshold: float = DEFAULT_THRESHOLD
142
+ ) -> None:
143
+ """Run live eye status detection using webcam feed.
144
+
145
+ Opens a window displaying webcam feed with overlaid eye status.
146
+ Press ESC to exit.
147
+
148
+ Args:
149
+ min_confidence: Detection confidence threshold.
150
+ Must be between 0 and 1.
151
+ Default: 0.7
152
+ threshold: EAR threshold for open vs closed.
153
+ Eye considered open if EAR > threshold.
154
+ Default: 0.2
155
+
156
+ Raises:
157
+ TypeError: If min_confidence is not a number
158
+ ValueError: If min_confidence not in [0,1]
159
+ RuntimeError: If webcam cannot be accessed
160
+ """
161
+ # Validate inputs
162
+ if not isinstance(min_confidence, (int, float)):
163
+ raise TypeError("'min_confidence' must be a number")
164
+ if not 0 <= min_confidence <= 1:
165
+ raise ValueError("'min_confidence' must be between 0 and 1")
166
+
167
+ # Initialize webcam
168
+ cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)
169
+ if not cap.isOpened():
170
+ raise RuntimeError("Failed to access webcam")
171
+
172
+ # Initialize face mesh detector for video
173
+ face_mesh = mp_face_mesh.FaceMesh(
174
+ max_num_faces=1,
175
+ min_detection_confidence=min_confidence,
176
+ refine_landmarks=True,
177
+ static_image_mode=False # Optimize for video
178
+ )
179
+
180
+ try:
181
+ while True:
182
+ # Read frame
183
+ ret, frame = cap.read()
184
+ if not ret:
185
+ print("Warning: Failed to read frame, skipping...")
186
+ continue
187
+
188
+ # Process frame
189
+ try:
190
+ img = Image.from_array(frame)
191
+ result = analyze_eye_status(
192
+ image=img,
193
+ min_confidence=min_confidence,
194
+ face_mesh_obj=face_mesh,
195
+ threshold=threshold
196
+ )
197
+ status = "Open" if result.data else "Closed"
198
+ except (TypeError, ValueError):
199
+ status = "No face"
200
+
201
+ # Draw results
202
+ color = (0, 255, 0) if status == "Open" else (0, 0, 255)
203
+ cv2.putText(
204
+ frame,
205
+ f"Eye: {status}",
206
+ (10, 40),
207
+ cv2.FONT_HERSHEY_SIMPLEX,
208
+ 0.7,
209
+ color,
210
+ 2
211
+ )
212
+
213
+ # Display frame
214
+ cv2.imshow("ImagePRO - Eye Status", frame)
215
+
216
+ # Check for ESC key
217
+ if cv2.waitKey(5) & 0xFF == 27:
218
+ break
219
+
220
+ finally:
221
+ cap.release()
222
+ cv2.destroyAllWindows()
223
+
224
+
225
+ if __name__ == "__main__":
226
+ analyze_eye_status_live()