ImagePRO-Python 0.5.2__py3-none-any.whl → 1.0.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 (27) hide show
  1. ImagePRO/__init__.py +1 -1
  2. ImagePRO/human_analysis/body_analysis/body_pose_estimation.py +104 -72
  3. ImagePRO/human_analysis/body_analysis/hand_tracking.py +118 -74
  4. ImagePRO/human_analysis/face_analysis/eye_status_analysis.py +149 -109
  5. ImagePRO/human_analysis/face_analysis/face_comparison.py +100 -37
  6. ImagePRO/human_analysis/face_analysis/face_detection.py +83 -76
  7. ImagePRO/human_analysis/face_analysis/face_mesh_analysis.py +153 -145
  8. ImagePRO/human_analysis/face_analysis/head_pose_estimation.py +153 -113
  9. ImagePRO/object_analysis/object_detection.py +59 -64
  10. ImagePRO/pre_processing/blur.py +173 -155
  11. ImagePRO/pre_processing/contrast.py +129 -127
  12. ImagePRO/pre_processing/crop.py +45 -53
  13. ImagePRO/pre_processing/dataset_generator.py +97 -85
  14. ImagePRO/pre_processing/grayscale.py +42 -42
  15. ImagePRO/pre_processing/resize.py +41 -49
  16. ImagePRO/pre_processing/rotate.py +131 -102
  17. ImagePRO/pre_processing/sharpen.py +94 -73
  18. ImagePRO/utils/__init__.py +3 -2
  19. ImagePRO/utils/image.py +118 -0
  20. ImagePRO/utils/result.py +146 -0
  21. {imagepro_python-0.5.2.dist-info → imagepro_python-1.0.1.dist-info}/METADATA +2 -1
  22. imagepro_python-1.0.1.dist-info/RECORD +30 -0
  23. ImagePRO/utils/io_handler.py +0 -180
  24. imagepro_python-0.5.2.dist-info/RECORD +0 -29
  25. {imagepro_python-0.5.2.dist-info → imagepro_python-1.0.1.dist-info}/WHEEL +0 -0
  26. {imagepro_python-0.5.2.dist-info → imagepro_python-1.0.1.dist-info}/licenses/LICENSE +0 -0
  27. {imagepro_python-0.5.2.dist-info → imagepro_python-1.0.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__ = "0.5.2"
18
+ __version__ = "1.0.0"
19
19
  __author__ = "Parsa Safaie"
20
20
  __email__ = "parsasafaie.2568@proton.me"
21
21
 
@@ -1,14 +1,10 @@
1
- import sys
2
- from pathlib import Path
1
+ from __future__ import annotations
3
2
 
4
3
  import cv2
5
4
  import mediapipe as mp
6
5
 
7
- # Add parent directory to import custom modules
8
- parent_dir = Path(__file__).resolve().parent.parent.parent
9
- sys.path.append(str(parent_dir))
10
-
11
- from utils.io_handler import IOHandler
6
+ from ImagePRO.utils.image import Image
7
+ from ImagePRO.utils.result import Result
12
8
 
13
9
  # Constants
14
10
  mp_pose = mp.solutions.pose
@@ -18,96 +14,121 @@ LANDMARK_RADIUS = 3
18
14
  LANDMARK_COLOR = (0, 0, 255) # Red color for landmarks
19
15
 
20
16
  def detect_body_pose(
21
- model_accuracy: float = DEFAULT_CONFIDENCE,
22
- landmarks_idx: list | None = None,
23
- src_image_path: str | None = None,
24
- src_np_image=None,
25
- output_image_path: str | None = None,
26
- output_csv_path: str | None = None,
27
- pose_obj=None
28
- ):
29
- """
30
- Detect body landmarks from an image using MediaPipe Pose.
31
-
32
- Parameters
33
- ----------
34
- model_accuracy : float, default=0.7
35
- Minimum detection confidence [0, 1].
36
- landmarks_idx : list[int] | None, optional
37
- Indices of landmarks to extract. Default: all 33.
38
- src_image_path : str | None, optional
39
- Path to image file.
40
- src_np_image : np.ndarray | None, optional
41
- Preloaded BGR image array.
42
- output_image_path : str | None, optional
43
- If provided, saves annotated image.
44
- output_csv_path : str | None, optional
45
- If provided, saves landmarks CSV.
46
- pose_obj : mp.solutions.pose.Pose | None, optional
47
- Optional pre-initialized pose model.
48
-
49
- Returns
50
- -------
51
- tuple[np.ndarray, list]
52
- Annotated image and landmarks list.
53
-
54
- Raises
55
- ------
56
- ValueError, TypeError, FileNotFoundError, IOError
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]
57
50
  """
58
- if landmarks_idx is not None and (not isinstance(landmarks_idx, list) or not all(isinstance(i, int) for i in landmarks_idx)):
59
- raise TypeError("'landmarks_idx' must be a list of ints or None.")
51
+ if not isinstance(image, Image):
52
+ raise TypeError("'image' must be an Image instance")
60
53
 
61
- np_image = IOHandler.load_image(image_path=src_image_path, np_image=src_np_image)
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")
62
56
 
63
- if landmarks_idx is None:
64
- landmarks_idx = list(range(TOTAL_LANDMARKS))
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")
65
62
 
63
+ # Initialize detector if needed
66
64
  if pose_obj is None:
67
65
  pose_obj = mp_pose.Pose(
68
- min_detection_confidence=model_accuracy,
66
+ min_detection_confidence=min_confidence,
69
67
  static_image_mode=True
70
68
  )
71
69
 
72
- image_rgb = cv2.cvtColor(np_image, cv2.COLOR_BGR2RGB)
73
- result = pose_obj.process(image_rgb)
70
+ # Use all landmarks if none specified
71
+ if landmarks_idx is None:
72
+ landmarks_idx = list(range(TOTAL_LANDMARKS))
74
73
 
75
- annotated_image = np_image.copy()
76
- all_landmarks = []
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 = []
77
82
 
78
83
  if result.pose_landmarks:
84
+ # Visualize landmarks
79
85
  if len(landmarks_idx) == TOTAL_LANDMARKS:
86
+ # Draw full skeleton
80
87
  mp.solutions.drawing_utils.draw_landmarks(
81
- annotated_image,
88
+ img_copy,
82
89
  result.pose_landmarks,
83
90
  mp_pose.POSE_CONNECTIONS,
84
91
  landmark_drawing_spec=mp.solutions.drawing_styles.get_default_pose_landmarks_style()
85
92
  )
86
93
  else:
87
- h, w, _ = annotated_image.shape
94
+ # Draw only selected points
88
95
  for idx in landmarks_idx:
89
96
  lm = result.pose_landmarks.landmark[idx]
90
97
  x, y = int(w * lm.x), int(h * lm.y)
91
- cv2.circle(annotated_image, (x, y), LANDMARK_RADIUS, LANDMARK_COLOR, -1)
98
+ cv2.circle(img_copy, (x, y), LANDMARK_RADIUS, LANDMARK_COLOR, -1)
92
99
 
100
+ # Extract coordinates
93
101
  for idx in landmarks_idx:
94
102
  lm = result.pose_landmarks.landmark[idx]
95
- all_landmarks.append([idx, lm.x, lm.y, lm.z])
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
+ )
96
115
 
97
- if output_image_path:
98
- print(IOHandler.save_image(annotated_image, output_image_path))
99
- if output_csv_path:
100
- print(IOHandler.save_csv(all_landmarks, output_csv_path))
101
116
 
102
- return annotated_image, all_landmarks
117
+ def detect_body_pose_live() -> None:
118
+ """Start live webcam feed with real-time body pose detection.
103
119
 
120
+ Opens the default camera and shows pose landmarks in real-time.
121
+ Press ESC to exit the application.
104
122
 
105
- def detect_body_pose_live():
106
- """Starts webcam and shows real-time body pose detection. Press ESC to exit."""
123
+ Raises:
124
+ RuntimeError: If camera cannot be accessed
125
+ """
126
+ # Initialize camera
107
127
  cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)
108
128
  if not cap.isOpened():
109
- raise RuntimeError("Failed to open webcam.")
129
+ raise RuntimeError("Cannot access webcam")
110
130
 
131
+ # Initialize pose detector in tracking mode
111
132
  pose_obj = mp_pose.Pose(
112
133
  min_detection_confidence=DEFAULT_CONFIDENCE,
113
134
  static_image_mode=False
@@ -115,21 +136,32 @@ def detect_body_pose_live():
115
136
 
116
137
  try:
117
138
  while True:
139
+ # Get frame
118
140
  success, frame = cap.read()
119
141
  if not success:
120
- print("Ignoring empty camera frame.")
142
+ print("Failed to capture frame, skipping...")
121
143
  continue
122
144
 
145
+ # Process frame
123
146
  try:
124
- annotated_img, _ = detect_body_pose(src_np_image=frame, pose_obj=pose_obj)
125
- except ValueError:
126
- annotated_img = frame
127
-
128
- cv2.imshow('ImagePRO - Live Body Pose Detection', annotated_img)
129
-
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
130
161
  if cv2.waitKey(5) & 0xFF == 27:
131
162
  break
132
163
  finally:
164
+ # Clean up resources
133
165
  cap.release()
134
166
  cv2.destroyAllWindows()
135
167
 
@@ -1,14 +1,10 @@
1
- import sys
2
- from pathlib import Path
1
+ from __future__ import annotations
3
2
 
4
3
  import cv2
5
4
  import mediapipe as mp
6
5
 
7
- # Add parent directory to import custom modules
8
- parent_dir = Path(__file__).resolve().parent.parent.parent
9
- sys.path.append(str(parent_dir))
10
-
11
- from utils.io_handler import IOHandler
6
+ from ImagePRO.utils.image import Image
7
+ from ImagePRO.utils.result import Result
12
8
 
13
9
  # Constants
14
10
  mp_hands = mp.solutions.hands
@@ -20,55 +16,61 @@ LANDMARK_COLOR = (0, 0, 255) # Red color for landmarks
20
16
 
21
17
 
22
18
  def detect_hands(
19
+ image: Image,
20
+ *,
23
21
  max_hands: int = DEFAULT_MAX_HANDS,
24
22
  min_confidence: float = DEFAULT_MIN_CONFIDENCE,
25
- landmarks_idx: list | None = None,
26
- src_image_path: str | None = None,
27
- src_np_image=None,
28
- output_image_path: str | None = None,
29
- output_csv_path: str | None = None,
30
- hands_obj=None
31
- ):
32
- """
33
- Detect hand landmarks in an image using MediaPipe Hands.
34
-
35
- Parameters
36
- ----------
37
- max_hands : int, default=2
38
- Max number of hands to detect.
39
- min_confidence : float, default=0.7
40
- Minimum detection confidence [0, 1].
41
- landmarks_idx : list[int] | None, optional
42
- Landmark indices to extract (default: all 21).
43
- src_image_path : str | None, optional
44
- Path to image file.
45
- src_np_image : np.ndarray | None, optional
46
- Preloaded BGR image array.
47
- output_image_path : str | None, optional
48
- If provided, saves annotated image.
49
- output_csv_path : str | None, optional
50
- If provided, saves landmarks CSV.
51
- hands_obj : mp.solutions.hands.Hands | None, optional
52
- Optional pre-initialized model.
53
-
54
- Returns
55
- -------
56
- tuple[np.ndarray, list]
57
- Annotated image and landmarks list.
58
-
59
- Raises
60
- ------
61
- ValueError, TypeError, FileNotFoundError, IOError
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]
62
57
  """
58
+ if not isinstance(image, Image):
59
+ raise TypeError("'image' must be an Image instance")
60
+
63
61
  if not isinstance(max_hands, int) or max_hands <= 0:
64
- raise ValueError("'max_hands' must be a positive integer.")
62
+ raise ValueError("'max_hands' must be positive")
63
+
65
64
  if not isinstance(min_confidence, (int, float)) or not (0.0 <= min_confidence <= 1.0):
66
- raise ValueError("'min_confidence' must be a float between 0.0 and 1.0.")
67
- if landmarks_idx is not None and (not isinstance(landmarks_idx, list) or not all(isinstance(i, int) for i in landmarks_idx)):
68
- raise TypeError("'landmarks_idx' must be a list of ints or None.")
65
+ raise ValueError("'min_confidence' must be between 0.0 and 1.0")
69
66
 
70
- np_image = IOHandler.load_image(image_path=src_image_path, np_image=src_np_image)
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")
71
72
 
73
+ # Initialize detector if needed
72
74
  if hands_obj is None:
73
75
  hands_obj = mp_hands.Hands(
74
76
  min_detection_confidence=min_confidence,
@@ -76,55 +78,91 @@ def detect_hands(
76
78
  static_image_mode=True
77
79
  )
78
80
 
81
+ # Use all landmarks if none specified
79
82
  if landmarks_idx is None:
80
83
  landmarks_idx = list(range(TOTAL_HAND_LANDMARKS))
81
84
 
82
- rgb_image = cv2.cvtColor(np_image, cv2.COLOR_BGR2RGB)
83
- results = hands_obj.process(rgb_image)
85
+ # Prepare image
86
+ img_copy = image._data.copy()
87
+ img_rgb = cv2.cvtColor(img_copy, cv2.COLOR_BGR2RGB)
84
88
 
85
- annotated_image = np_image.copy()
86
- all_landmarks = []
89
+ # Detect hands
90
+ results = hands_obj.process(img_rgb)
91
+ landmarks = []
87
92
 
93
+ # Process detections
88
94
  if results.multi_hand_landmarks:
89
95
  for hand_id, hand_landmarks in enumerate(results.multi_hand_landmarks):
96
+ # Visualize landmarks
90
97
  if len(landmarks_idx) == TOTAL_HAND_LANDMARKS:
98
+ # Draw full hand skeleton
91
99
  mp.solutions.drawing_utils.draw_landmarks(
92
- image=annotated_image,
100
+ image=img_copy,
93
101
  landmark_list=hand_landmarks,
94
102
  connections=mp_hands.HAND_CONNECTIONS,
95
103
  landmark_drawing_spec=mp.solutions.drawing_styles.get_default_hand_landmarks_style(),
96
104
  connection_drawing_spec=mp.solutions.drawing_styles.get_default_hand_connections_style()
97
105
  )
98
106
  else:
99
- h, w, _ = annotated_image.shape
107
+ # Draw only selected points
108
+ h, w, _ = img_copy.shape
100
109
  for idx in landmarks_idx:
101
110
  lm = hand_landmarks.landmark[idx]
102
111
  x, y = int(w * lm.x), int(h * lm.y)
103
- cv2.circle(annotated_image, (x, y), LANDMARK_RADIUS, LANDMARK_COLOR, -1)
112
+ cv2.circle(img_copy, (x, y), LANDMARK_RADIUS, LANDMARK_COLOR, -1)
104
113
 
114
+ # Extract coordinates
105
115
  for idx in landmarks_idx:
106
116
  lm = hand_landmarks.landmark[idx]
107
- all_landmarks.append([hand_id, idx, lm.x, lm.y, lm.z])
108
-
109
- if output_image_path:
110
- print(IOHandler.save_image(annotated_image, output_image_path))
111
- if output_csv_path:
112
- print(IOHandler.save_csv(all_landmarks, output_csv_path))
113
-
114
- return annotated_image, all_landmarks
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
+ )
115
130
 
116
131
 
117
- def detect_hands_live(max_hands: int = DEFAULT_MAX_HANDS, min_confidence: float = DEFAULT_MIN_CONFIDENCE):
118
- """Real-time hand detection via webcam. Press ESC to exit."""
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
119
154
  if not isinstance(max_hands, int) or max_hands <= 0:
120
- raise ValueError("'max_hands' must be a positive integer.")
155
+ raise ValueError("'max_hands' must be positive")
156
+
121
157
  if not isinstance(min_confidence, (int, float)) or not (0.0 <= min_confidence <= 1.0):
122
- raise ValueError("'min_confidence' must be a float between 0.0 and 1.0.")
158
+ raise ValueError("'min_confidence' must be between 0.0 and 1.0")
123
159
 
160
+ # Initialize camera
124
161
  cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)
125
162
  if not cap.isOpened():
126
- raise RuntimeError("Failed to open webcam.")
163
+ raise RuntimeError("Cannot access webcam")
127
164
 
165
+ # Initialize hand detector in tracking mode
128
166
  hands_obj = mp_hands.Hands(
129
167
  min_detection_confidence=min_confidence,
130
168
  max_num_hands=max_hands,
@@ -133,26 +171,32 @@ def detect_hands_live(max_hands: int = DEFAULT_MAX_HANDS, min_confidence: float
133
171
 
134
172
  try:
135
173
  while True:
174
+ # Get frame
136
175
  success, frame = cap.read()
137
176
  if not success:
138
- print("Ignoring empty camera frame.")
177
+ print("Failed to capture frame, skipping...")
139
178
  continue
140
179
 
141
- annotated_img, _ = detect_hands(
180
+ # Process frame
181
+ result = detect_hands(
182
+ image=Image.from_array(frame),
142
183
  max_hands=max_hands,
143
184
  min_confidence=min_confidence,
144
- src_np_image=frame,
145
185
  hands_obj=hands_obj
146
186
  )
187
+ display_frame = result.image if result.image is not None else frame
147
188
 
148
- cv2.imshow('Live hand detector - ImagePRO', annotated_img)
189
+ # Show result
190
+ cv2.imshow('ImagePRO - Live Hand Detection', display_frame)
149
191
 
192
+ # Check for ESC key
150
193
  if cv2.waitKey(5) & 0xFF == 27:
151
194
  break
152
195
  finally:
196
+ # Clean up resources
153
197
  cap.release()
154
198
  cv2.destroyAllWindows()
155
199
 
156
200
 
157
201
  if __name__ == "__main__":
158
- detect_hands_live(max_hands=DEFAULT_MAX_HANDS)
202
+ detect_hands_live()