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.
- matrice_analytics/post_processing/advanced_tracker/matching.py +3 -3
- matrice_analytics/post_processing/advanced_tracker/strack.py +1 -1
- matrice_analytics/post_processing/face_reg/compare_similarity.py +5 -5
- matrice_analytics/post_processing/face_reg/embedding_manager.py +14 -7
- matrice_analytics/post_processing/face_reg/face_recognition.py +123 -34
- matrice_analytics/post_processing/face_reg/face_recognition_client.py +332 -82
- matrice_analytics/post_processing/face_reg/people_activity_logging.py +29 -22
- 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 +19 -5
- 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 +21 -98
- 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 +252 -11
- 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.31.dist-info}/METADATA +1 -1
- {matrice_analytics-0.1.3.dist-info → matrice_analytics-0.1.31.dist-info}/RECORD +59 -24
- {matrice_analytics-0.1.3.dist-info → matrice_analytics-0.1.31.dist-info}/WHEEL +0 -0
- {matrice_analytics-0.1.3.dist-info → matrice_analytics-0.1.31.dist-info}/licenses/LICENSE.txt +0 -0
- {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
|
-
"
|
|
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,
|
|
@@ -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
|
-
|
|
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
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
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
|
-
|
|
211
|
-
|
|
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
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
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.
|
|
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
|
|
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"
|
|
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
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
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
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
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
|
-
|
|
292
|
-
|
|
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
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
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
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
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
|
-
#
|
|
20
|
-
self.activity_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 =
|
|
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
|
|
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
|
|
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"
|
|
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.
|
|
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
|
|
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
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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"""
|