matrice-analytics 0.1.3__py3-none-any.whl → 0.1.31__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.

Files changed (59) hide show
  1. matrice_analytics/post_processing/advanced_tracker/matching.py +3 -3
  2. matrice_analytics/post_processing/advanced_tracker/strack.py +1 -1
  3. matrice_analytics/post_processing/face_reg/compare_similarity.py +5 -5
  4. matrice_analytics/post_processing/face_reg/embedding_manager.py +14 -7
  5. matrice_analytics/post_processing/face_reg/face_recognition.py +123 -34
  6. matrice_analytics/post_processing/face_reg/face_recognition_client.py +332 -82
  7. matrice_analytics/post_processing/face_reg/people_activity_logging.py +29 -22
  8. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/__init__.py +9 -0
  9. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/__init__.py +4 -0
  10. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/cli.py +33 -0
  11. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/dataset_stats.py +139 -0
  12. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/export.py +398 -0
  13. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/train.py +447 -0
  14. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/utils.py +129 -0
  15. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/valid.py +93 -0
  16. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/validate_dataset.py +240 -0
  17. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/visualize_augmentation.py +176 -0
  18. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/visualize_predictions.py +96 -0
  19. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/core/__init__.py +3 -0
  20. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/core/process.py +246 -0
  21. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/core/types.py +60 -0
  22. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/core/utils.py +87 -0
  23. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/inference/__init__.py +3 -0
  24. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/inference/config.py +82 -0
  25. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/inference/hub.py +141 -0
  26. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/inference/plate_recognizer.py +323 -0
  27. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/py.typed +0 -0
  28. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/__init__.py +0 -0
  29. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/data/__init__.py +0 -0
  30. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/data/augmentation.py +101 -0
  31. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/data/dataset.py +97 -0
  32. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/__init__.py +0 -0
  33. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/config.py +114 -0
  34. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/layers.py +553 -0
  35. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/loss.py +55 -0
  36. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/metric.py +86 -0
  37. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/model_builders.py +95 -0
  38. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/model_schema.py +395 -0
  39. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/utilities/__init__.py +0 -0
  40. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/utilities/backend_utils.py +38 -0
  41. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/utilities/utils.py +214 -0
  42. matrice_analytics/post_processing/ocr/postprocessing.py +0 -1
  43. matrice_analytics/post_processing/post_processor.py +19 -5
  44. matrice_analytics/post_processing/usecases/color/clip.py +42 -8
  45. matrice_analytics/post_processing/usecases/color/color_mapper.py +2 -2
  46. matrice_analytics/post_processing/usecases/color_detection.py +21 -98
  47. matrice_analytics/post_processing/usecases/drone_traffic_monitoring.py +41 -386
  48. matrice_analytics/post_processing/usecases/flare_analysis.py +1 -56
  49. matrice_analytics/post_processing/usecases/license_plate_detection.py +476 -202
  50. matrice_analytics/post_processing/usecases/license_plate_monitoring.py +252 -11
  51. matrice_analytics/post_processing/usecases/people_counting.py +408 -1431
  52. matrice_analytics/post_processing/usecases/people_counting_bckp.py +1683 -0
  53. matrice_analytics/post_processing/usecases/vehicle_monitoring.py +39 -10
  54. matrice_analytics/post_processing/utils/__init__.py +8 -8
  55. {matrice_analytics-0.1.3.dist-info → matrice_analytics-0.1.31.dist-info}/METADATA +1 -1
  56. {matrice_analytics-0.1.3.dist-info → matrice_analytics-0.1.31.dist-info}/RECORD +59 -24
  57. {matrice_analytics-0.1.3.dist-info → matrice_analytics-0.1.31.dist-info}/WHEEL +0 -0
  58. {matrice_analytics-0.1.3.dist-info → matrice_analytics-0.1.31.dist-info}/licenses/LICENSE.txt +0 -0
  59. {matrice_analytics-0.1.3.dist-info → matrice_analytics-0.1.31.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
- "staff_info": staff_data,
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
- response = await self.session.rpc.async_send_request(
108
- method="POST",
109
- path=f"/v1/actions/facial_recognition/staff/enroll?serverID={self.server_id}",
110
- payload=enrollment_request
111
- )
112
- return self._handle_response(response)
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
- response = await self.session.rpc.async_send_request(
144
- method="POST",
145
- path=f"/v1/actions/facial_recognition/search/similar?serverID={self.server_id}",
146
- payload=search_request
147
- )
148
- return self._handle_response(response)
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
- response = await self.session.rpc.async_send_request(
155
- method="GET",
156
- path=f"/v1/actions/facial_recognition/staff/{staff_id}?serverID={self.server_id}",
157
- payload={}
158
- )
159
- return self._handle_response(response)
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,
@@ -169,6 +288,8 @@ class FacialRecognitionClient:
169
288
  """
170
289
  Store people activity data and return response with potential upload URLs
171
290
 
291
+ API: POST /v1/facial_recognition/store_people_activity?projectId={projectId}&serverID={serverID}
292
+
172
293
  Args:
173
294
  staff_id: Staff identifier (empty for unknown faces)
174
295
  detection_type: Type of detection (known, unknown, empty)
@@ -189,97 +310,201 @@ class FacialRecognitionClient:
189
310
  "location": location,
190
311
  }
191
312
 
192
- # Add optional fields if provided
193
- if detection_type == "unknown":
194
- if employee_id:
195
- activity_request["anonymous_id"] = employee_id
313
+ # Add optional fields if provided based on API spec
314
+ if detection_type == "unknown" and employee_id:
315
+ activity_request["anonymous_id"] = employee_id
196
316
  elif detection_type == "known" and employee_id:
197
317
  activity_request["employee_id"] = employee_id
198
- response = await self.session.rpc.async_send_request(
199
- method="POST",
200
- path=f"/v1/actions/facial_recognition/store_people_activity?serverID={self.server_id}",
201
- payload=activity_request
202
- )
203
- handled_response = self._handle_response(response)
204
- if handled_response.get("success", False):
205
- data = handled_response.get("data", {})
206
- self.logger.debug(f"Successfully stored {detection_type} activity")
207
- if not data:
208
- self.logger.warning("No data returned form store people activity")
318
+
319
+ self.logger.info(f"API REQUEST: Storing people activity - type={detection_type}, staff_id={staff_id}, location={location}")
320
+ self.logger.debug(f"Activity request payload: bbox={bbox}, employee_id={employee_id}")
321
+
322
+ try:
323
+ response = await self.session.rpc.async_send_request(
324
+ method="POST",
325
+ path=f"/v1/facial_recognition/store_people_activity?projectId={self.project_id}&serverID={self.server_id}",
326
+ payload=activity_request,
327
+ base_url=self.server_base_url
328
+ )
329
+ handled_response = self._handle_response(response)
330
+
331
+ if handled_response.get("success", False):
332
+ data = handled_response.get("data", {})
333
+ self.logger.info(f"API RESPONSE: Successfully stored {detection_type} activity for staff_id={staff_id}")
334
+ if not data:
335
+ self.logger.warning(f"No data returned from store people activity for staff_id={staff_id}")
336
+ return None
337
+ return data
338
+ else:
339
+ self.logger.warning(f"Failed to store {detection_type} activity: {handled_response.get('error', 'Unknown error')}")
209
340
  return None
210
- return data
211
- else:
212
- self.logger.error(f"Failed to store {detection_type} activity: {handled_response.get('error', 'Unknown error')}")
341
+ except Exception as e:
342
+ self.logger.error(f"API ERROR: Store people activity request failed - type={detection_type}, staff_id={staff_id} - {e}", exc_info=True)
213
343
  return None
214
344
 
215
345
  async def update_staff_images(self, image_url: str, employee_id: str) -> Dict[str, Any]:
216
- """Update staff images with uploaded image URL"""
346
+ """Update staff images with uploaded image URL
347
+
348
+ API: PUT /v1/facial_recognition/staff/update_images?projectId={projectId}&serverID={serverID}
349
+ """
217
350
 
218
351
  update_request = {
219
352
  "imageUrl": image_url,
220
353
  "employeeId": employee_id
221
354
  }
222
355
 
356
+ self.logger.info(f"API REQUEST: Updating staff images - employee_id={employee_id}")
357
+ self.logger.debug(f"Update request: image_url={image_url[:50]}...")
358
+
223
359
  # Use Matrice session for async RPC call
224
- response = await self.session.rpc.async_send_request(
225
- method="PUT",
226
- path=f"/v1/actions/facial_recognition/update_staff_images?serverID={self.server_id}",
227
- payload=update_request
228
- )
229
- return self._handle_response(response)
360
+ try:
361
+ response = await self.session.rpc.async_send_request(
362
+ method="PUT",
363
+ path=f"/v1/facial_recognition/staff/update_images?projectId={self.project_id}&serverID={self.server_id}",
364
+ payload=update_request,
365
+ base_url=self.server_base_url
366
+ )
367
+
368
+ if response.get('success', False):
369
+ self.logger.info(f"API RESPONSE: Staff images updated successfully - employee_id={employee_id}")
370
+ else:
371
+ self.logger.warning(f"Failed to update staff images for employee_id={employee_id}: {response.get('error', 'Unknown error')}")
372
+
373
+ return self._handle_response(response)
374
+ except Exception as e:
375
+ self.logger.error(f"API ERROR: Update staff images request failed - employee_id={employee_id} - {e}", exc_info=True)
376
+ return {"success": False, "error": str(e)}
230
377
 
231
378
  async def upload_image_to_url(self, image_bytes: bytes, upload_url: str) -> bool:
232
379
  """Upload image bytes to the provided URL"""
233
380
  try:
381
+ self.logger.info(f"API REQUEST: Uploading image to URL - size={len(image_bytes)} bytes")
382
+ self.logger.debug(f"Upload URL: {upload_url[:100]}...")
383
+
234
384
  # Upload the image to the signed URL using async httpx
235
385
  headers = {'Content-Type': 'image/jpeg'}
236
386
  async with httpx.AsyncClient() as client:
237
387
  response = await client.put(upload_url, content=image_bytes, headers=headers)
238
388
 
239
389
  if response.status_code in [200, 201]:
240
- self.logger.debug(f"Successfully uploaded image to URL")
390
+ self.logger.info(f"API RESPONSE: Successfully uploaded image - status={response.status_code}")
241
391
  return True
242
392
  else:
243
- self.logger.error(f"Failed to upload image: {response.status_code} - {response.text}")
393
+ self.logger.error(f"API ERROR: Failed to upload image - status={response.status_code}, response={response.text[:200]}")
244
394
  return False
245
395
 
246
396
  except Exception as e:
247
- self.logger.error(f"Error uploading image to URL: {e}", exc_info=True)
397
+ self.logger.error(f"API ERROR: Exception during image upload - {e}", exc_info=True)
248
398
  return False
249
399
 
250
400
  async def shutdown_service(self, action_record_id: Optional[str] = None) -> Dict[str, Any]:
251
- """Gracefully shutdown the service"""
401
+ """Gracefully shutdown the service
402
+
403
+ API: DELETE /v1/facial_recognition/shutdown?projectId={projectId}&serverID={serverID}
404
+ """
252
405
 
253
406
  payload = {} if not action_record_id else {"actionRecordId": action_record_id}
254
407
 
408
+ self.logger.info(f"API REQUEST: Shutting down service - action_record_id={action_record_id}")
409
+
255
410
  # Use Matrice session for async RPC call
256
- response = await self.session.rpc.async_send_request(
257
- method="DELETE",
258
- path=f"/v1/actions/facial_recognition/shutdown?serverID={self.server_id}",
259
- payload=payload
260
- )
261
- return self._handle_response(response)
411
+ try:
412
+ response = await self.session.rpc.async_send_request(
413
+ method="DELETE",
414
+ path=f"/v1/facial_recognition/shutdown?projectId={self.project_id}&serverID={self.server_id}",
415
+ payload=payload,
416
+ base_url=self.server_base_url
417
+ )
418
+
419
+ if response.get('success', False):
420
+ self.logger.info(f"API RESPONSE: Service shutdown successful")
421
+ else:
422
+ self.logger.warning(f"Service shutdown failed: {response.get('error', 'Unknown error')}")
423
+
424
+ return self._handle_response(response)
425
+ except Exception as e:
426
+ self.logger.error(f"API ERROR: Shutdown service request failed - {e}", exc_info=True)
427
+ return {"success": False, "error": str(e)}
262
428
 
263
429
  async def get_all_staff_embeddings(self) -> Dict[str, Any]:
264
- """Get all staff embeddings"""
430
+ """Get all staff embeddings
431
+
432
+ API: GET /v1/facial_recognition/get_all_staff_embeddings?projectId={projectId}&serverID={serverID}
433
+ """
265
434
 
266
435
  payload = {}
267
436
 
437
+ self.logger.info(f"API REQUEST: Getting all staff embeddings")
438
+
268
439
  # Use Matrice session for async RPC call
269
- response = await self.session.rpc.async_send_request(
270
- method="GET",
271
- path=f"/v1/actions/facial_recognition/get_all_staff_embeddings?serverID={self.server_id}",
272
- payload=payload,
273
- )
274
- return self._handle_response(response)
440
+ try:
441
+ response = await self.session.rpc.async_send_request(
442
+ method="GET",
443
+ path=f"/v1/facial_recognition/get_all_staff_embeddings?projectId={self.project_id}&serverID={self.server_id}",
444
+ payload=payload,
445
+ base_url=self.server_base_url
446
+ )
447
+
448
+ embeddings_count = 0
449
+ if response.get('success', False):
450
+ data = response.get('data', [])
451
+ embeddings_count = len(data) if isinstance(data, list) else 0
452
+ self.logger.info(f"API RESPONSE: Retrieved {embeddings_count} staff embeddings")
453
+ else:
454
+ self.logger.warning(f"Failed to get staff embeddings: {response.get('error', 'Unknown error')}")
455
+
456
+ return self._handle_response(response)
457
+ except Exception as e:
458
+ self.logger.error(f"API ERROR: Get all staff embeddings request failed - {e}", exc_info=True)
459
+ return {"success": False, "error": str(e)}
460
+
461
+ async def update_deployment(self, deployment_id: str) -> Dict[str, Any]:
462
+ """Update deployment to notify facial recognition server
463
+
464
+ API: PUT /v1/facial_recognition/update_deployment/:deployment_id
465
+
466
+ Args:
467
+ deployment_id: The deployment ID to update
468
+
469
+ Returns:
470
+ Dict containing response data
471
+ """
472
+ if not deployment_id:
473
+ self.logger.warning("No deployment_id provided for update_deployment")
474
+ return {"success": False, "error": "deployment_id is required"}
475
+
476
+ self.logger.info(f"API REQUEST: Updating deployment - deployment_id={deployment_id}")
477
+
478
+ # Use Matrice session for async RPC call
479
+ try:
480
+ response = await self.session.rpc.async_send_request(
481
+ method="PUT",
482
+ path=f"/v1/facial_recognition/update_deployment/{deployment_id}",
483
+ payload={},
484
+ base_url=self.server_base_url
485
+ )
486
+
487
+ if response.get('success', False):
488
+ self.logger.info(f"API RESPONSE: Deployment updated successfully - deployment_id={deployment_id}")
489
+ else:
490
+ self.logger.warning(f"Failed to update deployment for deployment_id={deployment_id}: {response.get('error', 'Unknown error')}")
491
+
492
+ return self._handle_response(response)
493
+ except Exception as e:
494
+ self.logger.error(f"API ERROR: Update deployment request failed - deployment_id={deployment_id} - {e}", exc_info=True)
495
+ return {"success": False, "error": str(e)}
275
496
 
276
497
  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"""
498
+ """Enroll an unknown person
499
+
500
+ API: POST /v1/facial_recognition/enroll_unknown_person?projectId={projectId}&serverID={serverID}
501
+ """
278
502
 
279
503
  payload = {
280
504
  "embedding": embedding
281
505
  }
282
506
 
507
+ # Add optional fields based on API spec
283
508
  if image_source:
284
509
  payload["imageSource"] = image_source
285
510
  if timestamp:
@@ -288,27 +513,52 @@ class FacialRecognitionClient:
288
513
  payload["timestamp"] = datetime.now(timezone.utc).isoformat()
289
514
  if location:
290
515
  payload["location"] = location
291
- if employee_id:
292
- payload["employeeId"] = employee_id
516
+
517
+ self.logger.info(f"API REQUEST: Enrolling unknown person - location={location}")
518
+ self.logger.debug(f"Unknown enrollment payload: has_embedding={bool(embedding)}, has_image_source={bool(image_source)}")
293
519
 
294
520
  # Use Matrice session for async RPC call
295
- response = await self.session.rpc.async_send_request(
296
- method="POST",
297
- path=f"/v1/actions/facial_recognition/enroll_unknown_person?serverID={self.server_id}",
298
- payload=payload,
299
- )
300
- return self._handle_response(response)
521
+ try:
522
+ response = await self.session.rpc.async_send_request(
523
+ method="POST",
524
+ path=f"/v1/facial_recognition/enroll_unknown_person?projectId={self.project_id}&serverID={self.server_id}",
525
+ payload=payload,
526
+ base_url=self.server_base_url
527
+ )
528
+
529
+ if response.get('success', False):
530
+ self.logger.info(f"API RESPONSE: Unknown person enrolled successfully")
531
+ else:
532
+ self.logger.warning(f"Failed to enroll unknown person: {response.get('error', 'Unknown error')}")
533
+
534
+ return self._handle_response(response)
535
+ except Exception as e:
536
+ self.logger.error(f"API ERROR: Enroll unknown person request failed - {e}", exc_info=True)
537
+ return {"success": False, "error": str(e)}
301
538
 
302
539
  async def health_check(self) -> Dict[str, Any]:
303
540
  """Check if the facial recognition service is healthy"""
304
541
 
542
+ self.logger.debug(f"API REQUEST: Health check")
543
+
305
544
  # Use Matrice session for async RPC call
306
- response = await self.session.rpc.async_send_request(
307
- method="GET",
308
- path=f"/v1/actions/facial_recognition/health?serverID={self.server_id}",
309
- payload={}
310
- )
311
- return self._handle_response(response)
545
+ try:
546
+ response = await self.session.rpc.async_send_request(
547
+ method="GET",
548
+ path=f"/v1/facial_recognition/health?serverID={self.server_id}",
549
+ payload={},
550
+ base_url=self.server_base_url
551
+ )
552
+
553
+ if response.get('success', False):
554
+ self.logger.info(f"API RESPONSE: Service is healthy")
555
+ else:
556
+ self.logger.warning(f"Health check failed: {response.get('error', 'Unknown error')}")
557
+
558
+ return self._handle_response(response)
559
+ except Exception as e:
560
+ self.logger.error(f"API ERROR: Health check request failed - {e}", exc_info=True)
561
+ return {"success": False, "error": str(e)}
312
562
 
313
563
  def _handle_response(self, response: Dict[str, Any]) -> Dict[str, Any]:
314
564
  """Handle RPC response and errors"""
@@ -2,6 +2,7 @@ import asyncio
2
2
  import logging
3
3
  import time
4
4
  import threading
5
+ import queue
5
6
  from typing import Dict, Optional, Set
6
7
  import numpy as np
7
8
  import cv2
@@ -16,8 +17,8 @@ class PeopleActivityLogging:
16
17
  self.face_client = face_client
17
18
  self.logger = logging.getLogger(__name__)
18
19
 
19
- # Queue for processing detections in background
20
- self.activity_queue = asyncio.Queue()
20
+ # Use thread-safe queue for cross-thread communication (Python 3.8 compatibility)
21
+ self.activity_queue = queue.Queue()
21
22
 
22
23
  # Thread for background processing
23
24
  self.processing_thread = None
@@ -75,14 +76,12 @@ class PeopleActivityLogging:
75
76
  """Process activity queue continuously"""
76
77
  while self.is_running:
77
78
  try:
78
- # Process queued detections with timeout
79
+ # Process queued detections with timeout using thread-safe queue
79
80
  try:
80
- activity_data = await asyncio.wait_for(
81
- self.activity_queue.get(), timeout=20
82
- )
81
+ activity_data = self.activity_queue.get(timeout=20)
83
82
  await self._process_activity(activity_data)
84
83
  self.activity_queue.task_done()
85
- except asyncio.TimeoutError:
84
+ except queue.Empty:
86
85
  # Continue loop to check for empty detections
87
86
  continue
88
87
 
@@ -135,7 +134,8 @@ class PeopleActivityLogging:
135
134
  self.last_detection_time = time.time()
136
135
  self.empty_detection_logged = False
137
136
 
138
- await self.activity_queue.put(activity_data)
137
+ # Use thread-safe put (no await needed for queue.Queue)
138
+ self.activity_queue.put(activity_data)
139
139
  except Exception as e:
140
140
  self.logger.error(f"Error enqueueing detection: {e}", exc_info=True)
141
141
 
@@ -180,14 +180,16 @@ class PeopleActivityLogging:
180
180
 
181
181
  try:
182
182
  if not self.face_client:
183
+ self.logger.warning("Face client not available for activity logging")
183
184
  return
184
185
 
185
186
  # Check if we should log this detection (avoid duplicates within time window)
186
187
  if not self._should_log_detection(employee_id):
188
+ self.logger.debug(f"Skipping activity log for employee_id={employee_id} (within cooldown period)")
187
189
  return None
188
190
 
189
191
  # Store activity data
190
- self.logger.info(f"Storing activity data for employee {employee_id}")
192
+ self.logger.info(f"Processing activity log - type={detection_type}, employee_id={employee_id}, staff_id={staff_id}, location={location}")
191
193
  upload_url = await self.face_client.store_people_activity(
192
194
  staff_id=staff_id,
193
195
  detection_type=detection_type,
@@ -198,28 +200,33 @@ class PeopleActivityLogging:
198
200
  )
199
201
 
200
202
  if upload_url:
201
- self.logger.debug("Successfully stored activity log and fetched upload URL")
203
+ self.logger.info(f"Activity log stored successfully, upload URL received for employee_id={employee_id}")
202
204
  await self._upload_frame(current_frame, upload_url, employee_id)
203
205
  else:
204
- self.logger.warning("Failed to store activity log")
206
+ self.logger.warning(f"Failed to store activity log for employee_id={employee_id} - no upload URL returned")
205
207
 
206
208
  return upload_url
207
209
  except Exception as e:
208
- self.logger.error(f"Error storing activity log: {e}", exc_info=True)
210
+ self.logger.error(f"Error processing activity log for employee_id={employee_id}: {e}", exc_info=True)
209
211
 
210
212
 
211
213
  async def _upload_frame(self, current_frame: np.ndarray, upload_url: str, employee_id: str):
212
- _, buffer = cv2.imencode(".jpg", current_frame)
213
- frame_bytes = buffer.tobytes()
214
-
215
- upload_success = await self.face_client.upload_image_to_url(
216
- frame_bytes, upload_url
217
- )
214
+ try:
215
+ self.logger.debug(f"Encoding frame for upload - employee_id={employee_id}")
216
+ _, buffer = cv2.imencode(".jpg", current_frame)
217
+ frame_bytes = buffer.tobytes()
218
+
219
+ self.logger.info(f"Uploading frame to storage - employee_id={employee_id}, size={len(frame_bytes)} bytes")
220
+ upload_success = await self.face_client.upload_image_to_url(
221
+ frame_bytes, upload_url
222
+ )
218
223
 
219
- if upload_success:
220
- self.logger.info(f"Successfully uploaded whole frame for employee {employee_id}")
221
- else:
222
- self.logger.warning("Failed to upload whole frame")
224
+ if upload_success:
225
+ self.logger.info(f"Frame uploaded successfully for employee_id={employee_id}")
226
+ else:
227
+ self.logger.warning(f"Failed to upload frame for employee_id={employee_id}")
228
+ except Exception as e:
229
+ self.logger.error(f"Error uploading frame for employee_id={employee_id}: {e}", exc_info=True)
223
230
 
224
231
  async def _should_log_activity(self, activity_data: Dict) -> bool:
225
232
  """Check if activity should be logged"""