ImagePRO-Python 1.3.1__tar.gz → 1.5.0__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.
- {imagepro_python-1.3.1/src/ImagePRO_Python.egg-info → imagepro_python-1.5.0}/PKG-INFO +25 -2
- {imagepro_python-1.3.1 → imagepro_python-1.5.0}/pyproject.toml +1 -1
- {imagepro_python-1.3.1 → imagepro_python-1.5.0}/readme.md +24 -1
- {imagepro_python-1.3.1 → imagepro_python-1.5.0}/src/ImagePRO/__init__.py +5 -2
- {imagepro_python-1.3.1 → imagepro_python-1.5.0}/src/ImagePRO/human_analysis/body_analysis/body_pose_estimation.py +10 -4
- {imagepro_python-1.3.1 → imagepro_python-1.5.0}/src/ImagePRO/human_analysis/body_analysis/hand_tracking.py +13 -7
- {imagepro_python-1.3.1 → imagepro_python-1.5.0}/src/ImagePRO/human_analysis/face_analysis/eye_status_analysis.py +10 -5
- {imagepro_python-1.3.1 → imagepro_python-1.5.0}/src/ImagePRO/human_analysis/face_analysis/face_comparison.py +54 -61
- {imagepro_python-1.3.1 → imagepro_python-1.5.0}/src/ImagePRO/human_analysis/face_analysis/face_detection.py +3 -2
- {imagepro_python-1.3.1 → imagepro_python-1.5.0}/src/ImagePRO/human_analysis/face_analysis/face_mesh_analysis.py +20 -9
- {imagepro_python-1.3.1 → imagepro_python-1.5.0}/src/ImagePRO/human_analysis/face_analysis/head_pose_estimation.py +20 -7
- {imagepro_python-1.3.1 → imagepro_python-1.5.0}/src/ImagePRO/object_analysis/object_detection.py +12 -3
- imagepro_python-1.5.0/src/ImagePRO/pipeline/__init__.py +9 -0
- imagepro_python-1.5.0/src/ImagePRO/pipeline/pipeline.py +364 -0
- {imagepro_python-1.3.1 → imagepro_python-1.5.0}/src/ImagePRO/pre_processing/blur.py +13 -6
- {imagepro_python-1.3.1 → imagepro_python-1.5.0}/src/ImagePRO/pre_processing/contrast.py +31 -13
- {imagepro_python-1.3.1 → imagepro_python-1.5.0}/src/ImagePRO/pre_processing/crop.py +6 -3
- {imagepro_python-1.3.1 → imagepro_python-1.5.0}/src/ImagePRO/pre_processing/dataset_generator.py +93 -26
- {imagepro_python-1.3.1 → imagepro_python-1.5.0}/src/ImagePRO/pre_processing/histogram.py +16 -11
- {imagepro_python-1.3.1 → imagepro_python-1.5.0}/src/ImagePRO/pre_processing/resize.py +4 -1
- {imagepro_python-1.3.1 → imagepro_python-1.5.0}/src/ImagePRO/pre_processing/rotate.py +2 -2
- {imagepro_python-1.3.1 → imagepro_python-1.5.0}/src/ImagePRO/pre_processing/sharpen.py +20 -21
- {imagepro_python-1.3.1 → imagepro_python-1.5.0}/src/ImagePRO/utils/image.py +11 -4
- {imagepro_python-1.3.1 → imagepro_python-1.5.0}/src/ImagePRO/utils/result.py +20 -13
- {imagepro_python-1.3.1 → imagepro_python-1.5.0/src/ImagePRO_Python.egg-info}/PKG-INFO +25 -2
- {imagepro_python-1.3.1 → imagepro_python-1.5.0}/src/ImagePRO_Python.egg-info/SOURCES.txt +3 -0
- {imagepro_python-1.3.1 → imagepro_python-1.5.0}/tests/test_body_pose_estimation.py +9 -3
- {imagepro_python-1.3.1 → imagepro_python-1.5.0}/tests/test_contrast.py +32 -0
- {imagepro_python-1.3.1 → imagepro_python-1.5.0}/tests/test_crop.py +13 -0
- imagepro_python-1.5.0/tests/test_dataset_generator.py +190 -0
- {imagepro_python-1.3.1 → imagepro_python-1.5.0}/tests/test_eye_status_analysis.py +20 -13
- {imagepro_python-1.3.1 → imagepro_python-1.5.0}/tests/test_face_comparison.py +20 -5
- {imagepro_python-1.3.1 → imagepro_python-1.5.0}/tests/test_face_mesh_analysis.py +29 -0
- {imagepro_python-1.3.1 → imagepro_python-1.5.0}/tests/test_hand_tracking.py +7 -1
- {imagepro_python-1.3.1 → imagepro_python-1.5.0}/tests/test_head_pose_estimation.py +3 -1
- {imagepro_python-1.3.1 → imagepro_python-1.5.0}/tests/test_histogram.py +34 -0
- {imagepro_python-1.3.1 → imagepro_python-1.5.0}/tests/test_image.py +32 -0
- {imagepro_python-1.3.1 → imagepro_python-1.5.0}/tests/test_object_detection.py +4 -2
- imagepro_python-1.5.0/tests/test_pipeline.py +357 -0
- {imagepro_python-1.3.1 → imagepro_python-1.5.0}/tests/test_result.py +32 -0
- {imagepro_python-1.3.1 → imagepro_python-1.5.0}/tests/test_sharpen.py +52 -0
- imagepro_python-1.3.1/tests/test_dataset_generator.py +0 -72
- {imagepro_python-1.3.1 → imagepro_python-1.5.0}/LICENSE +0 -0
- {imagepro_python-1.3.1 → imagepro_python-1.5.0}/setup.cfg +0 -0
- {imagepro_python-1.3.1 → imagepro_python-1.5.0}/src/ImagePRO/human_analysis/__init__.py +0 -0
- {imagepro_python-1.3.1 → imagepro_python-1.5.0}/src/ImagePRO/human_analysis/body_analysis/__init__.py +0 -0
- {imagepro_python-1.3.1 → imagepro_python-1.5.0}/src/ImagePRO/human_analysis/face_analysis/__init__.py +0 -0
- {imagepro_python-1.3.1 → imagepro_python-1.5.0}/src/ImagePRO/object_analysis/__init__.py +0 -0
- {imagepro_python-1.3.1 → imagepro_python-1.5.0}/src/ImagePRO/pre_processing/__init__.py +0 -0
- {imagepro_python-1.3.1 → imagepro_python-1.5.0}/src/ImagePRO/pre_processing/grayscale.py +0 -0
- {imagepro_python-1.3.1 → imagepro_python-1.5.0}/src/ImagePRO/utils/__init__.py +0 -0
- {imagepro_python-1.3.1 → imagepro_python-1.5.0}/src/ImagePRO_Python.egg-info/dependency_links.txt +0 -0
- {imagepro_python-1.3.1 → imagepro_python-1.5.0}/src/ImagePRO_Python.egg-info/requires.txt +0 -0
- {imagepro_python-1.3.1 → imagepro_python-1.5.0}/src/ImagePRO_Python.egg-info/top_level.txt +0 -0
- {imagepro_python-1.3.1 → imagepro_python-1.5.0}/tests/test_blur.py +0 -0
- {imagepro_python-1.3.1 → imagepro_python-1.5.0}/tests/test_face_detection.py +0 -0
- {imagepro_python-1.3.1 → imagepro_python-1.5.0}/tests/test_grayscale.py +0 -0
- {imagepro_python-1.3.1 → imagepro_python-1.5.0}/tests/test_integration.py +0 -0
- {imagepro_python-1.3.1 → imagepro_python-1.5.0}/tests/test_resize.py +0 -0
- {imagepro_python-1.3.1 → imagepro_python-1.5.0}/tests/test_rotate.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ImagePRO-Python
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.5.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
|
|
@@ -154,6 +159,22 @@ object_detection_result = detect_objects(
|
|
|
154
159
|
)
|
|
155
160
|
print(f"Detected {len(object_detection_result.data)} objects")
|
|
156
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
|
|
157
178
|
```
|
|
158
179
|
|
|
159
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.
|
|
@@ -167,6 +188,7 @@ Each module includes comprehensive documentation with detailed examples:
|
|
|
167
188
|
- [Face Analysis](src/ImagePRO/human_analysis/face_analysis/README.md): Face mesh, pose estimation, eye status, comparison
|
|
168
189
|
- [Body Analysis](src/ImagePRO/human_analysis/body_analysis/README.md): Body pose and hand tracking
|
|
169
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
|
|
170
192
|
- **[Utils](src/ImagePRO/utils/README.md)**: Shared utilities and I/O handling
|
|
171
193
|
|
|
172
194
|
For detailed project structure and development guidelines, see [PROJECT_STRUCTURE.md](PROJECT_STRUCTURE.md).
|
|
@@ -178,13 +200,14 @@ ImagePRO is built with a modular architecture designed for extensibility and mai
|
|
|
178
200
|
- **Clean Separation of Concerns**: Each module handles a specific domain
|
|
179
201
|
- **Consistent API Patterns**: All functions follow the same input/output conventions
|
|
180
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
|
|
181
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
|
|
182
205
|
- **Professional Error Handling**: Comprehensive validation with clear error messages
|
|
183
206
|
- **Type Safety**: Full type hints throughout the codebase
|
|
184
207
|
- **Documentation**: Google-style docstrings for all functions
|
|
185
208
|
|
|
186
209
|
### Key Design Principles
|
|
187
|
-
- **Non-Destructive Operations**: Operations never write into the input image; they return results in new `Result` objects
|
|
210
|
+
- **Non-Destructive Operations**: Operations never write into the input image; they return results in new `Result` objects
|
|
188
211
|
- **Functional Style**: Stateless functions that can be easily composed
|
|
189
212
|
- **Result Objects**: Unified return type containing image, data, and metadata
|
|
190
213
|
- **Keyword Arguments**: All optional parameters use keyword-only syntax
|
|
@@ -7,7 +7,7 @@ where = ["src"]
|
|
|
7
7
|
|
|
8
8
|
[project]
|
|
9
9
|
name = "ImagePRO-Python"
|
|
10
|
-
version = "1.
|
|
10
|
+
version = "1.5.0"
|
|
11
11
|
description = "Professional & modular image processing library with OpenCV, MediaPipe, and YOLO integration."
|
|
12
12
|
readme = "readme.md"
|
|
13
13
|
requires-python = ">=3.9,<3.13"
|
|
@@ -27,6 +27,11 @@ Whether you're working on computer vision pipelines, preprocessing images for AI
|
|
|
27
27
|
- **YOLO Integration**: Multiple accuracy levels (nano to extra-large)
|
|
28
28
|
- **Flexible Models**: Pre-trained or custom model support
|
|
29
29
|
|
|
30
|
+
### **Pipelines**
|
|
31
|
+
- **Reusable Operation Chains**: Define a sequence of functions once, then run it on any input (Image, NumPy array, or file path)
|
|
32
|
+
- **Flexible Steps**: Any ImagePRO function or custom callable; per-step keyword arguments via `Step`
|
|
33
|
+
- **Pipeline Metadata**: Every result lists the executed steps and each step's parameters
|
|
34
|
+
|
|
30
35
|
## Installation
|
|
31
36
|
|
|
32
37
|
### From PyPI
|
|
@@ -105,6 +110,22 @@ object_detection_result = detect_objects(
|
|
|
105
110
|
)
|
|
106
111
|
print(f"Detected {len(object_detection_result.data)} objects")
|
|
107
112
|
object_detection_result.save_as_img("detections.jpg")
|
|
113
|
+
|
|
114
|
+
# Chain operations into a reusable pipeline
|
|
115
|
+
from ImagePRO.pipeline import Pipeline, Step
|
|
116
|
+
from ImagePRO.pre_processing.grayscale import convert_to_grayscale
|
|
117
|
+
from ImagePRO.pre_processing.resize import resize_image
|
|
118
|
+
|
|
119
|
+
pipeline = Pipeline([
|
|
120
|
+
Step(apply_gaussian_blur, kernel_size=(7, 7)),
|
|
121
|
+
convert_to_grayscale,
|
|
122
|
+
Step(resize_image, new_size=(800, 600)),
|
|
123
|
+
])
|
|
124
|
+
|
|
125
|
+
# Run it on any input: an Image, a NumPy array, or a file path
|
|
126
|
+
pipeline_result = pipeline.run("person_and_objects.jpg")
|
|
127
|
+
pipeline_result.save_as_img("processed_output.jpg")
|
|
128
|
+
print(pipeline_result.meta["steps"]) # Executed step names
|
|
108
129
|
```
|
|
109
130
|
|
|
110
131
|
> **Note**: These are basic examples. Each module contains many more functions with extensive customization options. Explore the module-specific README files for detailed documentation.
|
|
@@ -118,6 +139,7 @@ Each module includes comprehensive documentation with detailed examples:
|
|
|
118
139
|
- [Face Analysis](src/ImagePRO/human_analysis/face_analysis/README.md): Face mesh, pose estimation, eye status, comparison
|
|
119
140
|
- [Body Analysis](src/ImagePRO/human_analysis/body_analysis/README.md): Body pose and hand tracking
|
|
120
141
|
- **[Object Analysis](src/ImagePRO/object_analysis/README.md)**: YOLO-based object detection
|
|
142
|
+
- **[Pipeline](src/ImagePRO/pipeline/README.md)**: Reusable chains of image processing operations
|
|
121
143
|
- **[Utils](src/ImagePRO/utils/README.md)**: Shared utilities and I/O handling
|
|
122
144
|
|
|
123
145
|
For detailed project structure and development guidelines, see [PROJECT_STRUCTURE.md](PROJECT_STRUCTURE.md).
|
|
@@ -129,13 +151,14 @@ ImagePRO is built with a modular architecture designed for extensibility and mai
|
|
|
129
151
|
- **Clean Separation of Concerns**: Each module handles a specific domain
|
|
130
152
|
- **Consistent API Patterns**: All functions follow the same input/output conventions
|
|
131
153
|
- **Shared Utilities**: Common `Image` and `Result` classes for unified I/O
|
|
154
|
+
- **Reusable Pipelines**: Chain any operations into a `Pipeline` and run it on any input
|
|
132
155
|
- **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
|
|
133
156
|
- **Professional Error Handling**: Comprehensive validation with clear error messages
|
|
134
157
|
- **Type Safety**: Full type hints throughout the codebase
|
|
135
158
|
- **Documentation**: Google-style docstrings for all functions
|
|
136
159
|
|
|
137
160
|
### Key Design Principles
|
|
138
|
-
- **Non-Destructive Operations**: Operations never write into the input image; they return results in new `Result` objects
|
|
161
|
+
- **Non-Destructive Operations**: Operations never write into the input image; they return results in new `Result` objects
|
|
139
162
|
- **Functional Style**: Stateless functions that can be easily composed
|
|
140
163
|
- **Result Objects**: Unified return type containing image, data, and metadata
|
|
141
164
|
- **Keyword Arguments**: All optional parameters use keyword-only syntax
|
|
@@ -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.5.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
|
]
|
|
@@ -23,6 +23,11 @@ TOTAL_LANDMARKS = 33
|
|
|
23
23
|
DEFAULT_CONFIDENCE = 0.7
|
|
24
24
|
LANDMARK_RADIUS = 3
|
|
25
25
|
LANDMARK_COLOR = (0, 0, 255) # Red color for landmarks
|
|
26
|
+
# Backend-agnostic capture: the DSHOW backend only exists on Windows, so
|
|
27
|
+
# the default leaves the choice to OpenCV. Tests inject a fake through
|
|
28
|
+
# this hook.
|
|
29
|
+
DEFAULT_CAMERA_BACKEND = None
|
|
30
|
+
|
|
26
31
|
|
|
27
32
|
def detect_body_pose(
|
|
28
33
|
image: Image,
|
|
@@ -62,12 +67,13 @@ def detect_body_pose(
|
|
|
62
67
|
if not isinstance(image, Image):
|
|
63
68
|
raise TypeError("'image' must be an Image instance")
|
|
64
69
|
|
|
65
|
-
if
|
|
70
|
+
if isinstance(min_confidence, bool) or not isinstance(min_confidence, (int, float)) \
|
|
71
|
+
or not (0.0 <= min_confidence <= 1.0):
|
|
66
72
|
raise ValueError("'min_confidence' must be between 0.0 and 1.0")
|
|
67
73
|
|
|
68
74
|
if landmarks_idx is not None and (
|
|
69
75
|
not isinstance(landmarks_idx, list)
|
|
70
|
-
or not all(isinstance(i, int) for i in landmarks_idx)
|
|
76
|
+
or not all(isinstance(i, int) and not isinstance(i, bool) for i in landmarks_idx)
|
|
71
77
|
):
|
|
72
78
|
raise TypeError("'landmarks_idx' must be a list of integers")
|
|
73
79
|
|
|
@@ -153,8 +159,8 @@ def detect_body_pose_live() -> None:
|
|
|
153
159
|
'detection. Install it with: pip install "ImagePRO-Python[mediapipe]"'
|
|
154
160
|
) from err
|
|
155
161
|
|
|
156
|
-
# Initialize camera
|
|
157
|
-
cap = cv2.VideoCapture(0,
|
|
162
|
+
# Initialize camera (backend-agnostic; see DEFAULT_CAMERA_BACKEND)
|
|
163
|
+
cap = cv2.VideoCapture(0, DEFAULT_CAMERA_BACKEND)
|
|
158
164
|
if not cap.isOpened():
|
|
159
165
|
raise RuntimeError("Cannot access webcam")
|
|
160
166
|
|
|
@@ -24,6 +24,10 @@ DEFAULT_MIN_CONFIDENCE = 0.7
|
|
|
24
24
|
TOTAL_HAND_LANDMARKS = 21
|
|
25
25
|
LANDMARK_RADIUS = 3
|
|
26
26
|
LANDMARK_COLOR = (0, 0, 255) # Red color for landmarks
|
|
27
|
+
# Backend-agnostic capture: the DSHOW backend only exists on Windows, so
|
|
28
|
+
# the default leaves the choice to OpenCV. Tests inject a fake through
|
|
29
|
+
# this hook.
|
|
30
|
+
DEFAULT_CAMERA_BACKEND = None
|
|
27
31
|
|
|
28
32
|
|
|
29
33
|
def detect_hands(
|
|
@@ -69,15 +73,16 @@ def detect_hands(
|
|
|
69
73
|
if not isinstance(image, Image):
|
|
70
74
|
raise TypeError("'image' must be an Image instance")
|
|
71
75
|
|
|
72
|
-
if not isinstance(max_hands, int) or max_hands <= 0:
|
|
76
|
+
if not isinstance(max_hands, int) or isinstance(max_hands, bool) or max_hands <= 0:
|
|
73
77
|
raise ValueError("'max_hands' must be positive")
|
|
74
78
|
|
|
75
|
-
if
|
|
79
|
+
if isinstance(min_confidence, bool) or not isinstance(min_confidence, (int, float)) \
|
|
80
|
+
or not (0.0 <= min_confidence <= 1.0):
|
|
76
81
|
raise ValueError("'min_confidence' must be between 0.0 and 1.0")
|
|
77
82
|
|
|
78
83
|
if landmarks_idx is not None and (
|
|
79
84
|
not isinstance(landmarks_idx, list)
|
|
80
|
-
or not all(isinstance(i, int) for i in landmarks_idx)
|
|
85
|
+
or not all(isinstance(i, int) and not isinstance(i, bool) for i in landmarks_idx)
|
|
81
86
|
):
|
|
82
87
|
raise TypeError("'landmarks_idx' must be a list of integers")
|
|
83
88
|
|
|
@@ -174,10 +179,11 @@ def detect_hands_live(
|
|
|
174
179
|
RuntimeError: If camera cannot be accessed
|
|
175
180
|
"""
|
|
176
181
|
# Validate parameters
|
|
177
|
-
if not isinstance(max_hands, int) or max_hands <= 0:
|
|
182
|
+
if not isinstance(max_hands, int) or isinstance(max_hands, bool) or max_hands <= 0:
|
|
178
183
|
raise ValueError("'max_hands' must be positive")
|
|
179
184
|
|
|
180
|
-
if
|
|
185
|
+
if isinstance(min_confidence, bool) or not isinstance(min_confidence, (int, float)) \
|
|
186
|
+
or not (0.0 <= min_confidence <= 1.0):
|
|
181
187
|
raise ValueError("'min_confidence' must be between 0.0 and 1.0")
|
|
182
188
|
|
|
183
189
|
try:
|
|
@@ -188,8 +194,8 @@ def detect_hands_live(
|
|
|
188
194
|
'tracking. Install it with: pip install "ImagePRO-Python[mediapipe]"'
|
|
189
195
|
) from err
|
|
190
196
|
|
|
191
|
-
# Initialize camera
|
|
192
|
-
cap = cv2.VideoCapture(0,
|
|
197
|
+
# Initialize camera (backend-agnostic; see DEFAULT_CAMERA_BACKEND)
|
|
198
|
+
cap = cv2.VideoCapture(0, DEFAULT_CAMERA_BACKEND)
|
|
193
199
|
if not cap.isOpened():
|
|
194
200
|
raise RuntimeError("Cannot access webcam")
|
|
195
201
|
|
|
@@ -23,6 +23,10 @@ if TYPE_CHECKING: # mediapipe is imported lazily inside the functions
|
|
|
23
23
|
DEFAULT_MIN_CONFIDENCE = 0.7
|
|
24
24
|
DEFAULT_THRESHOLD = 0.2
|
|
25
25
|
RIGHT_EYE_INDICES = [386, 374, 263, 362] # MediaPipe right eye landmarks
|
|
26
|
+
# Backend-agnostic capture: the DSHOW backend only exists on Windows, so
|
|
27
|
+
# the default leaves the choice to OpenCV. Tests inject a fake through
|
|
28
|
+
# this hook.
|
|
29
|
+
DEFAULT_CAMERA_BACKEND = None
|
|
26
30
|
|
|
27
31
|
|
|
28
32
|
def analyze_eye_status(
|
|
@@ -62,7 +66,7 @@ def analyze_eye_status(
|
|
|
62
66
|
if not isinstance(image, Image):
|
|
63
67
|
raise TypeError("'image' must be an Image instance")
|
|
64
68
|
|
|
65
|
-
if not isinstance(min_confidence, (int, float)):
|
|
69
|
+
if isinstance(min_confidence, bool) or not isinstance(min_confidence, (int, float)):
|
|
66
70
|
raise TypeError("'min_confidence' must be a number")
|
|
67
71
|
if not 0 <= min_confidence <= 1:
|
|
68
72
|
raise ValueError("'min_confidence' must be between 0 and 1")
|
|
@@ -178,7 +182,7 @@ def analyze_eye_status_live(
|
|
|
178
182
|
RuntimeError: If webcam cannot be accessed
|
|
179
183
|
"""
|
|
180
184
|
# Validate inputs
|
|
181
|
-
if not isinstance(min_confidence, (int, float)):
|
|
185
|
+
if isinstance(min_confidence, bool) or not isinstance(min_confidence, (int, float)):
|
|
182
186
|
raise TypeError("'min_confidence' must be a number")
|
|
183
187
|
if not 0 <= min_confidence <= 1:
|
|
184
188
|
raise ValueError("'min_confidence' must be between 0 and 1")
|
|
@@ -191,8 +195,8 @@ def analyze_eye_status_live(
|
|
|
191
195
|
'analysis. Install it with: pip install "ImagePRO-Python[mediapipe]"'
|
|
192
196
|
) from err
|
|
193
197
|
|
|
194
|
-
# Initialize webcam
|
|
195
|
-
cap = cv2.VideoCapture(0,
|
|
198
|
+
# Initialize webcam (backend-agnostic; see DEFAULT_CAMERA_BACKEND)
|
|
199
|
+
cap = cv2.VideoCapture(0, DEFAULT_CAMERA_BACKEND)
|
|
196
200
|
if not cap.isOpened():
|
|
197
201
|
raise RuntimeError("Failed to access webcam")
|
|
198
202
|
|
|
@@ -221,7 +225,8 @@ def analyze_eye_status_live(
|
|
|
221
225
|
face_mesh_obj=face_mesh,
|
|
222
226
|
threshold=threshold
|
|
223
227
|
)
|
|
224
|
-
status = "
|
|
228
|
+
status = "No face" if result.data is None else \
|
|
229
|
+
("Open" if result.data else "Closed")
|
|
225
230
|
except (TypeError, ValueError):
|
|
226
231
|
status = "No face"
|
|
227
232
|
|
|
@@ -10,8 +10,6 @@ _src_path = _file_path.parents[3] # Go up to src directory
|
|
|
10
10
|
if str(_src_path) not in sys.path:
|
|
11
11
|
sys.path.insert(0, str(_src_path))
|
|
12
12
|
|
|
13
|
-
import os
|
|
14
|
-
|
|
15
13
|
import cv2
|
|
16
14
|
import numpy as np
|
|
17
15
|
|
|
@@ -36,7 +34,9 @@ def compare_faces(
|
|
|
36
34
|
"""Compare two face images to determine if they are the same person.
|
|
37
35
|
|
|
38
36
|
Uses InsightFace's FaceAnalysis model to extract facial embeddings
|
|
39
|
-
and compares them using cosine similarity.
|
|
37
|
+
and compares them using cosine similarity. Arrays are passed to the
|
|
38
|
+
model in memory; only path-based images are read from disk, so no
|
|
39
|
+
temporary files are written.
|
|
40
40
|
|
|
41
41
|
Args:
|
|
42
42
|
image_1: First image to compare.
|
|
@@ -57,7 +57,7 @@ def compare_faces(
|
|
|
57
57
|
|
|
58
58
|
Raises:
|
|
59
59
|
TypeError: If either image is not an Image instance
|
|
60
|
-
FileNotFoundError: If image
|
|
60
|
+
FileNotFoundError: If a path-based image cannot be loaded
|
|
61
61
|
"""
|
|
62
62
|
# Validate inputs
|
|
63
63
|
if not isinstance(image_1, Image):
|
|
@@ -82,69 +82,62 @@ def compare_faces(
|
|
|
82
82
|
)
|
|
83
83
|
app.prepare(ctx_id=0) # Use CPU
|
|
84
84
|
|
|
85
|
-
def
|
|
86
|
-
"""
|
|
85
|
+
def load_rgb_image(image: Image) -> np.ndarray:
|
|
86
|
+
"""Return the image as an RGB array, via disk only if required."""
|
|
87
87
|
if image.source_type == 'path':
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
# Validate detections
|
|
115
|
-
if not faces1 or not faces2:
|
|
116
|
-
return Result(
|
|
117
|
-
image=None,
|
|
118
|
-
data=None,
|
|
119
|
-
meta={
|
|
120
|
-
"source": (image_1, image_2),
|
|
121
|
-
"operation": "compare_faces",
|
|
122
|
-
"error": "No face detected in one or both images"
|
|
123
|
-
}
|
|
124
|
-
)
|
|
125
|
-
|
|
126
|
-
# Extract embeddings
|
|
127
|
-
emb1 = faces1[0].embedding
|
|
128
|
-
emb2 = faces2[0].embedding
|
|
129
|
-
|
|
130
|
-
# Calculate similarity
|
|
131
|
-
similarity = np.dot(emb1, emb2) / (np.linalg.norm(emb1) * np.linalg.norm(emb2))
|
|
132
|
-
is_match = similarity > DEFAULT_SIMILARITY_THRESHOLD
|
|
133
|
-
|
|
88
|
+
# honor the declared colorspace when reading from disk
|
|
89
|
+
img = cv2.imread(str(image.path))
|
|
90
|
+
if img is None:
|
|
91
|
+
raise FileNotFoundError(f"Failed to load image from {image.path}")
|
|
92
|
+
if image.colorspace == "RGB":
|
|
93
|
+
return img
|
|
94
|
+
if image.colorspace == "GRAY":
|
|
95
|
+
return cv2.cvtColor(img, cv2.COLOR_GRAY2RGB)
|
|
96
|
+
return cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
|
|
97
|
+
|
|
98
|
+
# InsightFace accepts in-memory arrays; no temp file is written.
|
|
99
|
+
if image.colorspace == "RGB":
|
|
100
|
+
return image._data
|
|
101
|
+
if image.colorspace == "GRAY":
|
|
102
|
+
return cv2.cvtColor(image._data, cv2.COLOR_GRAY2RGB)
|
|
103
|
+
return cv2.cvtColor(image._data, cv2.COLOR_BGR2RGB)
|
|
104
|
+
|
|
105
|
+
img1 = load_rgb_image(image_1)
|
|
106
|
+
img2 = load_rgb_image(image_2)
|
|
107
|
+
|
|
108
|
+
# Detect faces
|
|
109
|
+
faces1 = app.get(img1)
|
|
110
|
+
faces2 = app.get(img2)
|
|
111
|
+
|
|
112
|
+
# Validate detections
|
|
113
|
+
if not faces1 or not faces2:
|
|
134
114
|
return Result(
|
|
135
115
|
image=None,
|
|
136
|
-
data=
|
|
116
|
+
data=None,
|
|
137
117
|
meta={
|
|
138
118
|
"source": (image_1, image_2),
|
|
139
119
|
"operation": "compare_faces",
|
|
140
|
-
"
|
|
141
|
-
"threshold": DEFAULT_SIMILARITY_THRESHOLD
|
|
120
|
+
"error": "No face detected in one or both images"
|
|
142
121
|
}
|
|
143
122
|
)
|
|
144
123
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
124
|
+
# Extract embeddings
|
|
125
|
+
emb1 = faces1[0].embedding
|
|
126
|
+
emb2 = faces2[0].embedding
|
|
127
|
+
|
|
128
|
+
# Calculate similarity
|
|
129
|
+
similarity = np.dot(emb1, emb2) / (np.linalg.norm(emb1) * np.linalg.norm(emb2))
|
|
130
|
+
# Cast to a plain bool: np.dot yields np.bool_, which fails `is True`
|
|
131
|
+
# checks and serializes oddly in CSV/JSON output.
|
|
132
|
+
is_match = bool(similarity > DEFAULT_SIMILARITY_THRESHOLD)
|
|
133
|
+
|
|
134
|
+
return Result(
|
|
135
|
+
image=None,
|
|
136
|
+
data=is_match,
|
|
137
|
+
meta={
|
|
138
|
+
"source": (image_1, image_2),
|
|
139
|
+
"operation": "compare_faces",
|
|
140
|
+
"similarity": float(similarity),
|
|
141
|
+
"threshold": DEFAULT_SIMILARITY_THRESHOLD
|
|
142
|
+
}
|
|
143
|
+
)
|
|
@@ -72,10 +72,11 @@ def detect_faces(
|
|
|
72
72
|
if not isinstance(image, Image):
|
|
73
73
|
raise TypeError("'image' must be an Image instance")
|
|
74
74
|
|
|
75
|
-
if not isinstance(max_faces, int) or max_faces <= 0:
|
|
75
|
+
if not isinstance(max_faces, int) or isinstance(max_faces, bool) or max_faces <= 0:
|
|
76
76
|
raise ValueError("'max_faces' must be positive")
|
|
77
77
|
|
|
78
|
-
if
|
|
78
|
+
if isinstance(min_confidence, bool) or not isinstance(min_confidence, (int, float)) \
|
|
79
|
+
or not (0 <= min_confidence <= 1):
|
|
79
80
|
raise ValueError("'min_confidence' must be between 0 and 1")
|
|
80
81
|
|
|
81
82
|
# Get image dimensions
|
|
@@ -22,6 +22,10 @@ if TYPE_CHECKING: # mediapipe is imported lazily inside the functions
|
|
|
22
22
|
DEFAULT_MAX_FACES = 1
|
|
23
23
|
DEFAULT_MIN_CONFIDENCE = 0.7
|
|
24
24
|
TOTAL_FACE_LANDMARKS = 468
|
|
25
|
+
# Backend-agnostic capture: the DSHOW backend only exists on Windows, so
|
|
26
|
+
# the default leaves the choice to OpenCV. Tests inject a fake through
|
|
27
|
+
# this hook.
|
|
28
|
+
DEFAULT_CAMERA_BACKEND = None
|
|
25
29
|
|
|
26
30
|
|
|
27
31
|
def analyze_face_mesh(
|
|
@@ -38,7 +42,7 @@ def analyze_face_mesh(
|
|
|
38
42
|
Can focus on specific landmarks or detect full 468-point mesh.
|
|
39
43
|
|
|
40
44
|
Args:
|
|
41
|
-
image: Input image to process.
|
|
45
|
+
image: Input image to process (BGR, RGB, or grayscale).
|
|
42
46
|
max_faces: Maximum number of faces to detect.
|
|
43
47
|
Must be positive.
|
|
44
48
|
Default: 1
|
|
@@ -75,15 +79,16 @@ def analyze_face_mesh(
|
|
|
75
79
|
if not isinstance(image, Image):
|
|
76
80
|
raise TypeError("'image' must be an Image instance")
|
|
77
81
|
|
|
78
|
-
if not isinstance(max_faces, int) or max_faces <= 0:
|
|
82
|
+
if not isinstance(max_faces, int) or isinstance(max_faces, bool) or max_faces <= 0:
|
|
79
83
|
raise ValueError("'max_faces' must be positive")
|
|
80
84
|
|
|
81
|
-
if
|
|
85
|
+
if isinstance(min_confidence, bool) or not isinstance(min_confidence, (int, float)) \
|
|
86
|
+
or not (0 <= min_confidence <= 1):
|
|
82
87
|
raise ValueError("'min_confidence' must be between 0 and 1")
|
|
83
88
|
|
|
84
89
|
if landmarks_idx is not None and (
|
|
85
90
|
not isinstance(landmarks_idx, list)
|
|
86
|
-
or not all(isinstance(i, int) for i in landmarks_idx)
|
|
91
|
+
or not all(isinstance(i, int) and not isinstance(i, bool) for i in landmarks_idx)
|
|
87
92
|
):
|
|
88
93
|
raise TypeError("'landmarks_idx' must be a list of integers")
|
|
89
94
|
|
|
@@ -110,9 +115,14 @@ def analyze_face_mesh(
|
|
|
110
115
|
else:
|
|
111
116
|
face_mesh = face_mesh_obj
|
|
112
117
|
|
|
113
|
-
#
|
|
114
|
-
|
|
115
|
-
|
|
118
|
+
# MediaPipe expects RGB; grayscale input needs channels added back.
|
|
119
|
+
if image.colorspace == "GRAY":
|
|
120
|
+
model_input = cv2.cvtColor(image._data, cv2.COLOR_GRAY2RGB)
|
|
121
|
+
elif image.colorspace == "RGB":
|
|
122
|
+
model_input = image._data
|
|
123
|
+
else: # BGR is the default
|
|
124
|
+
model_input = cv2.cvtColor(image._data, cv2.COLOR_BGR2RGB)
|
|
125
|
+
results = face_mesh.process(model_input)
|
|
116
126
|
|
|
117
127
|
# Handle no detections
|
|
118
128
|
if not results.multi_face_landmarks:
|
|
@@ -214,8 +224,9 @@ def analyze_face_mesh_live(
|
|
|
214
224
|
'analysis. Install it with: pip install "ImagePRO-Python[mediapipe]"'
|
|
215
225
|
) from err
|
|
216
226
|
|
|
217
|
-
# Initialize camera
|
|
218
|
-
|
|
227
|
+
# Initialize camera (backend-agnostic; DEFAULT_CAMERA_BACKEND is a
|
|
228
|
+
# module-level hook tests can monkeypatch)
|
|
229
|
+
cap = cv2.VideoCapture(0, DEFAULT_CAMERA_BACKEND)
|
|
219
230
|
if not cap.isOpened():
|
|
220
231
|
raise RuntimeError("Cannot access webcam")
|
|
221
232
|
|
|
@@ -23,6 +23,10 @@ if TYPE_CHECKING: # mediapipe is imported lazily inside the functions
|
|
|
23
23
|
DEFAULT_MAX_FACES = 1
|
|
24
24
|
DEFAULT_MIN_CONFIDENCE = 0.7
|
|
25
25
|
HEAD_POSE_INDICES = [1, 152, 33, 263, 168] # nose_tip, chin, left_eye, right_eye, nasion
|
|
26
|
+
# Backend-agnostic capture: the DSHOW backend only exists on Windows, so
|
|
27
|
+
# the default leaves the choice to OpenCV. Tests inject a fake through
|
|
28
|
+
# this hook.
|
|
29
|
+
DEFAULT_CAMERA_BACKEND = None
|
|
26
30
|
|
|
27
31
|
|
|
28
32
|
def estimate_head_pose(
|
|
@@ -37,6 +41,12 @@ def estimate_head_pose(
|
|
|
37
41
|
Calculates approximate yaw and pitch angles based on relative
|
|
38
42
|
positions of key facial landmarks (nose, eyes, chin).
|
|
39
43
|
|
|
44
|
+
The values are unscaled proportional scores, not degrees: each is the
|
|
45
|
+
normalized landmark asymmetry multiplied by an arbitrary constant of
|
|
46
|
+
100. They are useful for thresholds and trends (e.g. detecting when a
|
|
47
|
+
face turns away), not for measuring absolute angles. Mirror-image
|
|
48
|
+
webcam feeds flip the sign of yaw.
|
|
49
|
+
|
|
40
50
|
Args:
|
|
41
51
|
image: Input image to process.
|
|
42
52
|
max_faces: Maximum number of faces to analyze.
|
|
@@ -55,6 +65,7 @@ def estimate_head_pose(
|
|
|
55
65
|
None if no faces detected
|
|
56
66
|
- meta: Operation info and parameters
|
|
57
67
|
Includes error info if detection fails
|
|
68
|
+
- image: None
|
|
58
69
|
|
|
59
70
|
Raises:
|
|
60
71
|
TypeError: If image is not an Image instance
|
|
@@ -64,10 +75,11 @@ def estimate_head_pose(
|
|
|
64
75
|
if not isinstance(image, Image):
|
|
65
76
|
raise TypeError("'image' must be an Image instance")
|
|
66
77
|
|
|
67
|
-
if not isinstance(max_faces, int) or max_faces <= 0:
|
|
78
|
+
if not isinstance(max_faces, int) or isinstance(max_faces, bool) or max_faces <= 0:
|
|
68
79
|
raise ValueError("'max_faces' must be positive")
|
|
69
80
|
|
|
70
|
-
if
|
|
81
|
+
if isinstance(min_confidence, bool) or not isinstance(min_confidence, (int, float)) \
|
|
82
|
+
or not (0 <= min_confidence <= 1):
|
|
71
83
|
raise ValueError("'min_confidence' must be between 0 and 1")
|
|
72
84
|
|
|
73
85
|
# Get face landmarks
|
|
@@ -120,7 +132,8 @@ def estimate_head_pose(
|
|
|
120
132
|
}
|
|
121
133
|
)
|
|
122
134
|
|
|
123
|
-
# Calculate angles
|
|
135
|
+
# Calculate angles. The ×100 constant is arbitrary; the values are
|
|
136
|
+
# proportional scores for thresholding, not degrees.
|
|
124
137
|
yaw = 100 * ((right_x - nasion_x) - (nasion_x - left_x)) # Horizontal rotation
|
|
125
138
|
pitch = 100 * ((chin_y - nose_y) - (nose_y - nasion_y)) # Vertical rotation
|
|
126
139
|
pose_data.append([face[0][0], yaw, pitch])
|
|
@@ -162,11 +175,11 @@ def estimate_head_pose_live(
|
|
|
162
175
|
RuntimeError: If webcam cannot be accessed
|
|
163
176
|
"""
|
|
164
177
|
# Validate inputs
|
|
165
|
-
if not isinstance(max_faces, int):
|
|
178
|
+
if not isinstance(max_faces, int) or isinstance(max_faces, bool):
|
|
166
179
|
raise TypeError("'max_faces' must be an integer")
|
|
167
180
|
if max_faces <= 0:
|
|
168
181
|
raise ValueError("'max_faces' must be positive")
|
|
169
|
-
if not isinstance(min_confidence, (int, float)):
|
|
182
|
+
if isinstance(min_confidence, bool) or not isinstance(min_confidence, (int, float)):
|
|
170
183
|
raise TypeError("'min_confidence' must be a number")
|
|
171
184
|
if not 0 <= min_confidence <= 1:
|
|
172
185
|
raise ValueError("'min_confidence' must be between 0 and 1")
|
|
@@ -179,8 +192,8 @@ def estimate_head_pose_live(
|
|
|
179
192
|
'estimation. Install it with: pip install "ImagePRO-Python[mediapipe]"'
|
|
180
193
|
) from err
|
|
181
194
|
|
|
182
|
-
# Initialize webcam
|
|
183
|
-
cap = cv2.VideoCapture(0,
|
|
195
|
+
# Initialize webcam (backend-agnostic; see DEFAULT_CAMERA_BACKEND)
|
|
196
|
+
cap = cv2.VideoCapture(0, DEFAULT_CAMERA_BACKEND)
|
|
184
197
|
if not cap.isOpened():
|
|
185
198
|
raise RuntimeError("Failed to access webcam")
|
|
186
199
|
|