matrice-analytics 0.1.3__py3-none-any.whl → 0.1.32__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.
Potentially problematic release.
This version of matrice-analytics might be problematic. Click here for more details.
- matrice_analytics/post_processing/advanced_tracker/matching.py +3 -3
- matrice_analytics/post_processing/advanced_tracker/strack.py +1 -1
- matrice_analytics/post_processing/config.py +4 -0
- matrice_analytics/post_processing/core/config.py +115 -12
- matrice_analytics/post_processing/face_reg/compare_similarity.py +5 -5
- matrice_analytics/post_processing/face_reg/embedding_manager.py +109 -8
- matrice_analytics/post_processing/face_reg/face_recognition.py +157 -61
- matrice_analytics/post_processing/face_reg/face_recognition_client.py +339 -88
- matrice_analytics/post_processing/face_reg/people_activity_logging.py +67 -29
- 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 +0 -1
- matrice_analytics/post_processing/post_processor.py +32 -11
- matrice_analytics/post_processing/usecases/color/clip.py +42 -8
- matrice_analytics/post_processing/usecases/color/color_mapper.py +2 -2
- matrice_analytics/post_processing/usecases/color_detection.py +50 -129
- matrice_analytics/post_processing/usecases/drone_traffic_monitoring.py +41 -386
- matrice_analytics/post_processing/usecases/flare_analysis.py +1 -56
- matrice_analytics/post_processing/usecases/license_plate_detection.py +476 -202
- matrice_analytics/post_processing/usecases/license_plate_monitoring.py +351 -26
- matrice_analytics/post_processing/usecases/people_counting.py +408 -1431
- matrice_analytics/post_processing/usecases/people_counting_bckp.py +1683 -0
- matrice_analytics/post_processing/usecases/vehicle_monitoring.py +39 -10
- matrice_analytics/post_processing/utils/__init__.py +8 -8
- {matrice_analytics-0.1.3.dist-info → matrice_analytics-0.1.32.dist-info}/METADATA +1 -1
- {matrice_analytics-0.1.3.dist-info → matrice_analytics-0.1.32.dist-info}/RECORD +61 -26
- {matrice_analytics-0.1.3.dist-info → matrice_analytics-0.1.32.dist-info}/WHEEL +0 -0
- {matrice_analytics-0.1.3.dist-info → matrice_analytics-0.1.32.dist-info}/licenses/LICENSE.txt +0 -0
- {matrice_analytics-0.1.3.dist-info → matrice_analytics-0.1.32.dist-info}/top_level.txt +0 -0
|
@@ -10,6 +10,8 @@ import os
|
|
|
10
10
|
import base64
|
|
11
11
|
import logging
|
|
12
12
|
import httpx
|
|
13
|
+
import urllib
|
|
14
|
+
import urllib.request
|
|
13
15
|
from typing import List, Dict, Any, Optional
|
|
14
16
|
from datetime import datetime, timezone
|
|
15
17
|
|
|
@@ -38,6 +40,10 @@ class FacialRecognitionClient:
|
|
|
38
40
|
if not self.server_id:
|
|
39
41
|
raise ValueError("Server ID is required for Face Recognition Client")
|
|
40
42
|
|
|
43
|
+
self.server_info = None
|
|
44
|
+
self.server_base_url = None
|
|
45
|
+
self.public_ip = self._get_public_ip()
|
|
46
|
+
|
|
41
47
|
# Use existing session if provided, otherwise create new one
|
|
42
48
|
if session is not None:
|
|
43
49
|
self.session = session
|
|
@@ -70,6 +76,60 @@ class FacialRecognitionClient:
|
|
|
70
76
|
self.logger.error(f"Failed to initialize Matrice session: {e}", exc_info=True)
|
|
71
77
|
raise
|
|
72
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"https://{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
|
+
|
|
73
133
|
async def enroll_staff(self, staff_data: Dict[str, Any], image_paths: List[str]) -> Dict[str, Any]:
|
|
74
134
|
"""
|
|
75
135
|
Enroll a new staff member with face images
|
|
@@ -95,21 +155,40 @@ class FacialRecognitionClient:
|
|
|
95
155
|
return await self.enroll_staff_base64(staff_data, base64_images)
|
|
96
156
|
|
|
97
157
|
async def enroll_staff_base64(self, staff_data: Dict[str, Any], base64_images: List[str]) -> Dict[str, Any]:
|
|
98
|
-
"""Enroll staff with base64 encoded images
|
|
158
|
+
"""Enroll staff with base64 encoded images
|
|
159
|
+
|
|
160
|
+
API: POST /v1/facial_recognition/staff/enroll?projectId={projectId}&serverID={serverID}
|
|
161
|
+
"""
|
|
99
162
|
|
|
100
|
-
# Prepare enrollment request
|
|
163
|
+
# Prepare enrollment request matching API spec
|
|
101
164
|
enrollment_request = {
|
|
102
|
-
"
|
|
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", ""),
|
|
103
171
|
"images": base64_images
|
|
104
172
|
}
|
|
105
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
|
+
|
|
106
177
|
# Use Matrice session for async RPC call
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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)}
|
|
113
192
|
|
|
114
193
|
async def search_similar_faces(self, face_embedding: List[float],
|
|
115
194
|
threshold: float = 0.3, limit: int = 10,
|
|
@@ -119,6 +198,8 @@ class FacialRecognitionClient:
|
|
|
119
198
|
"""
|
|
120
199
|
Search for staff members by face embedding vector
|
|
121
200
|
|
|
201
|
+
API: POST /v1/facial_recognition/search/similar?projectId={projectId}&serverID={serverID}
|
|
202
|
+
|
|
122
203
|
Args:
|
|
123
204
|
face_embedding: Face embedding vector
|
|
124
205
|
collection: Vector collection name
|
|
@@ -134,29 +215,67 @@ class FacialRecognitionClient:
|
|
|
134
215
|
"embedding": face_embedding,
|
|
135
216
|
"collection": collection,
|
|
136
217
|
"threshold": threshold,
|
|
137
|
-
"limit": limit
|
|
138
|
-
"location": location,
|
|
139
|
-
"timestamp": timestamp
|
|
218
|
+
"limit": limit
|
|
140
219
|
}
|
|
220
|
+
|
|
221
|
+
# Add optional fields only if provided
|
|
222
|
+
if location:
|
|
223
|
+
search_request["location"] = location
|
|
224
|
+
if timestamp:
|
|
225
|
+
search_request["timestamp"] = timestamp
|
|
226
|
+
|
|
227
|
+
self.logger.debug(f"API REQUEST: Searching similar faces - threshold={threshold}, limit={limit}, collection={collection}, location={location}")
|
|
141
228
|
|
|
142
229
|
# Use Matrice session for async RPC call
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
230
|
+
try:
|
|
231
|
+
response = await self.session.rpc.async_send_request(
|
|
232
|
+
method="POST",
|
|
233
|
+
path=f"/v1/facial_recognition/search/similar?projectId={self.project_id}&serverID={self.server_id}",
|
|
234
|
+
payload=search_request,
|
|
235
|
+
base_url=self.server_base_url
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
results_count = 0
|
|
239
|
+
if response.get('success', False):
|
|
240
|
+
data = response.get('data', [])
|
|
241
|
+
results_count = len(data) if isinstance(data, list) else 0
|
|
242
|
+
self.logger.info(f"API RESPONSE: Face search completed - Found {results_count} matches")
|
|
243
|
+
if results_count > 0:
|
|
244
|
+
self.logger.debug(f"Top match: staff_id={data[0].get('staffId', 'N/A')}, score={data[0].get('score', 0):.3f}")
|
|
245
|
+
else:
|
|
246
|
+
self.logger.warning(f"Face search failed: {response.get('error', 'Unknown error')}")
|
|
247
|
+
|
|
248
|
+
return self._handle_response(response)
|
|
249
|
+
except Exception as e:
|
|
250
|
+
self.logger.error(f"API ERROR: Face search request failed - {e}", exc_info=True)
|
|
251
|
+
return {"success": False, "error": str(e)}
|
|
149
252
|
|
|
150
253
|
async def get_staff_details(self, staff_id: str) -> Dict[str, Any]:
|
|
151
|
-
"""Get full staff details by staff ID
|
|
254
|
+
"""Get full staff details by staff ID
|
|
255
|
+
|
|
256
|
+
API: GET /v1/facial_recognition/staff/:staffId?projectId={projectId}&serverID={serverID}
|
|
257
|
+
"""
|
|
258
|
+
|
|
259
|
+
self.logger.debug(f"API REQUEST: Getting staff details - staff_id={staff_id}")
|
|
152
260
|
|
|
153
261
|
# Use Matrice session for async RPC call
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
262
|
+
try:
|
|
263
|
+
response = await self.session.rpc.async_send_request(
|
|
264
|
+
method="GET",
|
|
265
|
+
path=f"/v1/facial_recognition/staff/{staff_id}?projectId={self.project_id}&serverID={self.server_id}",
|
|
266
|
+
payload={},
|
|
267
|
+
base_url=self.server_base_url
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
if response.get('success', False):
|
|
271
|
+
self.logger.info(f"API RESPONSE: Staff details retrieved successfully - staff_id={staff_id}")
|
|
272
|
+
else:
|
|
273
|
+
self.logger.warning(f"Failed to get staff details for staff_id={staff_id}: {response.get('error', 'Unknown error')}")
|
|
274
|
+
|
|
275
|
+
return self._handle_response(response)
|
|
276
|
+
except Exception as e:
|
|
277
|
+
self.logger.error(f"API ERROR: Get staff details request failed for staff_id={staff_id} - {e}", exc_info=True)
|
|
278
|
+
return {"success": False, "error": str(e)}
|
|
160
279
|
|
|
161
280
|
async def store_people_activity(self,
|
|
162
281
|
staff_id: str,
|
|
@@ -165,9 +284,12 @@ class FacialRecognitionClient:
|
|
|
165
284
|
location: str,
|
|
166
285
|
employee_id: Optional[str] = None,
|
|
167
286
|
timestamp: str = datetime.now(timezone.utc).isoformat(),
|
|
168
|
-
|
|
287
|
+
image_data: Optional[str] = None,
|
|
288
|
+
) -> Dict[str, Any]:
|
|
169
289
|
"""
|
|
170
|
-
Store people activity data
|
|
290
|
+
Store people activity data with optional image data
|
|
291
|
+
|
|
292
|
+
API: POST /v1/facial_recognition/store_people_activity?projectId={projectId}&serverID={serverID}
|
|
171
293
|
|
|
172
294
|
Args:
|
|
173
295
|
staff_id: Staff identifier (empty for unknown faces)
|
|
@@ -176,10 +298,10 @@ class FacialRecognitionClient:
|
|
|
176
298
|
location: Location identifier
|
|
177
299
|
employee_id: Employee ID (for unknown faces, this will be generated)
|
|
178
300
|
timestamp: Timestamp in ISO format
|
|
301
|
+
image_data: Base64-encoded JPEG image data (optional)
|
|
179
302
|
|
|
180
303
|
Returns:
|
|
181
|
-
Dict containing response data
|
|
182
|
-
or None if the request failed
|
|
304
|
+
Dict containing response data with success status
|
|
183
305
|
"""
|
|
184
306
|
activity_request = {
|
|
185
307
|
"staff_id": staff_id,
|
|
@@ -189,97 +311,201 @@ class FacialRecognitionClient:
|
|
|
189
311
|
"location": location,
|
|
190
312
|
}
|
|
191
313
|
|
|
192
|
-
# Add optional fields if provided
|
|
193
|
-
if detection_type == "unknown":
|
|
194
|
-
|
|
195
|
-
activity_request["anonymous_id"] = employee_id
|
|
314
|
+
# Add optional fields if provided based on API spec
|
|
315
|
+
if detection_type == "unknown" and employee_id:
|
|
316
|
+
activity_request["anonymous_id"] = employee_id
|
|
196
317
|
elif detection_type == "known" and employee_id:
|
|
197
318
|
activity_request["employee_id"] = employee_id
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
319
|
+
|
|
320
|
+
# Add image data if provided
|
|
321
|
+
if image_data:
|
|
322
|
+
activity_request["imageData"] = image_data
|
|
323
|
+
|
|
324
|
+
self.logger.info(f"API REQUEST: Storing people activity - type={detection_type}, staff_id={staff_id}, location={location}, has_image={bool(image_data)}")
|
|
325
|
+
self.logger.debug(f"Activity request payload: bbox={bbox}, employee_id={employee_id}")
|
|
326
|
+
|
|
327
|
+
try:
|
|
328
|
+
response = await self.session.rpc.async_send_request(
|
|
329
|
+
method="POST",
|
|
330
|
+
path=f"/v1/facial_recognition/store_people_activity?projectId={self.project_id}&serverID={self.server_id}",
|
|
331
|
+
payload=activity_request,
|
|
332
|
+
base_url=self.server_base_url
|
|
333
|
+
)
|
|
334
|
+
handled_response = self._handle_response(response)
|
|
335
|
+
|
|
336
|
+
if handled_response.get("success", False):
|
|
337
|
+
self.logger.info(f"API RESPONSE: Successfully stored {detection_type} activity for staff_id={staff_id}")
|
|
338
|
+
return handled_response
|
|
339
|
+
else:
|
|
340
|
+
self.logger.warning(f"Failed to store {detection_type} activity: {handled_response.get('error', 'Unknown error')}")
|
|
341
|
+
return handled_response
|
|
342
|
+
except Exception as e:
|
|
343
|
+
self.logger.error(f"API ERROR: Store people activity request failed - type={detection_type}, staff_id={staff_id} - {e}", exc_info=True)
|
|
344
|
+
return {"success": False, "error": str(e)}
|
|
214
345
|
|
|
215
346
|
async def update_staff_images(self, image_url: str, employee_id: str) -> Dict[str, Any]:
|
|
216
|
-
"""Update staff images with uploaded image URL
|
|
347
|
+
"""Update staff images with uploaded image URL
|
|
348
|
+
|
|
349
|
+
API: PUT /v1/facial_recognition/staff/update_images?projectId={projectId}&serverID={serverID}
|
|
350
|
+
"""
|
|
217
351
|
|
|
218
352
|
update_request = {
|
|
219
353
|
"imageUrl": image_url,
|
|
220
354
|
"employeeId": employee_id
|
|
221
355
|
}
|
|
222
356
|
|
|
357
|
+
self.logger.info(f"API REQUEST: Updating staff images - employee_id={employee_id}")
|
|
358
|
+
self.logger.debug(f"Update request: image_url={image_url[:50]}...")
|
|
359
|
+
|
|
223
360
|
# Use Matrice session for async RPC call
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
361
|
+
try:
|
|
362
|
+
response = await self.session.rpc.async_send_request(
|
|
363
|
+
method="PUT",
|
|
364
|
+
path=f"/v1/facial_recognition/staff/update_images?projectId={self.project_id}&serverID={self.server_id}",
|
|
365
|
+
payload=update_request,
|
|
366
|
+
base_url=self.server_base_url
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
if response.get('success', False):
|
|
370
|
+
self.logger.info(f"API RESPONSE: Staff images updated successfully - employee_id={employee_id}")
|
|
371
|
+
else:
|
|
372
|
+
self.logger.warning(f"Failed to update staff images for employee_id={employee_id}: {response.get('error', 'Unknown error')}")
|
|
373
|
+
|
|
374
|
+
return self._handle_response(response)
|
|
375
|
+
except Exception as e:
|
|
376
|
+
self.logger.error(f"API ERROR: Update staff images request failed - employee_id={employee_id} - {e}", exc_info=True)
|
|
377
|
+
return {"success": False, "error": str(e)}
|
|
230
378
|
|
|
231
379
|
async def upload_image_to_url(self, image_bytes: bytes, upload_url: str) -> bool:
|
|
232
380
|
"""Upload image bytes to the provided URL"""
|
|
233
381
|
try:
|
|
382
|
+
self.logger.info(f"API REQUEST: Uploading image to URL - size={len(image_bytes)} bytes")
|
|
383
|
+
self.logger.debug(f"Upload URL: {upload_url[:100]}...")
|
|
384
|
+
|
|
234
385
|
# Upload the image to the signed URL using async httpx
|
|
235
386
|
headers = {'Content-Type': 'image/jpeg'}
|
|
236
387
|
async with httpx.AsyncClient() as client:
|
|
237
388
|
response = await client.put(upload_url, content=image_bytes, headers=headers)
|
|
238
389
|
|
|
239
390
|
if response.status_code in [200, 201]:
|
|
240
|
-
self.logger.
|
|
391
|
+
self.logger.info(f"API RESPONSE: Successfully uploaded image - status={response.status_code}")
|
|
241
392
|
return True
|
|
242
393
|
else:
|
|
243
|
-
self.logger.error(f"Failed to upload image
|
|
394
|
+
self.logger.error(f"API ERROR: Failed to upload image - status={response.status_code}, response={response.text[:200]}")
|
|
244
395
|
return False
|
|
245
396
|
|
|
246
397
|
except Exception as e:
|
|
247
|
-
self.logger.error(f"
|
|
398
|
+
self.logger.error(f"API ERROR: Exception during image upload - {e}", exc_info=True)
|
|
248
399
|
return False
|
|
249
400
|
|
|
250
401
|
async def shutdown_service(self, action_record_id: Optional[str] = None) -> Dict[str, Any]:
|
|
251
|
-
"""Gracefully shutdown the service
|
|
402
|
+
"""Gracefully shutdown the service
|
|
403
|
+
|
|
404
|
+
API: DELETE /v1/facial_recognition/shutdown?projectId={projectId}&serverID={serverID}
|
|
405
|
+
"""
|
|
252
406
|
|
|
253
407
|
payload = {} if not action_record_id else {"actionRecordId": action_record_id}
|
|
254
408
|
|
|
409
|
+
self.logger.info(f"API REQUEST: Shutting down service - action_record_id={action_record_id}")
|
|
410
|
+
|
|
255
411
|
# Use Matrice session for async RPC call
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
412
|
+
try:
|
|
413
|
+
response = await self.session.rpc.async_send_request(
|
|
414
|
+
method="DELETE",
|
|
415
|
+
path=f"/v1/facial_recognition/shutdown?projectId={self.project_id}&serverID={self.server_id}",
|
|
416
|
+
payload=payload,
|
|
417
|
+
base_url=self.server_base_url
|
|
418
|
+
)
|
|
419
|
+
|
|
420
|
+
if response.get('success', False):
|
|
421
|
+
self.logger.info(f"API RESPONSE: Service shutdown successful")
|
|
422
|
+
else:
|
|
423
|
+
self.logger.warning(f"Service shutdown failed: {response.get('error', 'Unknown error')}")
|
|
424
|
+
|
|
425
|
+
return self._handle_response(response)
|
|
426
|
+
except Exception as e:
|
|
427
|
+
self.logger.error(f"API ERROR: Shutdown service request failed - {e}", exc_info=True)
|
|
428
|
+
return {"success": False, "error": str(e)}
|
|
262
429
|
|
|
263
430
|
async def get_all_staff_embeddings(self) -> Dict[str, Any]:
|
|
264
|
-
"""Get all staff embeddings
|
|
431
|
+
"""Get all staff embeddings
|
|
432
|
+
|
|
433
|
+
API: GET /v1/facial_recognition/get_all_staff_embeddings?projectId={projectId}&serverID={serverID}
|
|
434
|
+
"""
|
|
265
435
|
|
|
266
436
|
payload = {}
|
|
267
437
|
|
|
438
|
+
self.logger.info(f"API REQUEST: Getting all staff embeddings")
|
|
439
|
+
|
|
440
|
+
# Use Matrice session for async RPC call
|
|
441
|
+
try:
|
|
442
|
+
response = await self.session.rpc.async_send_request(
|
|
443
|
+
method="GET",
|
|
444
|
+
path=f"/v1/facial_recognition/get_all_staff_embeddings?projectId={self.project_id}&serverID={self.server_id}",
|
|
445
|
+
payload=payload,
|
|
446
|
+
base_url=self.server_base_url
|
|
447
|
+
)
|
|
448
|
+
|
|
449
|
+
embeddings_count = 0
|
|
450
|
+
if response.get('success', False):
|
|
451
|
+
data = response.get('data', [])
|
|
452
|
+
embeddings_count = len(data) if isinstance(data, list) else 0
|
|
453
|
+
self.logger.info(f"API RESPONSE: Retrieved {embeddings_count} staff embeddings")
|
|
454
|
+
else:
|
|
455
|
+
self.logger.warning(f"Failed to get staff embeddings: {response.get('error', 'Unknown error')}")
|
|
456
|
+
|
|
457
|
+
return self._handle_response(response)
|
|
458
|
+
except Exception as e:
|
|
459
|
+
self.logger.error(f"API ERROR: Get all staff embeddings request failed - {e}", exc_info=True)
|
|
460
|
+
return {"success": False, "error": str(e)}
|
|
461
|
+
|
|
462
|
+
async def update_deployment(self, deployment_id: str) -> Dict[str, Any]:
|
|
463
|
+
"""Update deployment to notify facial recognition server
|
|
464
|
+
|
|
465
|
+
API: PUT /v1/facial_recognition/update_deployment/:deployment_id
|
|
466
|
+
|
|
467
|
+
Args:
|
|
468
|
+
deployment_id: The deployment ID to update
|
|
469
|
+
|
|
470
|
+
Returns:
|
|
471
|
+
Dict containing response data
|
|
472
|
+
"""
|
|
473
|
+
if not deployment_id:
|
|
474
|
+
self.logger.warning("No deployment_id provided for update_deployment")
|
|
475
|
+
return {"success": False, "error": "deployment_id is required"}
|
|
476
|
+
|
|
477
|
+
self.logger.info(f"API REQUEST: Updating deployment - deployment_id={deployment_id}")
|
|
478
|
+
|
|
268
479
|
# Use Matrice session for async RPC call
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
480
|
+
try:
|
|
481
|
+
response = await self.session.rpc.async_send_request(
|
|
482
|
+
method="PUT",
|
|
483
|
+
path=f"/v1/facial_recognition/update_deployment/{deployment_id}",
|
|
484
|
+
payload={},
|
|
485
|
+
base_url=self.server_base_url
|
|
486
|
+
)
|
|
487
|
+
|
|
488
|
+
if response.get('success', False):
|
|
489
|
+
self.logger.info(f"API RESPONSE: Deployment updated successfully - deployment_id={deployment_id}")
|
|
490
|
+
else:
|
|
491
|
+
self.logger.warning(f"Failed to update deployment for deployment_id={deployment_id}: {response.get('error', 'Unknown error')}")
|
|
492
|
+
|
|
493
|
+
return self._handle_response(response)
|
|
494
|
+
except Exception as e:
|
|
495
|
+
self.logger.error(f"API ERROR: Update deployment request failed - deployment_id={deployment_id} - {e}", exc_info=True)
|
|
496
|
+
return {"success": False, "error": str(e)}
|
|
275
497
|
|
|
276
498
|
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]:
|
|
277
|
-
"""Enroll an unknown person
|
|
499
|
+
"""Enroll an unknown person
|
|
500
|
+
|
|
501
|
+
API: POST /v1/facial_recognition/enroll_unknown_person?projectId={projectId}&serverID={serverID}
|
|
502
|
+
"""
|
|
278
503
|
|
|
279
504
|
payload = {
|
|
280
505
|
"embedding": embedding
|
|
281
506
|
}
|
|
282
507
|
|
|
508
|
+
# Add optional fields based on API spec
|
|
283
509
|
if image_source:
|
|
284
510
|
payload["imageSource"] = image_source
|
|
285
511
|
if timestamp:
|
|
@@ -288,27 +514,52 @@ class FacialRecognitionClient:
|
|
|
288
514
|
payload["timestamp"] = datetime.now(timezone.utc).isoformat()
|
|
289
515
|
if location:
|
|
290
516
|
payload["location"] = location
|
|
291
|
-
|
|
292
|
-
|
|
517
|
+
|
|
518
|
+
self.logger.info(f"API REQUEST: Enrolling unknown person - location={location}")
|
|
519
|
+
self.logger.debug(f"Unknown enrollment payload: has_embedding={bool(embedding)}, has_image_source={bool(image_source)}")
|
|
293
520
|
|
|
294
521
|
# Use Matrice session for async RPC call
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
522
|
+
try:
|
|
523
|
+
response = await self.session.rpc.async_send_request(
|
|
524
|
+
method="POST",
|
|
525
|
+
path=f"/v1/facial_recognition/enroll_unknown_person?projectId={self.project_id}&serverID={self.server_id}",
|
|
526
|
+
payload=payload,
|
|
527
|
+
base_url=self.server_base_url
|
|
528
|
+
)
|
|
529
|
+
|
|
530
|
+
if response.get('success', False):
|
|
531
|
+
self.logger.info(f"API RESPONSE: Unknown person enrolled successfully")
|
|
532
|
+
else:
|
|
533
|
+
self.logger.warning(f"Failed to enroll unknown person: {response.get('error', 'Unknown error')}")
|
|
534
|
+
|
|
535
|
+
return self._handle_response(response)
|
|
536
|
+
except Exception as e:
|
|
537
|
+
self.logger.error(f"API ERROR: Enroll unknown person request failed - {e}", exc_info=True)
|
|
538
|
+
return {"success": False, "error": str(e)}
|
|
301
539
|
|
|
302
540
|
async def health_check(self) -> Dict[str, Any]:
|
|
303
541
|
"""Check if the facial recognition service is healthy"""
|
|
304
542
|
|
|
543
|
+
self.logger.debug(f"API REQUEST: Health check")
|
|
544
|
+
|
|
305
545
|
# Use Matrice session for async RPC call
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
546
|
+
try:
|
|
547
|
+
response = await self.session.rpc.async_send_request(
|
|
548
|
+
method="GET",
|
|
549
|
+
path=f"/v1/facial_recognition/health?serverID={self.server_id}",
|
|
550
|
+
payload={},
|
|
551
|
+
base_url=self.server_base_url
|
|
552
|
+
)
|
|
553
|
+
|
|
554
|
+
if response.get('success', False):
|
|
555
|
+
self.logger.info(f"API RESPONSE: Service is healthy")
|
|
556
|
+
else:
|
|
557
|
+
self.logger.warning(f"Health check failed: {response.get('error', 'Unknown error')}")
|
|
558
|
+
|
|
559
|
+
return self._handle_response(response)
|
|
560
|
+
except Exception as e:
|
|
561
|
+
self.logger.error(f"API ERROR: Health check request failed - {e}", exc_info=True)
|
|
562
|
+
return {"success": False, "error": str(e)}
|
|
312
563
|
|
|
313
564
|
def _handle_response(self, response: Dict[str, Any]) -> Dict[str, Any]:
|
|
314
565
|
"""Handle RPC response and errors"""
|