matrice-analytics 0.1.60__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.
- matrice_analytics/__init__.py +28 -0
- matrice_analytics/boundary_drawing_internal/README.md +305 -0
- matrice_analytics/boundary_drawing_internal/__init__.py +45 -0
- matrice_analytics/boundary_drawing_internal/boundary_drawing_internal.py +1207 -0
- matrice_analytics/boundary_drawing_internal/boundary_drawing_tool.py +429 -0
- matrice_analytics/boundary_drawing_internal/boundary_tool_template.html +1036 -0
- matrice_analytics/boundary_drawing_internal/data/.gitignore +12 -0
- matrice_analytics/boundary_drawing_internal/example_usage.py +206 -0
- matrice_analytics/boundary_drawing_internal/usage/README.md +110 -0
- matrice_analytics/boundary_drawing_internal/usage/boundary_drawer_launcher.py +102 -0
- matrice_analytics/boundary_drawing_internal/usage/simple_boundary_launcher.py +107 -0
- matrice_analytics/post_processing/README.md +455 -0
- matrice_analytics/post_processing/__init__.py +732 -0
- matrice_analytics/post_processing/advanced_tracker/README.md +650 -0
- matrice_analytics/post_processing/advanced_tracker/__init__.py +17 -0
- matrice_analytics/post_processing/advanced_tracker/base.py +99 -0
- matrice_analytics/post_processing/advanced_tracker/config.py +77 -0
- matrice_analytics/post_processing/advanced_tracker/kalman_filter.py +370 -0
- matrice_analytics/post_processing/advanced_tracker/matching.py +195 -0
- matrice_analytics/post_processing/advanced_tracker/strack.py +230 -0
- matrice_analytics/post_processing/advanced_tracker/tracker.py +367 -0
- matrice_analytics/post_processing/config.py +146 -0
- matrice_analytics/post_processing/core/__init__.py +63 -0
- matrice_analytics/post_processing/core/base.py +704 -0
- matrice_analytics/post_processing/core/config.py +3291 -0
- matrice_analytics/post_processing/core/config_utils.py +925 -0
- matrice_analytics/post_processing/face_reg/__init__.py +43 -0
- matrice_analytics/post_processing/face_reg/compare_similarity.py +556 -0
- matrice_analytics/post_processing/face_reg/embedding_manager.py +950 -0
- matrice_analytics/post_processing/face_reg/face_recognition.py +2234 -0
- matrice_analytics/post_processing/face_reg/face_recognition_client.py +606 -0
- matrice_analytics/post_processing/face_reg/people_activity_logging.py +321 -0
- matrice_analytics/post_processing/ocr/__init__.py +0 -0
- matrice_analytics/post_processing/ocr/easyocr_extractor.py +250 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/__init__.py +9 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/__init__.py +4 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/cli.py +33 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/dataset_stats.py +139 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/export.py +398 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/train.py +447 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/utils.py +129 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/valid.py +93 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/validate_dataset.py +240 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/visualize_augmentation.py +176 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/visualize_predictions.py +96 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/core/__init__.py +3 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/core/process.py +246 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/core/types.py +60 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/core/utils.py +87 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/inference/__init__.py +3 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/inference/config.py +82 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/inference/hub.py +141 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/inference/plate_recognizer.py +323 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/py.typed +0 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/__init__.py +0 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/data/__init__.py +0 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/data/augmentation.py +101 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/data/dataset.py +97 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/__init__.py +0 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/config.py +114 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/layers.py +553 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/loss.py +55 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/metric.py +86 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/model_builders.py +95 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/model_schema.py +395 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/utilities/__init__.py +0 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/utilities/backend_utils.py +38 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/utilities/utils.py +214 -0
- matrice_analytics/post_processing/ocr/postprocessing.py +270 -0
- matrice_analytics/post_processing/ocr/preprocessing.py +52 -0
- matrice_analytics/post_processing/post_processor.py +1175 -0
- matrice_analytics/post_processing/test_cases/__init__.py +1 -0
- matrice_analytics/post_processing/test_cases/run_tests.py +143 -0
- matrice_analytics/post_processing/test_cases/test_advanced_customer_service.py +841 -0
- matrice_analytics/post_processing/test_cases/test_basic_counting_tracking.py +523 -0
- matrice_analytics/post_processing/test_cases/test_comprehensive.py +531 -0
- matrice_analytics/post_processing/test_cases/test_config.py +852 -0
- matrice_analytics/post_processing/test_cases/test_customer_service.py +585 -0
- matrice_analytics/post_processing/test_cases/test_data_generators.py +583 -0
- matrice_analytics/post_processing/test_cases/test_people_counting.py +510 -0
- matrice_analytics/post_processing/test_cases/test_processor.py +524 -0
- matrice_analytics/post_processing/test_cases/test_usecases.py +165 -0
- matrice_analytics/post_processing/test_cases/test_utilities.py +356 -0
- matrice_analytics/post_processing/test_cases/test_utils.py +743 -0
- matrice_analytics/post_processing/usecases/Histopathological_Cancer_Detection_img.py +604 -0
- matrice_analytics/post_processing/usecases/__init__.py +267 -0
- matrice_analytics/post_processing/usecases/abandoned_object_detection.py +797 -0
- matrice_analytics/post_processing/usecases/advanced_customer_service.py +1601 -0
- matrice_analytics/post_processing/usecases/age_detection.py +842 -0
- matrice_analytics/post_processing/usecases/age_gender_detection.py +1085 -0
- matrice_analytics/post_processing/usecases/anti_spoofing_detection.py +656 -0
- matrice_analytics/post_processing/usecases/assembly_line_detection.py +841 -0
- matrice_analytics/post_processing/usecases/banana_defect_detection.py +624 -0
- matrice_analytics/post_processing/usecases/basic_counting_tracking.py +667 -0
- matrice_analytics/post_processing/usecases/blood_cancer_detection_img.py +881 -0
- matrice_analytics/post_processing/usecases/car_damage_detection.py +834 -0
- matrice_analytics/post_processing/usecases/car_part_segmentation.py +946 -0
- matrice_analytics/post_processing/usecases/car_service.py +1601 -0
- matrice_analytics/post_processing/usecases/cardiomegaly_classification.py +864 -0
- matrice_analytics/post_processing/usecases/cell_microscopy_segmentation.py +897 -0
- matrice_analytics/post_processing/usecases/chicken_pose_detection.py +648 -0
- matrice_analytics/post_processing/usecases/child_monitoring.py +814 -0
- matrice_analytics/post_processing/usecases/color/clip.py +660 -0
- matrice_analytics/post_processing/usecases/color/clip_processor/merges.txt +48895 -0
- matrice_analytics/post_processing/usecases/color/clip_processor/preprocessor_config.json +28 -0
- matrice_analytics/post_processing/usecases/color/clip_processor/special_tokens_map.json +30 -0
- matrice_analytics/post_processing/usecases/color/clip_processor/tokenizer.json +245079 -0
- matrice_analytics/post_processing/usecases/color/clip_processor/tokenizer_config.json +32 -0
- matrice_analytics/post_processing/usecases/color/clip_processor/vocab.json +1 -0
- matrice_analytics/post_processing/usecases/color/color_map_utils.py +70 -0
- matrice_analytics/post_processing/usecases/color/color_mapper.py +468 -0
- matrice_analytics/post_processing/usecases/color_detection.py +1936 -0
- matrice_analytics/post_processing/usecases/color_map_utils.py +70 -0
- matrice_analytics/post_processing/usecases/concrete_crack_detection.py +827 -0
- matrice_analytics/post_processing/usecases/crop_weed_detection.py +781 -0
- matrice_analytics/post_processing/usecases/customer_service.py +1008 -0
- matrice_analytics/post_processing/usecases/defect_detection_products.py +936 -0
- matrice_analytics/post_processing/usecases/distracted_driver_detection.py +822 -0
- matrice_analytics/post_processing/usecases/drone_traffic_monitoring.py +585 -0
- matrice_analytics/post_processing/usecases/drowsy_driver_detection.py +829 -0
- matrice_analytics/post_processing/usecases/dwell_detection.py +829 -0
- matrice_analytics/post_processing/usecases/emergency_vehicle_detection.py +827 -0
- matrice_analytics/post_processing/usecases/face_emotion.py +813 -0
- matrice_analytics/post_processing/usecases/face_recognition.py +827 -0
- matrice_analytics/post_processing/usecases/fashion_detection.py +835 -0
- matrice_analytics/post_processing/usecases/field_mapping.py +902 -0
- matrice_analytics/post_processing/usecases/fire_detection.py +1146 -0
- matrice_analytics/post_processing/usecases/flare_analysis.py +836 -0
- matrice_analytics/post_processing/usecases/flower_segmentation.py +1006 -0
- matrice_analytics/post_processing/usecases/gas_leak_detection.py +837 -0
- matrice_analytics/post_processing/usecases/gender_detection.py +832 -0
- matrice_analytics/post_processing/usecases/human_activity_recognition.py +871 -0
- matrice_analytics/post_processing/usecases/intrusion_detection.py +1672 -0
- matrice_analytics/post_processing/usecases/leaf.py +821 -0
- matrice_analytics/post_processing/usecases/leaf_disease.py +840 -0
- matrice_analytics/post_processing/usecases/leak_detection.py +837 -0
- matrice_analytics/post_processing/usecases/license_plate_detection.py +1188 -0
- matrice_analytics/post_processing/usecases/license_plate_monitoring.py +1781 -0
- matrice_analytics/post_processing/usecases/litter_monitoring.py +717 -0
- matrice_analytics/post_processing/usecases/mask_detection.py +869 -0
- matrice_analytics/post_processing/usecases/natural_disaster.py +907 -0
- matrice_analytics/post_processing/usecases/parking.py +787 -0
- matrice_analytics/post_processing/usecases/parking_space_detection.py +822 -0
- matrice_analytics/post_processing/usecases/pcb_defect_detection.py +888 -0
- matrice_analytics/post_processing/usecases/pedestrian_detection.py +808 -0
- matrice_analytics/post_processing/usecases/people_counting.py +706 -0
- matrice_analytics/post_processing/usecases/people_counting_bckp.py +1683 -0
- matrice_analytics/post_processing/usecases/people_tracking.py +1842 -0
- matrice_analytics/post_processing/usecases/pipeline_detection.py +605 -0
- matrice_analytics/post_processing/usecases/plaque_segmentation_img.py +874 -0
- matrice_analytics/post_processing/usecases/pothole_segmentation.py +915 -0
- matrice_analytics/post_processing/usecases/ppe_compliance.py +645 -0
- matrice_analytics/post_processing/usecases/price_tag_detection.py +822 -0
- matrice_analytics/post_processing/usecases/proximity_detection.py +1901 -0
- matrice_analytics/post_processing/usecases/road_lane_detection.py +623 -0
- matrice_analytics/post_processing/usecases/road_traffic_density.py +832 -0
- matrice_analytics/post_processing/usecases/road_view_segmentation.py +915 -0
- matrice_analytics/post_processing/usecases/shelf_inventory_detection.py +583 -0
- matrice_analytics/post_processing/usecases/shoplifting_detection.py +822 -0
- matrice_analytics/post_processing/usecases/shopping_cart_analysis.py +899 -0
- matrice_analytics/post_processing/usecases/skin_cancer_classification_img.py +864 -0
- matrice_analytics/post_processing/usecases/smoker_detection.py +833 -0
- matrice_analytics/post_processing/usecases/solar_panel.py +810 -0
- matrice_analytics/post_processing/usecases/suspicious_activity_detection.py +1030 -0
- matrice_analytics/post_processing/usecases/template_usecase.py +380 -0
- matrice_analytics/post_processing/usecases/theft_detection.py +648 -0
- matrice_analytics/post_processing/usecases/traffic_sign_monitoring.py +724 -0
- matrice_analytics/post_processing/usecases/underground_pipeline_defect_detection.py +775 -0
- matrice_analytics/post_processing/usecases/underwater_pollution_detection.py +842 -0
- matrice_analytics/post_processing/usecases/vehicle_monitoring.py +1029 -0
- matrice_analytics/post_processing/usecases/warehouse_object_segmentation.py +899 -0
- matrice_analytics/post_processing/usecases/waterbody_segmentation.py +923 -0
- matrice_analytics/post_processing/usecases/weapon_detection.py +771 -0
- matrice_analytics/post_processing/usecases/weld_defect_detection.py +615 -0
- matrice_analytics/post_processing/usecases/wildlife_monitoring.py +898 -0
- matrice_analytics/post_processing/usecases/windmill_maintenance.py +834 -0
- matrice_analytics/post_processing/usecases/wound_segmentation.py +856 -0
- matrice_analytics/post_processing/utils/__init__.py +150 -0
- matrice_analytics/post_processing/utils/advanced_counting_utils.py +400 -0
- matrice_analytics/post_processing/utils/advanced_helper_utils.py +317 -0
- matrice_analytics/post_processing/utils/advanced_tracking_utils.py +461 -0
- matrice_analytics/post_processing/utils/alerting_utils.py +213 -0
- matrice_analytics/post_processing/utils/category_mapping_utils.py +94 -0
- matrice_analytics/post_processing/utils/color_utils.py +592 -0
- matrice_analytics/post_processing/utils/counting_utils.py +182 -0
- matrice_analytics/post_processing/utils/filter_utils.py +261 -0
- matrice_analytics/post_processing/utils/format_utils.py +293 -0
- matrice_analytics/post_processing/utils/geometry_utils.py +300 -0
- matrice_analytics/post_processing/utils/smoothing_utils.py +358 -0
- matrice_analytics/post_processing/utils/tracking_utils.py +234 -0
- matrice_analytics/py.typed +0 -0
- matrice_analytics-0.1.60.dist-info/METADATA +481 -0
- matrice_analytics-0.1.60.dist-info/RECORD +196 -0
- matrice_analytics-0.1.60.dist-info/WHEEL +5 -0
- matrice_analytics-0.1.60.dist-info/licenses/LICENSE.txt +21 -0
- matrice_analytics-0.1.60.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,606 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Facial Recognition API - Python Client for Post-Processing
|
|
4
|
+
|
|
5
|
+
This client handles vector search and enrollment operations for face recognition
|
|
6
|
+
in the post-processing pipeline using Matrice Session.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
import base64
|
|
11
|
+
import logging
|
|
12
|
+
import httpx
|
|
13
|
+
import urllib
|
|
14
|
+
import urllib.request
|
|
15
|
+
from typing import List, Dict, Any, Optional
|
|
16
|
+
from datetime import datetime, timezone
|
|
17
|
+
|
|
18
|
+
# Import matrice session
|
|
19
|
+
try:
|
|
20
|
+
from matrice_common.session import Session
|
|
21
|
+
HAS_MATRICE_SESSION = True
|
|
22
|
+
except ImportError:
|
|
23
|
+
HAS_MATRICE_SESSION = False
|
|
24
|
+
logging.warning("Matrice session not available")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class FacialRecognitionClient:
|
|
28
|
+
"""
|
|
29
|
+
Simplified Face Recognition Client using Matrice Session.
|
|
30
|
+
All API calls are made through the Matrice session RPC interface.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
def __init__(self, account_number: str = "", access_key: str = "", secret_key: str = "",
|
|
34
|
+
project_id: str = "", server_id: str = "", session=None):
|
|
35
|
+
|
|
36
|
+
# Set up logging
|
|
37
|
+
self.logger = logging.getLogger(__name__)
|
|
38
|
+
|
|
39
|
+
self.server_id = server_id
|
|
40
|
+
if not self.server_id:
|
|
41
|
+
raise ValueError("Server ID is required for Face Recognition Client")
|
|
42
|
+
|
|
43
|
+
self.server_info = None
|
|
44
|
+
self.server_base_url = None
|
|
45
|
+
self.public_ip = self._get_public_ip()
|
|
46
|
+
|
|
47
|
+
# Use existing session if provided, otherwise create new one
|
|
48
|
+
if session is not None:
|
|
49
|
+
self.session = session
|
|
50
|
+
# Get project_id from session or parameter
|
|
51
|
+
self.project_id = getattr(session, 'project_id', '') or project_id or os.getenv("MATRICE_PROJECT_ID", "")
|
|
52
|
+
self.logger.info("Using existing Matrice session for face recognition client")
|
|
53
|
+
else:
|
|
54
|
+
# Initialize credentials from environment if not provided
|
|
55
|
+
self.account_number = account_number or os.getenv("MATRICE_ACCOUNT_NUMBER", "")
|
|
56
|
+
self.access_key = access_key or os.getenv("MATRICE_ACCESS_KEY_ID", "")
|
|
57
|
+
self.secret_key = secret_key or os.getenv("MATRICE_SECRET_ACCESS_KEY", "")
|
|
58
|
+
self.project_id = project_id or os.getenv("MATRICE_PROJECT_ID", "")
|
|
59
|
+
|
|
60
|
+
# Initialize Matrice session
|
|
61
|
+
if not HAS_MATRICE_SESSION:
|
|
62
|
+
raise ImportError("Matrice session is required for Face Recognition Client")
|
|
63
|
+
|
|
64
|
+
# if not all([self.account_number, self.access_key, self.secret_key]):
|
|
65
|
+
# raise ValueError("Missing required credentials: account_number, access_key, secret_key")
|
|
66
|
+
|
|
67
|
+
try:
|
|
68
|
+
self.session = Session(
|
|
69
|
+
account_number=self.account_number,
|
|
70
|
+
access_key=self.access_key,
|
|
71
|
+
secret_key=self.secret_key,
|
|
72
|
+
project_id=self.project_id,
|
|
73
|
+
)
|
|
74
|
+
self.logger.info("Initialized new Matrice session for face recognition client")
|
|
75
|
+
except Exception as e:
|
|
76
|
+
self.logger.error(f"Failed to initialize Matrice session: {e}", exc_info=True)
|
|
77
|
+
raise
|
|
78
|
+
|
|
79
|
+
# Fetch server connection info if server_id is provided
|
|
80
|
+
if self.server_id:
|
|
81
|
+
try:
|
|
82
|
+
self.server_info = self.get_server_connection_info()
|
|
83
|
+
if self.server_info:
|
|
84
|
+
self.logger.info(f"Successfully fetched facial recognition server info: {self.server_info.get('name', 'Unknown')}")
|
|
85
|
+
# Compare server host with public IP to determine if it's localhost
|
|
86
|
+
server_host = self.server_info.get('host', 'localhost')
|
|
87
|
+
server_port = self.server_info.get('port', 8081)
|
|
88
|
+
|
|
89
|
+
if server_host == self.public_ip:
|
|
90
|
+
self.server_base_url = f"http://localhost:{server_port}"
|
|
91
|
+
self.logger.warning(f"Server host matches public IP, using localhost: {self.server_base_url}")
|
|
92
|
+
else:
|
|
93
|
+
self.server_base_url = f"http://{server_host}:{server_port}"
|
|
94
|
+
self.logger.warning(f"Facial recognition server base URL: {self.server_base_url}")
|
|
95
|
+
|
|
96
|
+
self.session.update(self.server_info.get('projectID', ''))
|
|
97
|
+
self.logger.info(f"Updated Matrice session with project ID: {self.server_info.get('projectID', '')}")
|
|
98
|
+
else:
|
|
99
|
+
self.logger.warning("Failed to fetch facial recognition server connection info")
|
|
100
|
+
except Exception as e:
|
|
101
|
+
self.logger.error(f"Error fetching facial recognition server connection info: {e}", exc_info=True)
|
|
102
|
+
|
|
103
|
+
def _get_public_ip(self) -> str:
|
|
104
|
+
"""Get the public IP address of this machine."""
|
|
105
|
+
try:
|
|
106
|
+
public_ip = urllib.request.urlopen("https://v4.ident.me", timeout=120).read().decode("utf8").strip()
|
|
107
|
+
self.logger.warning(f"Successfully fetched external IP: {public_ip}")
|
|
108
|
+
return public_ip
|
|
109
|
+
except Exception as e:
|
|
110
|
+
self.logger.error(f"Error fetching external IP: {e}", exc_info=True)
|
|
111
|
+
return "localhost"
|
|
112
|
+
|
|
113
|
+
def get_server_connection_info(self) -> Optional[Dict[str, Any]]:
|
|
114
|
+
"""Fetch server connection info from RPC."""
|
|
115
|
+
if not self.server_id:
|
|
116
|
+
return None
|
|
117
|
+
|
|
118
|
+
try:
|
|
119
|
+
response = self.session.rpc.get(f"/v1/actions/get_facial_recognition_server/{self.server_id}")
|
|
120
|
+
if response.get("success", False) and response.get("code") == 200:
|
|
121
|
+
# Response format:
|
|
122
|
+
# {'success': True, 'code': 200, 'message': 'Success', 'serverTime': '2025-10-21T09:56:14Z',
|
|
123
|
+
# 'data': {'id': '68f28be1f74ae116727448c4', 'name': 'Local Server', 'host': '68.36.82.163', 'port': 8081, 'status': 'active', 'accountNumber': '3823255831182978487149732',
|
|
124
|
+
# 'projectID': '68aff0bbce98491879437909', 'region': 'United States', 'isShared': False}}
|
|
125
|
+
return response.get("data", {})
|
|
126
|
+
else:
|
|
127
|
+
self.logger.warning(f"Failed to fetch server info: {response.get('message', 'Unknown error')}")
|
|
128
|
+
return None
|
|
129
|
+
except Exception as e:
|
|
130
|
+
self.logger.error(f"Exception while fetching server connection info: {e}", exc_info=True)
|
|
131
|
+
return None
|
|
132
|
+
|
|
133
|
+
async def enroll_staff(self, staff_data: Dict[str, Any], image_paths: List[str]) -> Dict[str, Any]:
|
|
134
|
+
"""
|
|
135
|
+
Enroll a new staff member with face images
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
staff_data: Dictionary containing staff information (staffId, firstName, lastName, etc.)
|
|
139
|
+
image_paths: List of file paths to face images
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
Dict containing enrollment response
|
|
143
|
+
"""
|
|
144
|
+
# Convert images to base64
|
|
145
|
+
base64_images = []
|
|
146
|
+
for image_path in image_paths:
|
|
147
|
+
try:
|
|
148
|
+
with open(image_path, "rb") as image_file:
|
|
149
|
+
base64_image = base64.b64encode(image_file.read()).decode('utf-8')
|
|
150
|
+
base64_images.append(base64_image)
|
|
151
|
+
except Exception as e:
|
|
152
|
+
self.logger.error(f"Error reading image {image_path}: {e}", exc_info=True)
|
|
153
|
+
return {"success": False, "error": f"Failed to read image: {e}"}
|
|
154
|
+
|
|
155
|
+
return await self.enroll_staff_base64(staff_data, base64_images)
|
|
156
|
+
|
|
157
|
+
async def enroll_staff_base64(self, staff_data: Dict[str, Any], base64_images: List[str]) -> Dict[str, Any]:
|
|
158
|
+
"""Enroll staff with base64 encoded images
|
|
159
|
+
|
|
160
|
+
API: POST /v1/facial_recognition/staff/enroll?projectId={projectId}&serverID={serverID}
|
|
161
|
+
"""
|
|
162
|
+
|
|
163
|
+
# Prepare enrollment request matching API spec
|
|
164
|
+
enrollment_request = {
|
|
165
|
+
"staffId": staff_data.get("staffId", ""),
|
|
166
|
+
"firstName": staff_data.get("firstName", ""),
|
|
167
|
+
"lastName": staff_data.get("lastName", ""),
|
|
168
|
+
"email": staff_data.get("email", ""),
|
|
169
|
+
"position": staff_data.get("position", ""),
|
|
170
|
+
"department": staff_data.get("department", ""),
|
|
171
|
+
"images": base64_images
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
self.logger.info(f"API REQUEST: Enrolling staff with {len(base64_images)} images - Staff ID: {staff_data.get('staffId', 'N/A')}")
|
|
175
|
+
self.logger.debug(f"Enrollment request payload: {list(enrollment_request.keys())}, num_images={len(base64_images)}")
|
|
176
|
+
|
|
177
|
+
# Use Matrice session for async RPC call
|
|
178
|
+
try:
|
|
179
|
+
response = await self.session.rpc.async_send_request(
|
|
180
|
+
method="POST",
|
|
181
|
+
path=f"/v1/facial_recognition/staff/enroll?projectId={self.project_id}&serverID={self.server_id}",
|
|
182
|
+
payload=enrollment_request,
|
|
183
|
+
base_url=self.server_base_url
|
|
184
|
+
)
|
|
185
|
+
self.logger.info(f"API RESPONSE: Staff enrollment completed - Success: {response.get('success', False)}")
|
|
186
|
+
if not response.get('success', False):
|
|
187
|
+
self.logger.warning(f"Staff enrollment failed: {response.get('error', 'Unknown error')}")
|
|
188
|
+
return self._handle_response(response)
|
|
189
|
+
except Exception as e:
|
|
190
|
+
self.logger.error(f"API ERROR: Staff enrollment request failed - {e}", exc_info=True)
|
|
191
|
+
return {"success": False, "error": str(e)}
|
|
192
|
+
|
|
193
|
+
async def search_similar_faces(self, face_embedding: List[float],
|
|
194
|
+
threshold: float = 0.3, limit: int = 10,
|
|
195
|
+
collection: str = "staff_enrollment",
|
|
196
|
+
location: str = "",
|
|
197
|
+
timestamp: str = "") -> Dict[str, Any]:
|
|
198
|
+
"""
|
|
199
|
+
Search for staff members by face embedding vector
|
|
200
|
+
|
|
201
|
+
API: POST /v1/facial_recognition/search/similar?projectId={projectId}&serverID={serverID}
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
face_embedding: Face embedding vector
|
|
205
|
+
collection: Vector collection name
|
|
206
|
+
threshold: Similarity threshold (0.0 to 1.0)
|
|
207
|
+
limit: Maximum number of results to return
|
|
208
|
+
location: Location identifier for logging
|
|
209
|
+
timestamp: Current timestamp in ISO format
|
|
210
|
+
|
|
211
|
+
Returns:
|
|
212
|
+
Dict containing search results with detectionType (known/unknown)
|
|
213
|
+
"""
|
|
214
|
+
search_request = {
|
|
215
|
+
"embedding": face_embedding,
|
|
216
|
+
"collection": collection,
|
|
217
|
+
"threshold": threshold,
|
|
218
|
+
"limit": limit,
|
|
219
|
+
"images_required":False,
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
# Add optional fields only if provided
|
|
223
|
+
if location:
|
|
224
|
+
search_request["location"] = location
|
|
225
|
+
if timestamp:
|
|
226
|
+
search_request["timestamp"] = timestamp
|
|
227
|
+
|
|
228
|
+
self.logger.debug(f"API REQUEST: Searching similar faces - threshold={threshold}, limit={limit}, collection={collection}, location={location}")
|
|
229
|
+
|
|
230
|
+
# Use Matrice session for async RPC call
|
|
231
|
+
try:
|
|
232
|
+
response = await self.session.rpc.async_send_request(
|
|
233
|
+
method="POST",
|
|
234
|
+
path=f"/v1/facial_recognition/search/similar?projectId={self.project_id}&serverID={self.server_id}",
|
|
235
|
+
payload=search_request,
|
|
236
|
+
base_url=self.server_base_url
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
results_count = 0
|
|
240
|
+
if response.get('success', False):
|
|
241
|
+
data = response.get('data', [])
|
|
242
|
+
results_count = len(data) if isinstance(data, list) else 0
|
|
243
|
+
self.logger.info(f"API RESPONSE: Face search completed - Found {results_count} matches")
|
|
244
|
+
if results_count > 0:
|
|
245
|
+
self.logger.debug(f"Top match: staff_id={data[0].get('staffId', 'N/A')}, score={data[0].get('score', 0):.3f}")
|
|
246
|
+
else:
|
|
247
|
+
self.logger.warning(f"Face search failed: {response.get('error', 'Unknown error')}")
|
|
248
|
+
|
|
249
|
+
return self._handle_response(response)
|
|
250
|
+
except Exception as e:
|
|
251
|
+
self.logger.error(f"API ERROR: Face search request failed - {e}", exc_info=True)
|
|
252
|
+
return {"success": False, "error": str(e)}
|
|
253
|
+
|
|
254
|
+
async def get_staff_details(self, staff_id: str) -> Dict[str, Any]:
|
|
255
|
+
"""Get full staff details by staff ID
|
|
256
|
+
|
|
257
|
+
API: GET /v1/facial_recognition/staff/:staffId?projectId={projectId}&serverID={serverID}
|
|
258
|
+
"""
|
|
259
|
+
|
|
260
|
+
self.logger.debug(f"API REQUEST: Getting staff details - staff_id={staff_id}")
|
|
261
|
+
|
|
262
|
+
# Use Matrice session for async RPC call
|
|
263
|
+
try:
|
|
264
|
+
response = await self.session.rpc.async_send_request(
|
|
265
|
+
method="GET",
|
|
266
|
+
path=f"/v1/facial_recognition/staff/{staff_id}?projectId={self.project_id}&serverID={self.server_id}",
|
|
267
|
+
payload={},
|
|
268
|
+
base_url=self.server_base_url
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
if response.get('success', False):
|
|
272
|
+
self.logger.info(f"API RESPONSE: Staff details retrieved successfully - staff_id={staff_id}")
|
|
273
|
+
else:
|
|
274
|
+
self.logger.warning(f"Failed to get staff details for staff_id={staff_id}: {response.get('error', 'Unknown error')}")
|
|
275
|
+
|
|
276
|
+
return self._handle_response(response)
|
|
277
|
+
except Exception as e:
|
|
278
|
+
self.logger.error(f"API ERROR: Get staff details request failed for staff_id={staff_id} - {e}", exc_info=True)
|
|
279
|
+
return {"success": False, "error": str(e)}
|
|
280
|
+
|
|
281
|
+
async def store_people_activity(self,
|
|
282
|
+
staff_id: str,
|
|
283
|
+
detection_type: str,
|
|
284
|
+
bbox: List[float],
|
|
285
|
+
location: str,
|
|
286
|
+
employee_id: Optional[str] = None,
|
|
287
|
+
timestamp: str = datetime.now(timezone.utc).isoformat(),
|
|
288
|
+
image_data: Optional[str] = None,
|
|
289
|
+
) -> Dict[str, Any]:
|
|
290
|
+
"""
|
|
291
|
+
Store people activity data with optional image data
|
|
292
|
+
|
|
293
|
+
API: POST /v1/facial_recognition/store_people_activity?projectId={projectId}&serverID={serverID}
|
|
294
|
+
|
|
295
|
+
Args:
|
|
296
|
+
staff_id: Staff identifier (empty for unknown faces)
|
|
297
|
+
detection_type: Type of detection (known, unknown, empty)
|
|
298
|
+
bbox: Bounding box coordinates [x1, y1, x2, y2]
|
|
299
|
+
location: Location identifier
|
|
300
|
+
employee_id: Employee ID (for unknown faces, this will be generated)
|
|
301
|
+
timestamp: Timestamp in ISO format
|
|
302
|
+
image_data: Base64-encoded JPEG image data (optional)
|
|
303
|
+
|
|
304
|
+
Returns:
|
|
305
|
+
Dict containing response data with success status
|
|
306
|
+
"""
|
|
307
|
+
activity_request = {
|
|
308
|
+
"staff_id": staff_id,
|
|
309
|
+
"type": detection_type,
|
|
310
|
+
"timestamp": timestamp,
|
|
311
|
+
"bbox": bbox,
|
|
312
|
+
"location": location,
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
# Add optional fields if provided based on API spec
|
|
316
|
+
if detection_type == "unknown" and employee_id:
|
|
317
|
+
activity_request["anonymous_id"] = employee_id
|
|
318
|
+
elif detection_type == "known" and employee_id:
|
|
319
|
+
activity_request["employee_id"] = employee_id
|
|
320
|
+
|
|
321
|
+
# Add image data if provided
|
|
322
|
+
if image_data:
|
|
323
|
+
activity_request["imageData"] = image_data
|
|
324
|
+
|
|
325
|
+
self.logger.info(f"API REQUEST: Storing people activity - type={detection_type}, staff_id={staff_id}, location={location}, has_image={bool(image_data)}")
|
|
326
|
+
self.logger.debug(f"Activity request payload: bbox={bbox}, employee_id={employee_id}")
|
|
327
|
+
|
|
328
|
+
try:
|
|
329
|
+
response = await self.session.rpc.async_send_request(
|
|
330
|
+
method="POST",
|
|
331
|
+
path=f"/v1/facial_recognition/store_people_activity?projectId={self.project_id}&serverID={self.server_id}",
|
|
332
|
+
payload=activity_request,
|
|
333
|
+
base_url=self.server_base_url
|
|
334
|
+
)
|
|
335
|
+
handled_response = self._handle_response(response)
|
|
336
|
+
|
|
337
|
+
if handled_response.get("success", False):
|
|
338
|
+
self.logger.info(f"API RESPONSE: Successfully stored {detection_type} activity for staff_id={staff_id}")
|
|
339
|
+
return handled_response
|
|
340
|
+
else:
|
|
341
|
+
self.logger.warning(f"Failed to store {detection_type} activity: {handled_response.get('error', 'Unknown error')}")
|
|
342
|
+
return handled_response
|
|
343
|
+
except Exception as e:
|
|
344
|
+
self.logger.error(f"API ERROR: Store people activity request failed - type={detection_type}, staff_id={staff_id} - {e}", exc_info=True)
|
|
345
|
+
return {"success": False, "error": str(e)}
|
|
346
|
+
|
|
347
|
+
async def update_staff_images(self, image_url: str, employee_id: str) -> Dict[str, Any]:
|
|
348
|
+
"""Update staff images with uploaded image URL
|
|
349
|
+
|
|
350
|
+
API: PUT /v1/facial_recognition/staff/update_images?projectId={projectId}&serverID={serverID}
|
|
351
|
+
"""
|
|
352
|
+
|
|
353
|
+
update_request = {
|
|
354
|
+
"imageUrl": image_url,
|
|
355
|
+
"employeeId": employee_id
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
self.logger.info(f"API REQUEST: Updating staff images - employee_id={employee_id}")
|
|
359
|
+
self.logger.debug(f"Update request: image_url={image_url[:50]}...")
|
|
360
|
+
|
|
361
|
+
# Use Matrice session for async RPC call
|
|
362
|
+
try:
|
|
363
|
+
response = await self.session.rpc.async_send_request(
|
|
364
|
+
method="PUT",
|
|
365
|
+
path=f"/v1/facial_recognition/staff/update_images?projectId={self.project_id}&serverID={self.server_id}",
|
|
366
|
+
payload=update_request,
|
|
367
|
+
base_url=self.server_base_url
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
if response.get('success', False):
|
|
371
|
+
self.logger.info(f"API RESPONSE: Staff images updated successfully - employee_id={employee_id}")
|
|
372
|
+
else:
|
|
373
|
+
self.logger.warning(f"Failed to update staff images for employee_id={employee_id}: {response.get('error', 'Unknown error')}")
|
|
374
|
+
|
|
375
|
+
return self._handle_response(response)
|
|
376
|
+
except Exception as e:
|
|
377
|
+
self.logger.error(f"API ERROR: Update staff images request failed - employee_id={employee_id} - {e}", exc_info=True)
|
|
378
|
+
return {"success": False, "error": str(e)}
|
|
379
|
+
|
|
380
|
+
async def upload_image_to_url(self, image_bytes: bytes, upload_url: str) -> bool:
|
|
381
|
+
"""Upload image bytes to the provided URL"""
|
|
382
|
+
try:
|
|
383
|
+
self.logger.info(f"API REQUEST: Uploading image to URL - size={len(image_bytes)} bytes")
|
|
384
|
+
self.logger.debug(f"Upload URL: {upload_url[:100]}...")
|
|
385
|
+
|
|
386
|
+
# Upload the image to the signed URL using async httpx
|
|
387
|
+
headers = {'Content-Type': 'image/jpeg'}
|
|
388
|
+
async with httpx.AsyncClient() as client:
|
|
389
|
+
response = await client.put(upload_url, content=image_bytes, headers=headers)
|
|
390
|
+
|
|
391
|
+
if response.status_code in [200, 201]:
|
|
392
|
+
self.logger.info(f"API RESPONSE: Successfully uploaded image - status={response.status_code}")
|
|
393
|
+
return True
|
|
394
|
+
else:
|
|
395
|
+
self.logger.error(f"API ERROR: Failed to upload image - status={response.status_code}, response={response.text[:200]}")
|
|
396
|
+
return False
|
|
397
|
+
|
|
398
|
+
except Exception as e:
|
|
399
|
+
self.logger.error(f"API ERROR: Exception during image upload - {e}", exc_info=True)
|
|
400
|
+
return False
|
|
401
|
+
|
|
402
|
+
async def shutdown_service(self, action_record_id: Optional[str] = None) -> Dict[str, Any]:
|
|
403
|
+
"""Gracefully shutdown the service
|
|
404
|
+
|
|
405
|
+
API: DELETE /v1/facial_recognition/shutdown?projectId={projectId}&serverID={serverID}
|
|
406
|
+
"""
|
|
407
|
+
|
|
408
|
+
payload = {} if not action_record_id else {"actionRecordId": action_record_id}
|
|
409
|
+
|
|
410
|
+
self.logger.info(f"API REQUEST: Shutting down service - action_record_id={action_record_id}")
|
|
411
|
+
|
|
412
|
+
# Use Matrice session for async RPC call
|
|
413
|
+
try:
|
|
414
|
+
response = await self.session.rpc.async_send_request(
|
|
415
|
+
method="DELETE",
|
|
416
|
+
path=f"/v1/facial_recognition/shutdown?projectId={self.project_id}&serverID={self.server_id}",
|
|
417
|
+
payload=payload,
|
|
418
|
+
base_url=self.server_base_url
|
|
419
|
+
)
|
|
420
|
+
|
|
421
|
+
if response.get('success', False):
|
|
422
|
+
self.logger.info(f"API RESPONSE: Service shutdown successful")
|
|
423
|
+
else:
|
|
424
|
+
self.logger.warning(f"Service shutdown failed: {response.get('error', 'Unknown error')}")
|
|
425
|
+
|
|
426
|
+
return self._handle_response(response)
|
|
427
|
+
except Exception as e:
|
|
428
|
+
self.logger.error(f"API ERROR: Shutdown service request failed - {e}", exc_info=True)
|
|
429
|
+
return {"success": False, "error": str(e)}
|
|
430
|
+
|
|
431
|
+
async def get_all_staff_embeddings(self) -> Dict[str, Any]:
|
|
432
|
+
"""Get all staff embeddings
|
|
433
|
+
|
|
434
|
+
API: GET /v1/facial_recognition/get_all_staff_embeddings?projectId={projectId}&serverID={serverID}
|
|
435
|
+
"""
|
|
436
|
+
|
|
437
|
+
payload = {}
|
|
438
|
+
|
|
439
|
+
self.logger.info(f"API REQUEST: Getting all staff embeddings")
|
|
440
|
+
|
|
441
|
+
# Use Matrice session for async RPC call
|
|
442
|
+
try:
|
|
443
|
+
response = await self.session.rpc.async_send_request(
|
|
444
|
+
method="GET",
|
|
445
|
+
path=f"/v1/facial_recognition/get_all_staff_embeddings?projectId={self.project_id}&serverID={self.server_id}",
|
|
446
|
+
payload=payload,
|
|
447
|
+
base_url=self.server_base_url
|
|
448
|
+
)
|
|
449
|
+
|
|
450
|
+
embeddings_count = 0
|
|
451
|
+
if response:
|
|
452
|
+
# Handle both list and dict responses
|
|
453
|
+
if isinstance(response, list):
|
|
454
|
+
# API returned list directly
|
|
455
|
+
data = response
|
|
456
|
+
embeddings_count = len(data)
|
|
457
|
+
self.logger.info(f"API RESPONSE: Retrieved {embeddings_count} staff embeddings (list format)")
|
|
458
|
+
# Return in standard format for consistency
|
|
459
|
+
return {"success": True, "data": data}
|
|
460
|
+
elif isinstance(response, dict):
|
|
461
|
+
# API returned dict with 'data' key
|
|
462
|
+
data = response.get('data', [])
|
|
463
|
+
embeddings_count = len(data) if isinstance(data, list) else 0
|
|
464
|
+
self.logger.info(f"API RESPONSE: Retrieved {embeddings_count} staff embeddings (dict format)")
|
|
465
|
+
return self._handle_response(response)
|
|
466
|
+
else:
|
|
467
|
+
self.logger.error(f"Unexpected response type: {type(response)}")
|
|
468
|
+
return {"success": False, "error": f"Unexpected response type: {type(response)}"}
|
|
469
|
+
else:
|
|
470
|
+
error_msg = response.get('error', 'Unknown error') if isinstance(response, dict) else 'Empty response'
|
|
471
|
+
self.logger.warning(f"Failed to get staff embeddings: {error_msg}")
|
|
472
|
+
return {"success": False, "error": error_msg}
|
|
473
|
+
|
|
474
|
+
except Exception as e:
|
|
475
|
+
self.logger.error(f"API ERROR: Get all staff embeddings request failed - {e}", exc_info=True)
|
|
476
|
+
return {"success": False, "error": str(e)}
|
|
477
|
+
|
|
478
|
+
async def update_deployment(self, deployment_id: str) -> Dict[str, Any]:
|
|
479
|
+
"""Update deployment to notify facial recognition server
|
|
480
|
+
|
|
481
|
+
API: PUT /v1/facial_recognition/update_deployment/:deployment_id
|
|
482
|
+
|
|
483
|
+
Args:
|
|
484
|
+
deployment_id: The deployment ID to update
|
|
485
|
+
|
|
486
|
+
Returns:
|
|
487
|
+
Dict containing response data
|
|
488
|
+
"""
|
|
489
|
+
if not deployment_id:
|
|
490
|
+
self.logger.warning("No deployment_id provided for update_deployment")
|
|
491
|
+
return {"success": False, "error": "deployment_id is required"}
|
|
492
|
+
|
|
493
|
+
self.logger.info(f"API REQUEST: Updating deployment - deployment_id={deployment_id}")
|
|
494
|
+
|
|
495
|
+
# Use Matrice session for async RPC call
|
|
496
|
+
try:
|
|
497
|
+
response = await self.session.rpc.async_send_request(
|
|
498
|
+
method="PUT",
|
|
499
|
+
path=f"/v1/facial_recognition/update_deployment/{deployment_id}",
|
|
500
|
+
payload={},
|
|
501
|
+
base_url=self.server_base_url
|
|
502
|
+
)
|
|
503
|
+
|
|
504
|
+
if response.get('success', False):
|
|
505
|
+
self.logger.info(f"API RESPONSE: Deployment updated successfully - deployment_id={deployment_id}")
|
|
506
|
+
else:
|
|
507
|
+
self.logger.warning(f"Failed to update deployment for deployment_id={deployment_id}: {response.get('error', 'Unknown error')}")
|
|
508
|
+
|
|
509
|
+
return self._handle_response(response)
|
|
510
|
+
except Exception as e:
|
|
511
|
+
self.logger.error(f"API ERROR: Update deployment request failed - deployment_id={deployment_id} - {e}", exc_info=True)
|
|
512
|
+
return {"success": False, "error": str(e)}
|
|
513
|
+
|
|
514
|
+
async def enroll_unknown_person(self, embedding: List[float], image_source: str = None, timestamp: str = None, location: str = None, employee_id: str = None) -> Dict[str, Any]:
|
|
515
|
+
"""Enroll an unknown person
|
|
516
|
+
|
|
517
|
+
API: POST /v1/facial_recognition/enroll_unknown_person?projectId={projectId}&serverID={serverID}
|
|
518
|
+
"""
|
|
519
|
+
|
|
520
|
+
payload = {
|
|
521
|
+
"embedding": embedding
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
# Add optional fields based on API spec
|
|
525
|
+
if image_source:
|
|
526
|
+
payload["imageSource"] = image_source
|
|
527
|
+
if timestamp:
|
|
528
|
+
payload["timestamp"] = timestamp
|
|
529
|
+
else:
|
|
530
|
+
payload["timestamp"] = datetime.now(timezone.utc).isoformat()
|
|
531
|
+
if location:
|
|
532
|
+
payload["location"] = location
|
|
533
|
+
|
|
534
|
+
self.logger.info(f"API REQUEST: Enrolling unknown person - location={location}")
|
|
535
|
+
self.logger.debug(f"Unknown enrollment payload: has_embedding={bool(embedding)}, has_image_source={bool(image_source)}")
|
|
536
|
+
|
|
537
|
+
# Use Matrice session for async RPC call
|
|
538
|
+
try:
|
|
539
|
+
response = await self.session.rpc.async_send_request(
|
|
540
|
+
method="POST",
|
|
541
|
+
path=f"/v1/facial_recognition/enroll_unknown_person?projectId={self.project_id}&serverID={self.server_id}",
|
|
542
|
+
payload=payload,
|
|
543
|
+
base_url=self.server_base_url
|
|
544
|
+
)
|
|
545
|
+
|
|
546
|
+
if response.get('success', False):
|
|
547
|
+
self.logger.info(f"API RESPONSE: Unknown person enrolled successfully")
|
|
548
|
+
else:
|
|
549
|
+
self.logger.warning(f"Failed to enroll unknown person: {response.get('error', 'Unknown error')}")
|
|
550
|
+
|
|
551
|
+
return self._handle_response(response)
|
|
552
|
+
except Exception as e:
|
|
553
|
+
self.logger.error(f"API ERROR: Enroll unknown person request failed - {e}", exc_info=True)
|
|
554
|
+
return {"success": False, "error": str(e)}
|
|
555
|
+
|
|
556
|
+
async def health_check(self) -> Dict[str, Any]:
|
|
557
|
+
"""Check if the facial recognition service is healthy"""
|
|
558
|
+
|
|
559
|
+
self.logger.debug(f"API REQUEST: Health check")
|
|
560
|
+
|
|
561
|
+
# Use Matrice session for async RPC call
|
|
562
|
+
try:
|
|
563
|
+
response = await self.session.rpc.async_send_request(
|
|
564
|
+
method="GET",
|
|
565
|
+
path=f"/v1/facial_recognition/health?serverID={self.server_id}",
|
|
566
|
+
payload={},
|
|
567
|
+
base_url=self.server_base_url
|
|
568
|
+
)
|
|
569
|
+
|
|
570
|
+
if response.get('success', False):
|
|
571
|
+
self.logger.info(f"API RESPONSE: Service is healthy")
|
|
572
|
+
else:
|
|
573
|
+
self.logger.warning(f"Health check failed: {response.get('error', 'Unknown error')}")
|
|
574
|
+
|
|
575
|
+
return self._handle_response(response)
|
|
576
|
+
except Exception as e:
|
|
577
|
+
self.logger.error(f"API ERROR: Health check request failed - {e}", exc_info=True)
|
|
578
|
+
return {"success": False, "error": str(e)}
|
|
579
|
+
|
|
580
|
+
def _handle_response(self, response: Dict[str, Any]) -> Dict[str, Any]:
|
|
581
|
+
"""Handle RPC response and errors"""
|
|
582
|
+
try:
|
|
583
|
+
if response:
|
|
584
|
+
return response
|
|
585
|
+
else:
|
|
586
|
+
error_msg = response #.get("error", "Unknown RPC error")
|
|
587
|
+
self.logger.error(f"RPC Error: {error_msg}", exc_info=True)
|
|
588
|
+
return {"success": False, "error": error_msg}
|
|
589
|
+
except Exception as e:
|
|
590
|
+
self.logger.error(f"Error handling RPC response: {e}", exc_info=True)
|
|
591
|
+
return {"success": False, "error": f"Response handling error: {e}"}
|
|
592
|
+
|
|
593
|
+
|
|
594
|
+
# Factory function for easy initialization
|
|
595
|
+
def create_face_client(account_number: str = None, access_key: str = None,
|
|
596
|
+
secret_key: str = None, project_id: str = None,
|
|
597
|
+
server_id: str = "", session=None) -> FacialRecognitionClient:
|
|
598
|
+
"""Create a facial recognition client with automatic credential detection"""
|
|
599
|
+
return FacialRecognitionClient(
|
|
600
|
+
account_number=account_number,
|
|
601
|
+
access_key=access_key,
|
|
602
|
+
secret_key=secret_key,
|
|
603
|
+
project_id=project_id,
|
|
604
|
+
server_id=server_id,
|
|
605
|
+
session=session
|
|
606
|
+
)
|