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.
Files changed (196) hide show
  1. matrice_analytics/__init__.py +28 -0
  2. matrice_analytics/boundary_drawing_internal/README.md +305 -0
  3. matrice_analytics/boundary_drawing_internal/__init__.py +45 -0
  4. matrice_analytics/boundary_drawing_internal/boundary_drawing_internal.py +1207 -0
  5. matrice_analytics/boundary_drawing_internal/boundary_drawing_tool.py +429 -0
  6. matrice_analytics/boundary_drawing_internal/boundary_tool_template.html +1036 -0
  7. matrice_analytics/boundary_drawing_internal/data/.gitignore +12 -0
  8. matrice_analytics/boundary_drawing_internal/example_usage.py +206 -0
  9. matrice_analytics/boundary_drawing_internal/usage/README.md +110 -0
  10. matrice_analytics/boundary_drawing_internal/usage/boundary_drawer_launcher.py +102 -0
  11. matrice_analytics/boundary_drawing_internal/usage/simple_boundary_launcher.py +107 -0
  12. matrice_analytics/post_processing/README.md +455 -0
  13. matrice_analytics/post_processing/__init__.py +732 -0
  14. matrice_analytics/post_processing/advanced_tracker/README.md +650 -0
  15. matrice_analytics/post_processing/advanced_tracker/__init__.py +17 -0
  16. matrice_analytics/post_processing/advanced_tracker/base.py +99 -0
  17. matrice_analytics/post_processing/advanced_tracker/config.py +77 -0
  18. matrice_analytics/post_processing/advanced_tracker/kalman_filter.py +370 -0
  19. matrice_analytics/post_processing/advanced_tracker/matching.py +195 -0
  20. matrice_analytics/post_processing/advanced_tracker/strack.py +230 -0
  21. matrice_analytics/post_processing/advanced_tracker/tracker.py +367 -0
  22. matrice_analytics/post_processing/config.py +146 -0
  23. matrice_analytics/post_processing/core/__init__.py +63 -0
  24. matrice_analytics/post_processing/core/base.py +704 -0
  25. matrice_analytics/post_processing/core/config.py +3291 -0
  26. matrice_analytics/post_processing/core/config_utils.py +925 -0
  27. matrice_analytics/post_processing/face_reg/__init__.py +43 -0
  28. matrice_analytics/post_processing/face_reg/compare_similarity.py +556 -0
  29. matrice_analytics/post_processing/face_reg/embedding_manager.py +950 -0
  30. matrice_analytics/post_processing/face_reg/face_recognition.py +2234 -0
  31. matrice_analytics/post_processing/face_reg/face_recognition_client.py +606 -0
  32. matrice_analytics/post_processing/face_reg/people_activity_logging.py +321 -0
  33. matrice_analytics/post_processing/ocr/__init__.py +0 -0
  34. matrice_analytics/post_processing/ocr/easyocr_extractor.py +250 -0
  35. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/__init__.py +9 -0
  36. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/__init__.py +4 -0
  37. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/cli.py +33 -0
  38. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/dataset_stats.py +139 -0
  39. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/export.py +398 -0
  40. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/train.py +447 -0
  41. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/utils.py +129 -0
  42. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/valid.py +93 -0
  43. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/validate_dataset.py +240 -0
  44. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/visualize_augmentation.py +176 -0
  45. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/visualize_predictions.py +96 -0
  46. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/core/__init__.py +3 -0
  47. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/core/process.py +246 -0
  48. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/core/types.py +60 -0
  49. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/core/utils.py +87 -0
  50. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/inference/__init__.py +3 -0
  51. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/inference/config.py +82 -0
  52. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/inference/hub.py +141 -0
  53. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/inference/plate_recognizer.py +323 -0
  54. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/py.typed +0 -0
  55. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/__init__.py +0 -0
  56. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/data/__init__.py +0 -0
  57. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/data/augmentation.py +101 -0
  58. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/data/dataset.py +97 -0
  59. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/__init__.py +0 -0
  60. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/config.py +114 -0
  61. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/layers.py +553 -0
  62. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/loss.py +55 -0
  63. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/metric.py +86 -0
  64. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/model_builders.py +95 -0
  65. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/model_schema.py +395 -0
  66. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/utilities/__init__.py +0 -0
  67. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/utilities/backend_utils.py +38 -0
  68. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/utilities/utils.py +214 -0
  69. matrice_analytics/post_processing/ocr/postprocessing.py +270 -0
  70. matrice_analytics/post_processing/ocr/preprocessing.py +52 -0
  71. matrice_analytics/post_processing/post_processor.py +1175 -0
  72. matrice_analytics/post_processing/test_cases/__init__.py +1 -0
  73. matrice_analytics/post_processing/test_cases/run_tests.py +143 -0
  74. matrice_analytics/post_processing/test_cases/test_advanced_customer_service.py +841 -0
  75. matrice_analytics/post_processing/test_cases/test_basic_counting_tracking.py +523 -0
  76. matrice_analytics/post_processing/test_cases/test_comprehensive.py +531 -0
  77. matrice_analytics/post_processing/test_cases/test_config.py +852 -0
  78. matrice_analytics/post_processing/test_cases/test_customer_service.py +585 -0
  79. matrice_analytics/post_processing/test_cases/test_data_generators.py +583 -0
  80. matrice_analytics/post_processing/test_cases/test_people_counting.py +510 -0
  81. matrice_analytics/post_processing/test_cases/test_processor.py +524 -0
  82. matrice_analytics/post_processing/test_cases/test_usecases.py +165 -0
  83. matrice_analytics/post_processing/test_cases/test_utilities.py +356 -0
  84. matrice_analytics/post_processing/test_cases/test_utils.py +743 -0
  85. matrice_analytics/post_processing/usecases/Histopathological_Cancer_Detection_img.py +604 -0
  86. matrice_analytics/post_processing/usecases/__init__.py +267 -0
  87. matrice_analytics/post_processing/usecases/abandoned_object_detection.py +797 -0
  88. matrice_analytics/post_processing/usecases/advanced_customer_service.py +1601 -0
  89. matrice_analytics/post_processing/usecases/age_detection.py +842 -0
  90. matrice_analytics/post_processing/usecases/age_gender_detection.py +1085 -0
  91. matrice_analytics/post_processing/usecases/anti_spoofing_detection.py +656 -0
  92. matrice_analytics/post_processing/usecases/assembly_line_detection.py +841 -0
  93. matrice_analytics/post_processing/usecases/banana_defect_detection.py +624 -0
  94. matrice_analytics/post_processing/usecases/basic_counting_tracking.py +667 -0
  95. matrice_analytics/post_processing/usecases/blood_cancer_detection_img.py +881 -0
  96. matrice_analytics/post_processing/usecases/car_damage_detection.py +834 -0
  97. matrice_analytics/post_processing/usecases/car_part_segmentation.py +946 -0
  98. matrice_analytics/post_processing/usecases/car_service.py +1601 -0
  99. matrice_analytics/post_processing/usecases/cardiomegaly_classification.py +864 -0
  100. matrice_analytics/post_processing/usecases/cell_microscopy_segmentation.py +897 -0
  101. matrice_analytics/post_processing/usecases/chicken_pose_detection.py +648 -0
  102. matrice_analytics/post_processing/usecases/child_monitoring.py +814 -0
  103. matrice_analytics/post_processing/usecases/color/clip.py +660 -0
  104. matrice_analytics/post_processing/usecases/color/clip_processor/merges.txt +48895 -0
  105. matrice_analytics/post_processing/usecases/color/clip_processor/preprocessor_config.json +28 -0
  106. matrice_analytics/post_processing/usecases/color/clip_processor/special_tokens_map.json +30 -0
  107. matrice_analytics/post_processing/usecases/color/clip_processor/tokenizer.json +245079 -0
  108. matrice_analytics/post_processing/usecases/color/clip_processor/tokenizer_config.json +32 -0
  109. matrice_analytics/post_processing/usecases/color/clip_processor/vocab.json +1 -0
  110. matrice_analytics/post_processing/usecases/color/color_map_utils.py +70 -0
  111. matrice_analytics/post_processing/usecases/color/color_mapper.py +468 -0
  112. matrice_analytics/post_processing/usecases/color_detection.py +1936 -0
  113. matrice_analytics/post_processing/usecases/color_map_utils.py +70 -0
  114. matrice_analytics/post_processing/usecases/concrete_crack_detection.py +827 -0
  115. matrice_analytics/post_processing/usecases/crop_weed_detection.py +781 -0
  116. matrice_analytics/post_processing/usecases/customer_service.py +1008 -0
  117. matrice_analytics/post_processing/usecases/defect_detection_products.py +936 -0
  118. matrice_analytics/post_processing/usecases/distracted_driver_detection.py +822 -0
  119. matrice_analytics/post_processing/usecases/drone_traffic_monitoring.py +585 -0
  120. matrice_analytics/post_processing/usecases/drowsy_driver_detection.py +829 -0
  121. matrice_analytics/post_processing/usecases/dwell_detection.py +829 -0
  122. matrice_analytics/post_processing/usecases/emergency_vehicle_detection.py +827 -0
  123. matrice_analytics/post_processing/usecases/face_emotion.py +813 -0
  124. matrice_analytics/post_processing/usecases/face_recognition.py +827 -0
  125. matrice_analytics/post_processing/usecases/fashion_detection.py +835 -0
  126. matrice_analytics/post_processing/usecases/field_mapping.py +902 -0
  127. matrice_analytics/post_processing/usecases/fire_detection.py +1146 -0
  128. matrice_analytics/post_processing/usecases/flare_analysis.py +836 -0
  129. matrice_analytics/post_processing/usecases/flower_segmentation.py +1006 -0
  130. matrice_analytics/post_processing/usecases/gas_leak_detection.py +837 -0
  131. matrice_analytics/post_processing/usecases/gender_detection.py +832 -0
  132. matrice_analytics/post_processing/usecases/human_activity_recognition.py +871 -0
  133. matrice_analytics/post_processing/usecases/intrusion_detection.py +1672 -0
  134. matrice_analytics/post_processing/usecases/leaf.py +821 -0
  135. matrice_analytics/post_processing/usecases/leaf_disease.py +840 -0
  136. matrice_analytics/post_processing/usecases/leak_detection.py +837 -0
  137. matrice_analytics/post_processing/usecases/license_plate_detection.py +1188 -0
  138. matrice_analytics/post_processing/usecases/license_plate_monitoring.py +1781 -0
  139. matrice_analytics/post_processing/usecases/litter_monitoring.py +717 -0
  140. matrice_analytics/post_processing/usecases/mask_detection.py +869 -0
  141. matrice_analytics/post_processing/usecases/natural_disaster.py +907 -0
  142. matrice_analytics/post_processing/usecases/parking.py +787 -0
  143. matrice_analytics/post_processing/usecases/parking_space_detection.py +822 -0
  144. matrice_analytics/post_processing/usecases/pcb_defect_detection.py +888 -0
  145. matrice_analytics/post_processing/usecases/pedestrian_detection.py +808 -0
  146. matrice_analytics/post_processing/usecases/people_counting.py +706 -0
  147. matrice_analytics/post_processing/usecases/people_counting_bckp.py +1683 -0
  148. matrice_analytics/post_processing/usecases/people_tracking.py +1842 -0
  149. matrice_analytics/post_processing/usecases/pipeline_detection.py +605 -0
  150. matrice_analytics/post_processing/usecases/plaque_segmentation_img.py +874 -0
  151. matrice_analytics/post_processing/usecases/pothole_segmentation.py +915 -0
  152. matrice_analytics/post_processing/usecases/ppe_compliance.py +645 -0
  153. matrice_analytics/post_processing/usecases/price_tag_detection.py +822 -0
  154. matrice_analytics/post_processing/usecases/proximity_detection.py +1901 -0
  155. matrice_analytics/post_processing/usecases/road_lane_detection.py +623 -0
  156. matrice_analytics/post_processing/usecases/road_traffic_density.py +832 -0
  157. matrice_analytics/post_processing/usecases/road_view_segmentation.py +915 -0
  158. matrice_analytics/post_processing/usecases/shelf_inventory_detection.py +583 -0
  159. matrice_analytics/post_processing/usecases/shoplifting_detection.py +822 -0
  160. matrice_analytics/post_processing/usecases/shopping_cart_analysis.py +899 -0
  161. matrice_analytics/post_processing/usecases/skin_cancer_classification_img.py +864 -0
  162. matrice_analytics/post_processing/usecases/smoker_detection.py +833 -0
  163. matrice_analytics/post_processing/usecases/solar_panel.py +810 -0
  164. matrice_analytics/post_processing/usecases/suspicious_activity_detection.py +1030 -0
  165. matrice_analytics/post_processing/usecases/template_usecase.py +380 -0
  166. matrice_analytics/post_processing/usecases/theft_detection.py +648 -0
  167. matrice_analytics/post_processing/usecases/traffic_sign_monitoring.py +724 -0
  168. matrice_analytics/post_processing/usecases/underground_pipeline_defect_detection.py +775 -0
  169. matrice_analytics/post_processing/usecases/underwater_pollution_detection.py +842 -0
  170. matrice_analytics/post_processing/usecases/vehicle_monitoring.py +1029 -0
  171. matrice_analytics/post_processing/usecases/warehouse_object_segmentation.py +899 -0
  172. matrice_analytics/post_processing/usecases/waterbody_segmentation.py +923 -0
  173. matrice_analytics/post_processing/usecases/weapon_detection.py +771 -0
  174. matrice_analytics/post_processing/usecases/weld_defect_detection.py +615 -0
  175. matrice_analytics/post_processing/usecases/wildlife_monitoring.py +898 -0
  176. matrice_analytics/post_processing/usecases/windmill_maintenance.py +834 -0
  177. matrice_analytics/post_processing/usecases/wound_segmentation.py +856 -0
  178. matrice_analytics/post_processing/utils/__init__.py +150 -0
  179. matrice_analytics/post_processing/utils/advanced_counting_utils.py +400 -0
  180. matrice_analytics/post_processing/utils/advanced_helper_utils.py +317 -0
  181. matrice_analytics/post_processing/utils/advanced_tracking_utils.py +461 -0
  182. matrice_analytics/post_processing/utils/alerting_utils.py +213 -0
  183. matrice_analytics/post_processing/utils/category_mapping_utils.py +94 -0
  184. matrice_analytics/post_processing/utils/color_utils.py +592 -0
  185. matrice_analytics/post_processing/utils/counting_utils.py +182 -0
  186. matrice_analytics/post_processing/utils/filter_utils.py +261 -0
  187. matrice_analytics/post_processing/utils/format_utils.py +293 -0
  188. matrice_analytics/post_processing/utils/geometry_utils.py +300 -0
  189. matrice_analytics/post_processing/utils/smoothing_utils.py +358 -0
  190. matrice_analytics/post_processing/utils/tracking_utils.py +234 -0
  191. matrice_analytics/py.typed +0 -0
  192. matrice_analytics-0.1.60.dist-info/METADATA +481 -0
  193. matrice_analytics-0.1.60.dist-info/RECORD +196 -0
  194. matrice_analytics-0.1.60.dist-info/WHEEL +5 -0
  195. matrice_analytics-0.1.60.dist-info/licenses/LICENSE.txt +21 -0
  196. 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
+ )