matrice-analytics 0.1.44__py3-none-any.whl → 0.1.45__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/config.py +1 -1
- matrice_analytics/post_processing/face_reg/face_recognition.py +75 -64
- matrice_analytics/post_processing/face_reg/face_recognition_client.py +24 -19
- matrice_analytics/post_processing/face_reg/people_activity_logging.py +6 -4
- matrice_analytics/post_processing/ocr/easyocr_extractor.py +3 -1
- matrice_analytics/post_processing/test_cases/test_usecases.py +165 -0
- matrice_analytics/post_processing/usecases/color/clip.py +4 -3
- matrice_analytics/post_processing/usecases/color/color_mapper.py +1 -1
- matrice_analytics/post_processing/usecases/fire_detection.py +0 -1
- {matrice_analytics-0.1.44.dist-info → matrice_analytics-0.1.45.dist-info}/METADATA +1 -1
- {matrice_analytics-0.1.44.dist-info → matrice_analytics-0.1.45.dist-info}/RECORD +14 -13
- {matrice_analytics-0.1.44.dist-info → matrice_analytics-0.1.45.dist-info}/WHEEL +0 -0
- {matrice_analytics-0.1.44.dist-info → matrice_analytics-0.1.45.dist-info}/licenses/LICENSE.txt +0 -0
- {matrice_analytics-0.1.44.dist-info → matrice_analytics-0.1.45.dist-info}/top_level.txt +0 -0
|
@@ -143,4 +143,4 @@ def get_usecase_from_app_name(app_name: str) -> str:
|
|
|
143
143
|
|
|
144
144
|
def get_category_from_app_name(app_name: str) -> str:
|
|
145
145
|
normalized_app_name = app_name.lower().replace(" ", "_").replace("-", "_")
|
|
146
|
-
return APP_NAME_TO_CATEGORY.get(app_name, APP_NAME_TO_CATEGORY.get(normalized_app_name))
|
|
146
|
+
return APP_NAME_TO_CATEGORY.get(app_name, APP_NAME_TO_CATEGORY.get(normalized_app_name))
|
|
@@ -24,7 +24,7 @@ subprocess.run(
|
|
|
24
24
|
cmd,
|
|
25
25
|
stdout=log_file,
|
|
26
26
|
stderr=subprocess.STDOUT,
|
|
27
|
-
preexec_fn=os.setpgrp
|
|
27
|
+
# preexec_fn=os.setpgrp
|
|
28
28
|
)
|
|
29
29
|
log_file.close()
|
|
30
30
|
|
|
@@ -81,13 +81,13 @@ class TemporalIdentityManager:
|
|
|
81
81
|
"""
|
|
82
82
|
Maintains stable identity labels per tracker ID using temporal smoothing and embedding history.
|
|
83
83
|
|
|
84
|
-
|
|
85
|
-
via search_similar_faces(embedding, threshold=0.01, limit=1) to obtain top-1 match and score.
|
|
84
|
+
Uses local embedding similarity search via EmbeddingManager instead of API calls.
|
|
86
85
|
"""
|
|
87
86
|
|
|
88
87
|
def __init__(
|
|
89
88
|
self,
|
|
90
89
|
face_client: FacialRecognitionClient,
|
|
90
|
+
embedding_manager: Optional[Any] = None, # EmbeddingManager instance
|
|
91
91
|
recognition_threshold: float = 0.35,
|
|
92
92
|
history_size: int = 20,
|
|
93
93
|
unknown_patience: int = 7,
|
|
@@ -96,6 +96,7 @@ class TemporalIdentityManager:
|
|
|
96
96
|
) -> None:
|
|
97
97
|
self.logger = logging.getLogger(__name__)
|
|
98
98
|
self.face_client = face_client
|
|
99
|
+
self.embedding_manager = embedding_manager
|
|
99
100
|
self.threshold = float(recognition_threshold)
|
|
100
101
|
self.history_size = int(history_size)
|
|
101
102
|
self.unknown_patience = int(unknown_patience)
|
|
@@ -119,69 +120,65 @@ class TemporalIdentityManager:
|
|
|
119
120
|
|
|
120
121
|
async def _compute_best_identity(self, emb: List[float], location: str = "", timestamp: str = "") -> Tuple[Optional[str], str, float, Optional[str], Dict[str, Any], str]:
|
|
121
122
|
"""
|
|
122
|
-
Query
|
|
123
|
+
Query embedding manager for top-1 match using local similarity search.
|
|
123
124
|
Returns (staff_id, person_name, score, employee_id, staff_details, detection_type).
|
|
124
|
-
|
|
125
|
+
|
|
126
|
+
NOTE: API call to search_similar_faces is commented out - using local embeddings only.
|
|
125
127
|
"""
|
|
126
128
|
if not emb or not isinstance(emb, list):
|
|
127
129
|
return None, "Unknown", 0.0, None, {}, "unknown"
|
|
130
|
+
|
|
131
|
+
# COMMENTED OUT: API-based search - now using local embedding similarity search
|
|
132
|
+
# try:
|
|
133
|
+
# resp = await self.face_client.search_similar_faces(
|
|
134
|
+
# face_embedding=emb,
|
|
135
|
+
# threshold=0.01, # low threshold to always get top-1
|
|
136
|
+
# limit=1,
|
|
137
|
+
# collection="staff_enrollment",
|
|
138
|
+
# location=location,
|
|
139
|
+
# timestamp=timestamp,
|
|
140
|
+
# )
|
|
141
|
+
# except Exception as e:
|
|
142
|
+
# self.logger.error(f"API ERROR: Failed to search similar faces in _compute_best_identity: {e}", exc_info=True)
|
|
143
|
+
# return None, "Unknown", 0.0, None, {}, "unknown"
|
|
144
|
+
#
|
|
145
|
+
# [API response parsing code removed - see git history]
|
|
146
|
+
|
|
147
|
+
# NEW: Use local embedding manager for similarity search
|
|
128
148
|
try:
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
149
|
+
if not self.embedding_manager:
|
|
150
|
+
self.logger.warning("Embedding manager not available for local similarity search")
|
|
151
|
+
return None, "Unknown", 0.0, None, {}, "unknown"
|
|
152
|
+
|
|
153
|
+
# Perform local similarity search using embedding manager
|
|
154
|
+
search_result = await self.embedding_manager.search_face_embedding(
|
|
155
|
+
embedding=emb,
|
|
156
|
+
track_id=None, # No track_id needed for this search
|
|
134
157
|
location=location,
|
|
135
158
|
timestamp=timestamp,
|
|
136
159
|
)
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
try:
|
|
142
|
-
results: List[Any] = []
|
|
143
|
-
self.logger.debug('API Response received for identity search')
|
|
144
|
-
if isinstance(resp, dict):
|
|
145
|
-
if isinstance(resp.get("data"), list):
|
|
146
|
-
results = resp.get("data", [])
|
|
147
|
-
elif isinstance(resp.get("results"), list):
|
|
148
|
-
results = resp.get("results", [])
|
|
149
|
-
elif isinstance(resp.get("items"), list):
|
|
150
|
-
results = resp.get("items", [])
|
|
151
|
-
elif isinstance(resp, list):
|
|
152
|
-
results = resp
|
|
153
|
-
|
|
154
|
-
if not results:
|
|
155
|
-
self.logger.debug("No identity match found from API")
|
|
160
|
+
|
|
161
|
+
if not search_result:
|
|
162
|
+
self.logger.debug("No identity match found from local embeddings")
|
|
156
163
|
return None, "Unknown", 0.0, None, {}, "unknown"
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
employee_id =
|
|
163
|
-
|
|
164
|
-
detection_type =
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
if isinstance(staff_details, dict) and staff_details:
|
|
169
|
-
first_name = staff_details.get("firstName")
|
|
170
|
-
last_name = staff_details.get("lastName")
|
|
171
|
-
name = staff_details.get("name")
|
|
172
|
-
if name:
|
|
173
|
-
person_name = str(name)
|
|
174
|
-
else:
|
|
175
|
-
if first_name or last_name:
|
|
176
|
-
person_name = f"{first_name or ''} {last_name or ''}".strip() or "UnknowNN" #TODO:ebugging change to normal once done
|
|
177
|
-
# If API says unknown or missing staff_id, treat as unknown
|
|
178
|
-
if not staff_id: #or detection_type == "unknown"
|
|
179
|
-
self.logger.debug(f"API returned unknown or missing staff_id - score={score}, employee_id={employee_id}")
|
|
164
|
+
|
|
165
|
+
# Extract data from SearchResult
|
|
166
|
+
staff_id = search_result.staff_id
|
|
167
|
+
person_name = search_result.person_name
|
|
168
|
+
score = search_result.similarity_score
|
|
169
|
+
employee_id = search_result.employee_id
|
|
170
|
+
staff_details = search_result.staff_details
|
|
171
|
+
detection_type = search_result.detection_type
|
|
172
|
+
|
|
173
|
+
if not staff_id or detection_type == "unknown":
|
|
174
|
+
self.logger.debug(f"Local search returned unknown or missing staff_id - score={score}, employee_id={employee_id}")
|
|
180
175
|
return None, "Unknown", float(score), employee_id, staff_details if isinstance(staff_details, dict) else {}, "unknown"
|
|
181
|
-
|
|
176
|
+
|
|
177
|
+
self.logger.info(f"Local embedding identified face - staff_id={staff_id}, person_name={person_name}, score={score:.3f}")
|
|
182
178
|
return str(staff_id), person_name, float(score), employee_id, staff_details if isinstance(staff_details, dict) else {}, "known"
|
|
179
|
+
|
|
183
180
|
except Exception as e:
|
|
184
|
-
self.logger.error(f"Error
|
|
181
|
+
self.logger.error(f"Error in local embedding similarity search: {e}", exc_info=True)
|
|
185
182
|
return None, "Unknown", 0.0, None, {}, "unknown"
|
|
186
183
|
|
|
187
184
|
async def _compute_best_identity_from_history(self, track_state: Dict[str, object], location: str = "", timestamp: str = "") -> Tuple[Optional[str], str, float, Optional[str], Dict[str, Any], str]:
|
|
@@ -488,16 +485,17 @@ class FaceRecognitionEmbeddingUseCase(BaseProcessor):
|
|
|
488
485
|
self.embedding_manager = EmbeddingManager(init_config.embedding_config, self.face_client)
|
|
489
486
|
self.logger.info("Embedding manager initialized")
|
|
490
487
|
|
|
491
|
-
# Initialize TemporalIdentityManager
|
|
488
|
+
# Initialize TemporalIdentityManager with embedding_manager for local search
|
|
492
489
|
self.temporal_identity_manager = TemporalIdentityManager(
|
|
493
490
|
face_client=self.face_client,
|
|
491
|
+
embedding_manager=self.embedding_manager, # Pass embedding manager for local similarity search
|
|
494
492
|
recognition_threshold=float(init_config.similarity_threshold),
|
|
495
493
|
history_size=20,
|
|
496
494
|
unknown_patience=7,
|
|
497
495
|
switch_patience=5,
|
|
498
496
|
fallback_margin=0.05,
|
|
499
497
|
)
|
|
500
|
-
self.logger.info("Temporal identity manager initialized")
|
|
498
|
+
self.logger.info("Temporal identity manager initialized with local embedding search")
|
|
501
499
|
|
|
502
500
|
self._initialized = True
|
|
503
501
|
self.logger.info("Face recognition use case fully initialized")
|
|
@@ -909,16 +907,28 @@ class FaceRecognitionEmbeddingUseCase(BaseProcessor):
|
|
|
909
907
|
# Generate current timestamp
|
|
910
908
|
current_timestamp = datetime.now(timezone.utc).isoformat()
|
|
911
909
|
|
|
912
|
-
|
|
913
|
-
#
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
# Process each detection sequentially with await to preserve order
|
|
917
|
-
processed_detection = await self._process_face(
|
|
910
|
+
# Process all detections in parallel for better performance
|
|
911
|
+
# Create tasks for parallel processing
|
|
912
|
+
tasks = [
|
|
913
|
+
self._process_face(
|
|
918
914
|
detection, current_frame, location, current_timestamp, config,
|
|
919
915
|
current_recognized_count, current_unknown_count,
|
|
920
916
|
recognized_persons, current_frame_staff_details
|
|
921
917
|
)
|
|
918
|
+
for detection in detections
|
|
919
|
+
]
|
|
920
|
+
|
|
921
|
+
# Process all faces in parallel
|
|
922
|
+
processed_detections = await asyncio.gather(*tasks, return_exceptions=True)
|
|
923
|
+
|
|
924
|
+
# Build final results and update counters (maintains order)
|
|
925
|
+
final_detections = []
|
|
926
|
+
for processed_detection in processed_detections:
|
|
927
|
+
# Handle exceptions from parallel processing
|
|
928
|
+
if isinstance(processed_detection, Exception):
|
|
929
|
+
self.logger.error(f"Error processing face detection: {processed_detection}", exc_info=True)
|
|
930
|
+
continue
|
|
931
|
+
|
|
922
932
|
# Include both known and unknown faces in final detections (maintains original order)
|
|
923
933
|
if processed_detection:
|
|
924
934
|
final_detections.append(processed_detection)
|
|
@@ -1085,7 +1095,7 @@ class FaceRecognitionEmbeddingUseCase(BaseProcessor):
|
|
|
1085
1095
|
# If it later becomes recognized, we'll remove it from unknown set above
|
|
1086
1096
|
self._unknown_track_ids.add(internal_tid)
|
|
1087
1097
|
|
|
1088
|
-
# Enqueue detection for background logging with all required parameters
|
|
1098
|
+
# Enqueue detection for background logging with all required parameters (non-blocking)
|
|
1089
1099
|
try:
|
|
1090
1100
|
# Log known faces for activity tracking (skip any employee_id starting with "unknown_")
|
|
1091
1101
|
if (
|
|
@@ -1096,7 +1106,8 @@ class FaceRecognitionEmbeddingUseCase(BaseProcessor):
|
|
|
1096
1106
|
and employee_id
|
|
1097
1107
|
and not str(employee_id).startswith("unknown_")
|
|
1098
1108
|
):
|
|
1099
|
-
await
|
|
1109
|
+
# Non-blocking enqueue - no await needed
|
|
1110
|
+
self.people_activity_logging.enqueue_detection(
|
|
1100
1111
|
detection=detection,
|
|
1101
1112
|
current_frame=current_frame,
|
|
1102
1113
|
location=location,
|
|
@@ -182,9 +182,9 @@ class FacialRecognitionClient:
|
|
|
182
182
|
payload=enrollment_request,
|
|
183
183
|
base_url=self.server_base_url
|
|
184
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')}")
|
|
185
|
+
self.logger.info(f"API RESPONSE: Staff enrollment completed - Success: {response.get('success', False) if response else False}")
|
|
186
|
+
if not response or not response.get('success', False):
|
|
187
|
+
self.logger.warning(f"Staff enrollment failed: {response.get('error', 'Unknown error') if response else 'No response'}")
|
|
188
188
|
return self._handle_response(response)
|
|
189
189
|
except Exception as e:
|
|
190
190
|
self.logger.error(f"API ERROR: Staff enrollment request failed - {e}", exc_info=True)
|
|
@@ -236,14 +236,14 @@ class FacialRecognitionClient:
|
|
|
236
236
|
)
|
|
237
237
|
|
|
238
238
|
results_count = 0
|
|
239
|
-
if response.get('success', False):
|
|
239
|
+
if response and response.get('success', False):
|
|
240
240
|
data = response.get('data', [])
|
|
241
241
|
results_count = len(data) if isinstance(data, list) else 0
|
|
242
242
|
self.logger.info(f"API RESPONSE: Face search completed - Found {results_count} matches")
|
|
243
243
|
if results_count > 0:
|
|
244
244
|
self.logger.debug(f"Top match: staff_id={data[0].get('staffId', 'N/A')}, score={data[0].get('score', 0):.3f}")
|
|
245
245
|
else:
|
|
246
|
-
self.logger.warning(f"Face search failed: {response.get('error', 'Unknown error')}")
|
|
246
|
+
self.logger.warning(f"Face search failed: {response.get('error', 'Unknown error') if response else 'No response'}")
|
|
247
247
|
|
|
248
248
|
return self._handle_response(response)
|
|
249
249
|
except Exception as e:
|
|
@@ -267,10 +267,10 @@ class FacialRecognitionClient:
|
|
|
267
267
|
base_url=self.server_base_url
|
|
268
268
|
)
|
|
269
269
|
|
|
270
|
-
if response.get('success', False):
|
|
270
|
+
if response and response.get('success', False):
|
|
271
271
|
self.logger.info(f"API RESPONSE: Staff details retrieved successfully - staff_id={staff_id}")
|
|
272
272
|
else:
|
|
273
|
-
self.logger.warning(f"Failed to get staff details for staff_id={staff_id}: {response.get('error', 'Unknown error')}")
|
|
273
|
+
self.logger.warning(f"Failed to get staff details for staff_id={staff_id}: {response.get('error', 'Unknown error') if response else 'No response'}")
|
|
274
274
|
|
|
275
275
|
return self._handle_response(response)
|
|
276
276
|
except Exception as e:
|
|
@@ -366,10 +366,10 @@ class FacialRecognitionClient:
|
|
|
366
366
|
base_url=self.server_base_url
|
|
367
367
|
)
|
|
368
368
|
|
|
369
|
-
if response.get('success', False):
|
|
369
|
+
if response and response.get('success', False):
|
|
370
370
|
self.logger.info(f"API RESPONSE: Staff images updated successfully - employee_id={employee_id}")
|
|
371
371
|
else:
|
|
372
|
-
self.logger.warning(f"Failed to update staff images for employee_id={employee_id}: {response.get('error', 'Unknown error')}")
|
|
372
|
+
self.logger.warning(f"Failed to update staff images for employee_id={employee_id}: {response.get('error', 'Unknown error') if response else 'No response'}")
|
|
373
373
|
|
|
374
374
|
return self._handle_response(response)
|
|
375
375
|
except Exception as e:
|
|
@@ -417,10 +417,10 @@ class FacialRecognitionClient:
|
|
|
417
417
|
base_url=self.server_base_url
|
|
418
418
|
)
|
|
419
419
|
|
|
420
|
-
if response.get('success', False):
|
|
420
|
+
if response and response.get('success', False):
|
|
421
421
|
self.logger.info(f"API RESPONSE: Service shutdown successful")
|
|
422
422
|
else:
|
|
423
|
-
self.logger.warning(f"Service shutdown failed: {response.get('error', 'Unknown error')}")
|
|
423
|
+
self.logger.warning(f"Service shutdown failed: {response.get('error', 'Unknown error') if response else 'No response'}")
|
|
424
424
|
|
|
425
425
|
return self._handle_response(response)
|
|
426
426
|
except Exception as e:
|
|
@@ -447,12 +447,12 @@ class FacialRecognitionClient:
|
|
|
447
447
|
)
|
|
448
448
|
|
|
449
449
|
embeddings_count = 0
|
|
450
|
-
if response.get('success', False):
|
|
450
|
+
if response and response.get('success', False):
|
|
451
451
|
data = response.get('data', [])
|
|
452
452
|
embeddings_count = len(data) if isinstance(data, list) else 0
|
|
453
453
|
self.logger.info(f"API RESPONSE: Retrieved {embeddings_count} staff embeddings")
|
|
454
454
|
else:
|
|
455
|
-
self.logger.warning(f"Failed to get staff embeddings: {response.get('error', 'Unknown error')}")
|
|
455
|
+
self.logger.warning(f"Failed to get staff embeddings: {response.get('error', 'Unknown error') if response else 'No response'}")
|
|
456
456
|
|
|
457
457
|
return self._handle_response(response)
|
|
458
458
|
except Exception as e:
|
|
@@ -485,10 +485,10 @@ class FacialRecognitionClient:
|
|
|
485
485
|
base_url=self.server_base_url
|
|
486
486
|
)
|
|
487
487
|
|
|
488
|
-
if response.get('success', False):
|
|
488
|
+
if response and response.get('success', False):
|
|
489
489
|
self.logger.info(f"API RESPONSE: Deployment updated successfully - deployment_id={deployment_id}")
|
|
490
490
|
else:
|
|
491
|
-
self.logger.warning(f"Failed to update deployment for deployment_id={deployment_id}: {response.get('error', 'Unknown error')}")
|
|
491
|
+
self.logger.warning(f"Failed to update deployment for deployment_id={deployment_id}: {response.get('error', 'Unknown error') if response else 'No response'}")
|
|
492
492
|
|
|
493
493
|
return self._handle_response(response)
|
|
494
494
|
except Exception as e:
|
|
@@ -527,10 +527,10 @@ class FacialRecognitionClient:
|
|
|
527
527
|
base_url=self.server_base_url
|
|
528
528
|
)
|
|
529
529
|
|
|
530
|
-
if response.get('success', False):
|
|
530
|
+
if response and response.get('success', False):
|
|
531
531
|
self.logger.info(f"API RESPONSE: Unknown person enrolled successfully")
|
|
532
532
|
else:
|
|
533
|
-
self.logger.warning(f"Failed to enroll unknown person: {response.get('error', 'Unknown error')}")
|
|
533
|
+
self.logger.warning(f"Failed to enroll unknown person: {response.get('error', 'Unknown error') if response else 'No response'}")
|
|
534
534
|
|
|
535
535
|
return self._handle_response(response)
|
|
536
536
|
except Exception as e:
|
|
@@ -551,10 +551,10 @@ class FacialRecognitionClient:
|
|
|
551
551
|
base_url=self.server_base_url
|
|
552
552
|
)
|
|
553
553
|
|
|
554
|
-
if response.get('success', False):
|
|
554
|
+
if response and response.get('success', False):
|
|
555
555
|
self.logger.info(f"API RESPONSE: Service is healthy")
|
|
556
556
|
else:
|
|
557
|
-
self.logger.warning(f"Health check failed: {response.get('error', 'Unknown error')}")
|
|
557
|
+
self.logger.warning(f"Health check failed: {response.get('error', 'Unknown error') if response else 'No response'}")
|
|
558
558
|
|
|
559
559
|
return self._handle_response(response)
|
|
560
560
|
except Exception as e:
|
|
@@ -564,6 +564,11 @@ class FacialRecognitionClient:
|
|
|
564
564
|
def _handle_response(self, response: Dict[str, Any]) -> Dict[str, Any]:
|
|
565
565
|
"""Handle RPC response and errors"""
|
|
566
566
|
try:
|
|
567
|
+
# Handle None response
|
|
568
|
+
if response is None:
|
|
569
|
+
self.logger.error("RPC Error: Received None response from API")
|
|
570
|
+
return {"success": False, "error": "No response received from API"}
|
|
571
|
+
|
|
567
572
|
if response.get("success", True):
|
|
568
573
|
return response
|
|
569
574
|
else:
|
|
@@ -84,19 +84,20 @@ class PeopleActivityLogging:
|
|
|
84
84
|
self.activity_queue.task_done()
|
|
85
85
|
except queue.Empty:
|
|
86
86
|
# Continue loop to check for empty detections
|
|
87
|
+
await asyncio.sleep(0.01)
|
|
87
88
|
continue
|
|
88
89
|
|
|
89
90
|
except Exception as e:
|
|
90
91
|
self.logger.error(f"Error processing activity queue: {e}", exc_info=True)
|
|
91
92
|
await asyncio.sleep(1.0)
|
|
92
93
|
|
|
93
|
-
|
|
94
|
+
def enqueue_detection(
|
|
94
95
|
self,
|
|
95
96
|
detection: Dict,
|
|
96
97
|
current_frame: Optional[np.ndarray] = None,
|
|
97
98
|
location: str = "",
|
|
98
99
|
):
|
|
99
|
-
"""Enqueue a detection for background processing"""
|
|
100
|
+
"""Enqueue a detection for background processing (non-blocking)"""
|
|
100
101
|
try:
|
|
101
102
|
activity_data = {
|
|
102
103
|
"detection_type": detection["recognition_status"], # known, unknown
|
|
@@ -135,8 +136,9 @@ class PeopleActivityLogging:
|
|
|
135
136
|
self.last_detection_time = time.time()
|
|
136
137
|
self.empty_detection_logged = False
|
|
137
138
|
|
|
138
|
-
# Use thread-safe
|
|
139
|
-
|
|
139
|
+
# Use thread-safe put_nowait to avoid any potential blocking
|
|
140
|
+
# queue.Queue.put() is already non-blocking with no timeout, but being explicit
|
|
141
|
+
self.activity_queue.put_nowait(activity_data)
|
|
140
142
|
except Exception as e:
|
|
141
143
|
self.logger.error(f"Error enqueueing detection: {e}", exc_info=True)
|
|
142
144
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import easyocr
|
|
2
2
|
import numpy as np
|
|
3
3
|
import torch
|
|
4
|
+
from matrice_common.utils import log_errors
|
|
4
5
|
|
|
5
6
|
class EasyOCRExtractor:
|
|
6
7
|
def __init__(self, lang=['en', 'hi', 'ar'], gpu=False, model_storage_directory=None,
|
|
@@ -30,7 +31,8 @@ class EasyOCRExtractor:
|
|
|
30
31
|
self.recognizer = recognizer
|
|
31
32
|
self.verbose = verbose
|
|
32
33
|
self.reader = None
|
|
33
|
-
|
|
34
|
+
|
|
35
|
+
@log_errors(default_return=None, raise_exception=True, service_name="py_analytics", log_error=True)
|
|
34
36
|
def setup(self):
|
|
35
37
|
"""
|
|
36
38
|
Initializes the EasyOCR reader if not already initialized.
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
import cv2
|
|
4
|
+
import json
|
|
5
|
+
import importlib
|
|
6
|
+
import argparse
|
|
7
|
+
from ultralytics import YOLO
|
|
8
|
+
from src.matrice_analytics.post_processing.core.base import ProcessingContext
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class UseCaseTestProcessor:
|
|
12
|
+
"""
|
|
13
|
+
A flexible YOLO-based video processor for testing different post-processing use cases.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def __init__(self, file_name, config_name, usecase_name, model_path, video_path, post_process=None, max_frames=None):
|
|
17
|
+
self.file_name = file_name
|
|
18
|
+
self.config_name = config_name
|
|
19
|
+
self.usecase_name = usecase_name
|
|
20
|
+
self.model_path = model_path
|
|
21
|
+
self.video_path = video_path
|
|
22
|
+
self.post_process = post_process
|
|
23
|
+
self.max_frames = max_frames
|
|
24
|
+
self.json_dir = "jsons"
|
|
25
|
+
|
|
26
|
+
self._setup_environment()
|
|
27
|
+
self.ConfigClass, self.UsecaseClass = self._load_usecase()
|
|
28
|
+
self.config = self._initialize_config()
|
|
29
|
+
self.processor = self.UsecaseClass()
|
|
30
|
+
self.model = YOLO(self.model_path)
|
|
31
|
+
os.makedirs(self.json_dir, exist_ok=True)
|
|
32
|
+
|
|
33
|
+
def _setup_environment(self):
|
|
34
|
+
"""Ensure project root is added to sys.path."""
|
|
35
|
+
project_root = os.path.abspath("/content/py_analytics")
|
|
36
|
+
if project_root not in sys.path:
|
|
37
|
+
sys.path.append(project_root)
|
|
38
|
+
|
|
39
|
+
def _load_usecase(self):
|
|
40
|
+
"""Dynamically import config and usecase classes."""
|
|
41
|
+
module_path = f"src.matrice_analytics.post_processing.usecases.{self.file_name}"
|
|
42
|
+
module = importlib.import_module(module_path)
|
|
43
|
+
return getattr(module, self.config_name), getattr(module, self.usecase_name)
|
|
44
|
+
|
|
45
|
+
def _initialize_config(self):
|
|
46
|
+
"""Initialize config object, applying overrides if provided."""
|
|
47
|
+
if self.post_process:
|
|
48
|
+
return self.ConfigClass(**self.post_process)
|
|
49
|
+
return self.ConfigClass()
|
|
50
|
+
|
|
51
|
+
def _serialize_result(self, result):
|
|
52
|
+
"""Convert result object into JSON-serializable dict."""
|
|
53
|
+
def to_serializable(obj):
|
|
54
|
+
if hasattr(obj, "to_dict"):
|
|
55
|
+
return obj.to_dict()
|
|
56
|
+
if hasattr(obj, "__dict__"):
|
|
57
|
+
return obj.__dict__
|
|
58
|
+
return str(obj)
|
|
59
|
+
return json.loads(json.dumps(result, default=to_serializable))
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def process_video(self):
|
|
63
|
+
"""Run YOLO inference on video and post-process frame by frame."""
|
|
64
|
+
cap = cv2.VideoCapture(self.video_path)
|
|
65
|
+
if not cap.isOpened():
|
|
66
|
+
raise ValueError(f"Failed to open video at {self.video_path}")
|
|
67
|
+
|
|
68
|
+
frame_idx = 0
|
|
69
|
+
stream_info = {
|
|
70
|
+
'input_settings': {
|
|
71
|
+
'start_frame': 0,
|
|
72
|
+
'original_fps': cap.get(cv2.CAP_PROP_FPS),
|
|
73
|
+
'camera_info': {'id': 'cam1', 'name': 'Test Camera'}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
print(f"\nStarting video processing: {self.video_path}")
|
|
78
|
+
print(f"Model: {self.model_path}")
|
|
79
|
+
print(f"Output directory: {self.json_dir}\n")
|
|
80
|
+
|
|
81
|
+
while cap.isOpened():
|
|
82
|
+
ret, frame = cap.read()
|
|
83
|
+
if not ret:
|
|
84
|
+
break
|
|
85
|
+
|
|
86
|
+
frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
|
|
87
|
+
results = self.model(frame_rgb)
|
|
88
|
+
|
|
89
|
+
detections = []
|
|
90
|
+
for xyxy, conf, cls in zip(results[0].boxes.xyxy, results[0].boxes.conf, results[0].boxes.cls):
|
|
91
|
+
x1, y1, x2, y2 = xyxy.tolist()
|
|
92
|
+
detections.append({
|
|
93
|
+
'category_id': int(cls),
|
|
94
|
+
'confidence': conf.item(),
|
|
95
|
+
'bounding_box': {
|
|
96
|
+
'xmin': int(x1),
|
|
97
|
+
'ymin': int(y1),
|
|
98
|
+
'xmax': int(x2),
|
|
99
|
+
'ymax': int(y2)
|
|
100
|
+
}
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
success, encoded_image = cv2.imencode(".jpg", frame)
|
|
104
|
+
input_bytes = encoded_image.tobytes() if success else None
|
|
105
|
+
|
|
106
|
+
try:
|
|
107
|
+
result = self.processor.process(
|
|
108
|
+
detections, self.config, input_bytes, ProcessingContext(), stream_info
|
|
109
|
+
)
|
|
110
|
+
except TypeError:
|
|
111
|
+
result = self.processor.process(
|
|
112
|
+
detections, self.config, ProcessingContext(), stream_info
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
json_path = os.path.join(self.json_dir, f"frame_{frame_idx:04d}.json")
|
|
116
|
+
with open(json_path, "w") as f:
|
|
117
|
+
json.dump(self._serialize_result(result), f, indent=2)
|
|
118
|
+
|
|
119
|
+
print(f"Frame {frame_idx} processed — detections: {len(detections)} — saved: {json_path}")
|
|
120
|
+
|
|
121
|
+
frame_idx += 1
|
|
122
|
+
stream_info['input_settings']['start_frame'] += 1
|
|
123
|
+
|
|
124
|
+
if self.max_frames and frame_idx >= self.max_frames:
|
|
125
|
+
print(f"\nMax frame limit ({self.max_frames}) reached.")
|
|
126
|
+
break
|
|
127
|
+
|
|
128
|
+
cap.release()
|
|
129
|
+
print(f"\nProcessing complete. JSON outputs saved in: {self.json_dir}")
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def main():
|
|
133
|
+
parser = argparse.ArgumentParser(description="YOLO Use Case Test Processor")
|
|
134
|
+
|
|
135
|
+
parser.add_argument("--file_name", type=str, required=True,
|
|
136
|
+
help="Usecase file name under src/matrice_analytics/post_processing/usecases/")
|
|
137
|
+
parser.add_argument("--config_name", type=str, required=True,
|
|
138
|
+
help="Config class name (e.g., PeopleCountingConfig)")
|
|
139
|
+
parser.add_argument("--usecase_name", type=str, required=True,
|
|
140
|
+
help="Use case class name (e.g., PeopleCountingUseCase)")
|
|
141
|
+
parser.add_argument("--model_path", type=str, required=True,
|
|
142
|
+
help="Path to YOLO model file (.pt)")
|
|
143
|
+
parser.add_argument("--video_path", type=str, required=True,
|
|
144
|
+
help="Path to input video")
|
|
145
|
+
parser.add_argument("--post_process", type=json.loads, default=None,
|
|
146
|
+
help="JSON string for config overrides, e.g. '{\"min_confidence\": 0.5}'")
|
|
147
|
+
parser.add_argument("--max_frames", type=int, default=None,
|
|
148
|
+
help="Limit number of frames processed")
|
|
149
|
+
|
|
150
|
+
args = parser.parse_args()
|
|
151
|
+
|
|
152
|
+
processor = UseCaseTestProcessor(
|
|
153
|
+
file_name=args.file_name,
|
|
154
|
+
config_name=args.config_name,
|
|
155
|
+
usecase_name=args.usecase_name,
|
|
156
|
+
model_path=args.model_path,
|
|
157
|
+
video_path=args.video_path,
|
|
158
|
+
post_process=args.post_process,
|
|
159
|
+
max_frames=args.max_frames
|
|
160
|
+
)
|
|
161
|
+
processor.process_video()
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
if __name__ == "__main__":
|
|
165
|
+
main()
|
|
@@ -11,20 +11,21 @@ subprocess.run(
|
|
|
11
11
|
cmd,
|
|
12
12
|
stdout=log_file,
|
|
13
13
|
stderr=subprocess.STDOUT,
|
|
14
|
-
preexec_fn=os.setpgrp
|
|
14
|
+
# preexec_fn=os.setpgrp
|
|
15
15
|
)
|
|
16
16
|
cmd = ["pip", "install", "httpx", "aiohttp", "filterpy"]
|
|
17
17
|
subprocess.run(
|
|
18
18
|
cmd,
|
|
19
19
|
stdout=log_file,
|
|
20
20
|
stderr=subprocess.STDOUT,
|
|
21
|
-
preexec_fn=os.setpgrp
|
|
21
|
+
# preexec_fn=os.setpgrp
|
|
22
22
|
)
|
|
23
23
|
log_file.close()
|
|
24
24
|
|
|
25
25
|
import numpy as np
|
|
26
26
|
from typing import List, Dict, Tuple, Optional
|
|
27
27
|
from dataclasses import dataclass, field
|
|
28
|
+
from pathlib import Path
|
|
28
29
|
import cv2
|
|
29
30
|
import io
|
|
30
31
|
import threading
|
|
@@ -371,7 +372,7 @@ class ClipProcessor:
|
|
|
371
372
|
cmd,
|
|
372
373
|
stdout=log_file,
|
|
373
374
|
stderr=subprocess.STDOUT,
|
|
374
|
-
preexec_fn=os.setpgrp
|
|
375
|
+
# preexec_fn=os.setpgrp
|
|
375
376
|
)
|
|
376
377
|
|
|
377
378
|
# Determine and enforce providers (prefer CUDA only)
|
|
@@ -12,7 +12,7 @@ matrice_analytics/boundary_drawing_internal/usage/boundary_drawer_launcher.py,sh
|
|
|
12
12
|
matrice_analytics/boundary_drawing_internal/usage/simple_boundary_launcher.py,sha256=jHPriRLorLuiC8km0MFNS96w121tKxd7t5GQl7I5kKE,3494
|
|
13
13
|
matrice_analytics/post_processing/README.md,sha256=bDszazvqV5xbGhMM6hDaMctIyk5gox9bADo2IZZ9Goo,13368
|
|
14
14
|
matrice_analytics/post_processing/__init__.py,sha256=dxGBUQaRCGndQmXpYAWqUhDeUZAcxU-_6HFnm3GRDRA,29417
|
|
15
|
-
matrice_analytics/post_processing/config.py,sha256=
|
|
15
|
+
matrice_analytics/post_processing/config.py,sha256=XQLl1bW3X0vqb--b_OAQcvSP0JKPGEcLUdHE8NRnqVs,6770
|
|
16
16
|
matrice_analytics/post_processing/post_processor.py,sha256=F838_vc7p9tjcp-vTMgTbpHqQcLX94xhL9HM06Wvpo8,44384
|
|
17
17
|
matrice_analytics/post_processing/advanced_tracker/README.md,sha256=RM8dynVoUWKn_hTbw9c6jHAbnQj-8hEAXnmuRZr2w1M,22485
|
|
18
18
|
matrice_analytics/post_processing/advanced_tracker/__init__.py,sha256=tAPFzI_Yep5TLX60FDwKqBqppc-EbxSr0wNsQ9DGI1o,423
|
|
@@ -29,11 +29,11 @@ matrice_analytics/post_processing/core/config_utils.py,sha256=QuAS-_JKSoNOtfUWgr
|
|
|
29
29
|
matrice_analytics/post_processing/face_reg/__init__.py,sha256=yntaiGlW9vdjBpPZQXNuovALihJPzRlFyUE88l3MhBA,1364
|
|
30
30
|
matrice_analytics/post_processing/face_reg/compare_similarity.py,sha256=NlFc8b2a74k0PqSFAbuM_fUbA1BT3pr3VUgvSqRpJzQ,23396
|
|
31
31
|
matrice_analytics/post_processing/face_reg/embedding_manager.py,sha256=qbh0df3-YbE0qvFDQvjpCg-JrsCZRJ5capjQ2LPOj1k,35619
|
|
32
|
-
matrice_analytics/post_processing/face_reg/face_recognition.py,sha256=
|
|
33
|
-
matrice_analytics/post_processing/face_reg/face_recognition_client.py,sha256=
|
|
34
|
-
matrice_analytics/post_processing/face_reg/people_activity_logging.py,sha256=
|
|
32
|
+
matrice_analytics/post_processing/face_reg/face_recognition.py,sha256=iKblqR88bcvP6Vu-8_FEuBLgGSZs5nfJVSTY-U4Ws9c,92786
|
|
33
|
+
matrice_analytics/post_processing/face_reg/face_recognition_client.py,sha256=4H0I1fLmv6BaqWQXRWOHN12GIV5IsHk5xkDOUI-JGQ8,28242
|
|
34
|
+
matrice_analytics/post_processing/face_reg/people_activity_logging.py,sha256=xV5BliZf4As_sg4h-fqav5-KZeGi96fEfv8vLSliEk8,13835
|
|
35
35
|
matrice_analytics/post_processing/ocr/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
36
|
-
matrice_analytics/post_processing/ocr/easyocr_extractor.py,sha256=
|
|
36
|
+
matrice_analytics/post_processing/ocr/easyocr_extractor.py,sha256=RMrRoGb2gMcJEGouQn8U9cCgCLXPT7qRa8liI4LNxFM,11555
|
|
37
37
|
matrice_analytics/post_processing/ocr/postprocessing.py,sha256=RILArp8I9WRH7bALVZ9wPGc-aR7YMdqV1ndOOIcOnGQ,12309
|
|
38
38
|
matrice_analytics/post_processing/ocr/preprocessing.py,sha256=LZolyUw4syMU6Q0V6SQzvkITVSmlkvwu0p9cUNhkbgA,1790
|
|
39
39
|
matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/__init__.py,sha256=OHd4VO0dI8daHojE5900DvreiDT6SGOSZF6VvxU0Tx4,184
|
|
@@ -80,6 +80,7 @@ matrice_analytics/post_processing/test_cases/test_customer_service.py,sha256=gfJ
|
|
|
80
80
|
matrice_analytics/post_processing/test_cases/test_data_generators.py,sha256=RYeY1pCQdPSAsnevdMkqudBOCIDBrWS2-cZK-hcOSlU,20214
|
|
81
81
|
matrice_analytics/post_processing/test_cases/test_people_counting.py,sha256=1QINfIvtQ5idIMHaojgilRcSiCCVqZPd1fyGOM08HiE,19194
|
|
82
82
|
matrice_analytics/post_processing/test_cases/test_processor.py,sha256=Mi0dRkpszChMS1SOPVBHj2bgkRt93Xxl94mvpQ-5yws,19799
|
|
83
|
+
matrice_analytics/post_processing/test_cases/test_usecases.py,sha256=e09c9JaOhtiwO5_TXDV5V_dPsc_jJBG36egu-WM02mE,6331
|
|
83
84
|
matrice_analytics/post_processing/test_cases/test_utilities.py,sha256=zUtBqwELjovkhQfhn1vM-y7aH04z9sFvt6LIpXXBFSE,13415
|
|
84
85
|
matrice_analytics/post_processing/test_cases/test_utils.py,sha256=lgDX0vILylA6m8sG3_3kxAJ7TiDo8xkprJNfBrLoID4,29371
|
|
85
86
|
matrice_analytics/post_processing/usecases/Histopathological_Cancer_Detection_img.py,sha256=bHDXxxG3QgWMFZbDuBaJWpkIvxTXsFMTqCPBCFm3SDs,30247
|
|
@@ -115,7 +116,7 @@ matrice_analytics/post_processing/usecases/face_emotion.py,sha256=eRfqBdryB0uNoO
|
|
|
115
116
|
matrice_analytics/post_processing/usecases/face_recognition.py,sha256=T5xAuv6b9OrkmTmoXgZs4LZ5XUsbvp9xCpeLBwdu7eI,40231
|
|
116
117
|
matrice_analytics/post_processing/usecases/fashion_detection.py,sha256=f9gpzMDhIW-gyn46k9jgf8nY7YeoqAnTxGOzksabFbE,40457
|
|
117
118
|
matrice_analytics/post_processing/usecases/field_mapping.py,sha256=JDwYX8pd2W-waDvBh98Y_o_uchJu7wEYbFxOliA4Iq4,39822
|
|
118
|
-
matrice_analytics/post_processing/usecases/fire_detection.py,sha256=
|
|
119
|
+
matrice_analytics/post_processing/usecases/fire_detection.py,sha256=o57jhIrsVBRkpD4nzaUHyuBRA4YZb3yNvQz0Ri_xiVg,54613
|
|
119
120
|
matrice_analytics/post_processing/usecases/flare_analysis.py,sha256=3nf4fUeUwlP_UII0h5fQkUGPXbr32ZnJjaM-dukNSP8,42680
|
|
120
121
|
matrice_analytics/post_processing/usecases/flower_segmentation.py,sha256=4I7qMx9Ztxg_hy9KTVX-3qBhAN-QwDt_Yigf9fFjLus,52017
|
|
121
122
|
matrice_analytics/post_processing/usecases/gas_leak_detection.py,sha256=KL2ft7fXvjTas-65-QgcJm3W8KBsrwF44qibSXjfaLc,40557
|
|
@@ -166,9 +167,9 @@ matrice_analytics/post_processing/usecases/weld_defect_detection.py,sha256=b0dAJ
|
|
|
166
167
|
matrice_analytics/post_processing/usecases/wildlife_monitoring.py,sha256=TMVHJ5GLezmqG7DywmqbLggqNXgpsb63MD7IR6kvDkk,43446
|
|
167
168
|
matrice_analytics/post_processing/usecases/windmill_maintenance.py,sha256=G1eqo3Z-HYmGJ6oeZYrpZwhpvqQ9Lc_T-6S7BLBXHeA,40498
|
|
168
169
|
matrice_analytics/post_processing/usecases/wound_segmentation.py,sha256=ehNX6VuWMB3xAnCySO3ra3Tf_5FUNg5LCSdq_91h374,38342
|
|
169
|
-
matrice_analytics/post_processing/usecases/color/clip.py,sha256=
|
|
170
|
+
matrice_analytics/post_processing/usecases/color/clip.py,sha256=EN8z3XIwkkmuhGcSjTWV0Os7tSVwZOL-svsxkzT21e4,27586
|
|
170
171
|
matrice_analytics/post_processing/usecases/color/color_map_utils.py,sha256=SP-AEVcjLmL8rxblu-ixqUJC2fqlcr7ab4hWo4Fcr_k,2677
|
|
171
|
-
matrice_analytics/post_processing/usecases/color/color_mapper.py,sha256=
|
|
172
|
+
matrice_analytics/post_processing/usecases/color/color_mapper.py,sha256=aUbs5bGxxKEg0CFZUIKE1zbABLr02ZB81HRUb_wdPfk,17458
|
|
172
173
|
matrice_analytics/post_processing/usecases/color/clip_processor/merges.txt,sha256=n9aR98gDkhDg_O0VhlRmxlgg0JtjmIsBdL_iXeKZBRo,524619
|
|
173
174
|
matrice_analytics/post_processing/usecases/color/clip_processor/preprocessor_config.json,sha256=j7VHQDZW6QGbCYLSMQsEW-85udKoEaDJh1blLUjiXbY,504
|
|
174
175
|
matrice_analytics/post_processing/usecases/color/clip_processor/special_tokens_map.json,sha256=LNs7gzGmDJL8HlWhPp_WH9IpPFpRJ1_czNYreABSUw4,588
|
|
@@ -188,8 +189,8 @@ matrice_analytics/post_processing/utils/format_utils.py,sha256=UTF7A5h9j0_S12xH9
|
|
|
188
189
|
matrice_analytics/post_processing/utils/geometry_utils.py,sha256=BWfdM6RsdJTTLR1GqkWfdwpjMEjTCJyuBxA4zVGKdfk,9623
|
|
189
190
|
matrice_analytics/post_processing/utils/smoothing_utils.py,sha256=78U-yucAcjUiZ0NIAc9NOUSIT0PWP1cqyIPA_Fdrjp0,14699
|
|
190
191
|
matrice_analytics/post_processing/utils/tracking_utils.py,sha256=rWxuotnJ3VLMHIBOud2KLcu4yZfDp7hVPWUtNAq_2xw,8288
|
|
191
|
-
matrice_analytics-0.1.
|
|
192
|
-
matrice_analytics-0.1.
|
|
193
|
-
matrice_analytics-0.1.
|
|
194
|
-
matrice_analytics-0.1.
|
|
195
|
-
matrice_analytics-0.1.
|
|
192
|
+
matrice_analytics-0.1.45.dist-info/licenses/LICENSE.txt,sha256=_uQUZpgO0mRYL5-fPoEvLSbNnLPv6OmbeEDCHXhK6Qc,1066
|
|
193
|
+
matrice_analytics-0.1.45.dist-info/METADATA,sha256=cejPbTOU7QL_WuBVD_azQBNbihZX1FM-aLK9QrM7x58,14378
|
|
194
|
+
matrice_analytics-0.1.45.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
195
|
+
matrice_analytics-0.1.45.dist-info/top_level.txt,sha256=STAPEU-e-rWTerXaspdi76T_eVRSrEfFpURSP7_Dt8E,18
|
|
196
|
+
matrice_analytics-0.1.45.dist-info/RECORD,,
|
|
File without changes
|
{matrice_analytics-0.1.44.dist-info → matrice_analytics-0.1.45.dist-info}/licenses/LICENSE.txt
RENAMED
|
File without changes
|
|
File without changes
|