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
|
@@ -93,8 +93,8 @@ def iou_distance(atracks: list, btracks: list) -> np.ndarray:
|
|
|
93
93
|
Compute cost based on Intersection over Union (IoU) between tracks.
|
|
94
94
|
|
|
95
95
|
Args:
|
|
96
|
-
atracks (List[STrack]
|
|
97
|
-
btracks (List[STrack]
|
|
96
|
+
atracks (List[STrack] or List[np.ndarray]): List of tracks 'a' or bounding boxes.
|
|
97
|
+
btracks (List[STrack] or List[np.ndarray]): List of tracks 'b' or bounding boxes.
|
|
98
98
|
|
|
99
99
|
Returns:
|
|
100
100
|
(np.ndarray): Cost matrix computed based on IoU with shape (len(atracks), len(btracks)).
|
|
@@ -139,7 +139,7 @@ def embedding_distance(tracks: list, detections: list, metric: str = "cosine") -
|
|
|
139
139
|
Compute distance between tracks and detections based on embeddings.
|
|
140
140
|
|
|
141
141
|
Args:
|
|
142
|
-
tracks (List[STrack]): List of tracks, where each track contains embedding features.
|
|
142
|
+
tracks (List[STrack] or List[np.ndarray]): List of tracks, where each track contains embedding features.
|
|
143
143
|
detections (List[BaseTrack]): List of detections, where each detection contains embedding features.
|
|
144
144
|
metric (str): Metric for distance computation. Supported metrics include 'cosine', 'euclidean', etc.
|
|
145
145
|
|
|
@@ -46,7 +46,7 @@ class STrack(BaseTrack):
|
|
|
46
46
|
idx (int): Index or identifier for the object.
|
|
47
47
|
frame_id (int): Current frame ID.
|
|
48
48
|
start_frame (int): Frame where the object was first detected.
|
|
49
|
-
angle (float
|
|
49
|
+
angle (float or None): Optional angle information for oriented bounding boxes.
|
|
50
50
|
"""
|
|
51
51
|
|
|
52
52
|
shared_kalman = KalmanFilterXYAH()
|
|
@@ -62,11 +62,11 @@ class FaceTracker:
|
|
|
62
62
|
self.tracks: Dict[str, Dict[str, object]] = {}
|
|
63
63
|
self.track_counter: int = 1
|
|
64
64
|
|
|
65
|
-
def _find_matching_track(self, new_embedding: List[float]) -> str
|
|
65
|
+
def _find_matching_track(self, new_embedding: List[float]) -> Optional[str]:
|
|
66
66
|
if not new_embedding:
|
|
67
67
|
return None
|
|
68
68
|
best_similarity: float = 0.0
|
|
69
|
-
best_track_id: str
|
|
69
|
+
best_track_id: Optional[str] = None
|
|
70
70
|
for track_id, data in self.tracks.items():
|
|
71
71
|
stored_embedding = data.get("embedding")
|
|
72
72
|
if stored_embedding:
|
|
@@ -76,7 +76,7 @@ class FaceTracker:
|
|
|
76
76
|
best_track_id = track_id
|
|
77
77
|
return best_track_id
|
|
78
78
|
|
|
79
|
-
def assign_track_id(self, embedding: List[float], frame_id: int
|
|
79
|
+
def assign_track_id(self, embedding: List[float], frame_id: Optional[int] = None) -> str:
|
|
80
80
|
match_id = self._find_matching_track(embedding)
|
|
81
81
|
if match_id is not None and match_id in self.tracks:
|
|
82
82
|
# Update last seen frame for the matched track
|
|
@@ -139,7 +139,7 @@ def compute_pairwise_similarities(embeddings: List[List[float]]) -> Dict[Tuple[i
|
|
|
139
139
|
return similarity_dict
|
|
140
140
|
|
|
141
141
|
|
|
142
|
-
def get_embeddings_from_folder(folder_path: str, max_images: int
|
|
142
|
+
def get_embeddings_from_folder(folder_path: str, max_images: Optional[int] = None) -> Tuple[List[List[float]], List[str]]:
|
|
143
143
|
image_paths = sorted([p for p in Path(folder_path).iterdir() if p.suffix.lower() in {'.jpg', '.jpeg', '.png'}])
|
|
144
144
|
if max_images is not None:
|
|
145
145
|
image_paths = image_paths[:max_images]
|
|
@@ -155,7 +155,7 @@ def get_embeddings_from_folder(folder_path: str, max_images: int | None = None)
|
|
|
155
155
|
return embeddings, img_names
|
|
156
156
|
|
|
157
157
|
|
|
158
|
-
def get_embeddings_per_person(identity_root: str, max_images_per_person: int
|
|
158
|
+
def get_embeddings_per_person(identity_root: str, max_images_per_person: Optional[int] = None) -> Dict[str, List[List[float]]]:
|
|
159
159
|
"""Build a mapping: person (subdirectory name) -> list of embeddings from all images inside it."""
|
|
160
160
|
root = Path(identity_root)
|
|
161
161
|
if not root.exists():
|
|
@@ -87,10 +87,11 @@ class EmbeddingManager:
|
|
|
87
87
|
return False
|
|
88
88
|
|
|
89
89
|
try:
|
|
90
|
+
self.logger.info("Loading staff embeddings from API...")
|
|
90
91
|
response = await self.face_client.get_all_staff_embeddings()
|
|
91
92
|
|
|
92
93
|
if not response.get("success", False):
|
|
93
|
-
self.logger.error(f"Failed to get staff embeddings: {response.get('error', 'Unknown error')}")
|
|
94
|
+
self.logger.error(f"Failed to get staff embeddings from API: {response.get('error', 'Unknown error')}")
|
|
94
95
|
return False
|
|
95
96
|
|
|
96
97
|
# The API response has the format: {"success": True, "data": [embedding_items]}
|
|
@@ -128,10 +129,11 @@ class EmbeddingManager:
|
|
|
128
129
|
|
|
129
130
|
self.embedding_metadata = self.staff_embeddings.copy()
|
|
130
131
|
self.staff_embeddings_last_update = time.time()
|
|
131
|
-
self.logger.info(f"
|
|
132
|
+
self.logger.info(f"Successfully loaded and cached {len(self.staff_embeddings)} staff embeddings")
|
|
133
|
+
self.logger.debug(f"Embeddings matrix shape: {self.embeddings_matrix.shape}")
|
|
132
134
|
return True
|
|
133
135
|
else:
|
|
134
|
-
self.logger.warning("No active staff embeddings found")
|
|
136
|
+
self.logger.warning("No active staff embeddings found in API response")
|
|
135
137
|
return False
|
|
136
138
|
|
|
137
139
|
except Exception as e:
|
|
@@ -440,6 +442,7 @@ class EmbeddingManager:
|
|
|
440
442
|
|
|
441
443
|
# Refresh staff embeddings if needed
|
|
442
444
|
if self._should_refresh_embeddings() or self.embeddings_matrix is None:
|
|
445
|
+
self.logger.debug("Staff embeddings cache expired or empty, refreshing...")
|
|
443
446
|
await self._load_staff_embeddings()
|
|
444
447
|
|
|
445
448
|
# Always perform similarity search first
|
|
@@ -448,7 +451,8 @@ class EmbeddingManager:
|
|
|
448
451
|
|
|
449
452
|
if local_match:
|
|
450
453
|
staff_embedding, similarity_score = local_match
|
|
451
|
-
self.logger.
|
|
454
|
+
self.logger.info(f"Local embedding match found - staff_id={staff_embedding.staff_id}, similarity={similarity_score:.3f}, employee_id={staff_embedding.employee_id}")
|
|
455
|
+
self.logger.debug(f"Match details: staff_details={staff_embedding.staff_details}")
|
|
452
456
|
|
|
453
457
|
current_search_result = SearchResult(
|
|
454
458
|
employee_id=staff_embedding.employee_id,
|
|
@@ -466,6 +470,8 @@ class EmbeddingManager:
|
|
|
466
470
|
employee_id = f"unknown_{int(time.time())}_{counter_value}"
|
|
467
471
|
staff_id = track_id if track_id else f"unknown_{counter_value}"
|
|
468
472
|
|
|
473
|
+
self.logger.info(f"No local match found - creating unknown face entry: employee_id={employee_id}, track_id={track_id}")
|
|
474
|
+
|
|
469
475
|
current_search_result = SearchResult(
|
|
470
476
|
employee_id=employee_id,
|
|
471
477
|
staff_id=staff_id,
|
|
@@ -482,13 +488,14 @@ class EmbeddingManager:
|
|
|
482
488
|
|
|
483
489
|
# If current result is unknown, always continue checking even if cached
|
|
484
490
|
if current_search_result.detection_type == "unknown":
|
|
485
|
-
self.logger.debug(f"Unknown face with track_id
|
|
491
|
+
self.logger.debug(f"Unknown face with track_id={track_id} - not caching, will re-check for potential identification")
|
|
486
492
|
# Still update cache if new result is better, but don't return cached result for unknowns
|
|
487
493
|
if cached_result and current_search_result.similarity_score > cached_result.similarity_score:
|
|
488
|
-
self._update_track_id_cache(track_id, current_search_result)
|
|
494
|
+
self._update_track_id_cache(track_id, current_search_result) # TODO: check if this is correct
|
|
495
|
+
self.logger.debug(f"Not updating cache for unknown face (track_id={track_id})")
|
|
489
496
|
elif not cached_result:
|
|
490
497
|
# Don't cache unknown results - let them be rechecked every time
|
|
491
|
-
self.logger.debug(f"Not caching unknown face result for track_id
|
|
498
|
+
self.logger.debug(f"Not caching unknown face result for track_id={track_id}")
|
|
492
499
|
return current_search_result
|
|
493
500
|
|
|
494
501
|
if cached_result:
|
|
@@ -14,6 +14,18 @@ Configuration options:
|
|
|
14
14
|
- cache_max_size: Maximum number of cached track IDs (default: 1000)
|
|
15
15
|
- cache_ttl: Cache time-to-live in seconds (default: 3600)
|
|
16
16
|
"""
|
|
17
|
+
import subprocess
|
|
18
|
+
import asyncio
|
|
19
|
+
import os
|
|
20
|
+
log_file = open("pip_jetson_btii.log", "w")
|
|
21
|
+
cmd = ["pip", "install", "httpx"]
|
|
22
|
+
subprocess.run(
|
|
23
|
+
cmd,
|
|
24
|
+
stdout=log_file,
|
|
25
|
+
stderr=subprocess.STDOUT,
|
|
26
|
+
preexec_fn=os.setpgrp
|
|
27
|
+
)
|
|
28
|
+
log_file.close()
|
|
17
29
|
|
|
18
30
|
from typing import Any, Dict, List, Optional, Tuple
|
|
19
31
|
import time
|
|
@@ -48,6 +60,8 @@ from .embedding_manager import EmbeddingManager, EmbeddingConfig
|
|
|
48
60
|
from collections import deque, defaultdict
|
|
49
61
|
|
|
50
62
|
|
|
63
|
+
|
|
64
|
+
|
|
51
65
|
def _normalize_embedding(vec: List[float]) -> List[float]:
|
|
52
66
|
"""Normalize an embedding vector to unit length (L2). Returns float32 list."""
|
|
53
67
|
arr = np.asarray(vec, dtype=np.float32)
|
|
@@ -118,14 +132,13 @@ class TemporalIdentityManager:
|
|
|
118
132
|
location=location,
|
|
119
133
|
timestamp=timestamp,
|
|
120
134
|
)
|
|
121
|
-
except Exception:
|
|
135
|
+
except Exception as e:
|
|
136
|
+
self.logger.error(f"API ERROR: Failed to search similar faces in _compute_best_identity: {e}", exc_info=True)
|
|
122
137
|
return None, "Unknown", 0.0, None, {}, "unknown"
|
|
123
138
|
|
|
124
139
|
try:
|
|
125
140
|
results: List[Any] = []
|
|
126
|
-
|
|
127
|
-
print(resp)
|
|
128
|
-
print('----------------------------------------------------------')
|
|
141
|
+
self.logger.debug('API Response received for identity search')
|
|
129
142
|
if isinstance(resp, dict):
|
|
130
143
|
if isinstance(resp.get("data"), list):
|
|
131
144
|
results = resp.get("data", [])
|
|
@@ -137,12 +150,11 @@ class TemporalIdentityManager:
|
|
|
137
150
|
results = resp
|
|
138
151
|
|
|
139
152
|
if not results:
|
|
153
|
+
self.logger.debug("No identity match found from API")
|
|
140
154
|
return None, "Unknown", 0.0, None, {}, "unknown"
|
|
141
155
|
|
|
142
156
|
item = results[0] if isinstance(results, list) else results
|
|
143
|
-
|
|
144
|
-
print(item)
|
|
145
|
-
print('----------------------------------------------------------')
|
|
157
|
+
self.logger.debug(f'Top-1 match from API: {item}')
|
|
146
158
|
# Be defensive with keys and types
|
|
147
159
|
staff_id = item.get("staffId") if isinstance(item, dict) else None
|
|
148
160
|
employee_id = str(item.get("_id")) if isinstance(item, dict) and item.get("_id") is not None else None
|
|
@@ -162,9 +174,12 @@ class TemporalIdentityManager:
|
|
|
162
174
|
person_name = f"{first_name or ''} {last_name or ''}".strip() or "UnknowNN" #TODO:ebugging change to normal once done
|
|
163
175
|
# If API says unknown or missing staff_id, treat as unknown
|
|
164
176
|
if not staff_id: #or detection_type == "unknown"
|
|
177
|
+
self.logger.debug(f"API returned unknown or missing staff_id - score={score}, employee_id={employee_id}")
|
|
165
178
|
return None, "Unknown", float(score), employee_id, staff_details if isinstance(staff_details, dict) else {}, "unknown"
|
|
179
|
+
self.logger.info(f"API identified face - staff_id={staff_id}, person_name={person_name}, score={score:.3f}")
|
|
166
180
|
return str(staff_id), person_name, float(score), employee_id, staff_details if isinstance(staff_details, dict) else {}, "known"
|
|
167
|
-
except Exception:
|
|
181
|
+
except Exception as e:
|
|
182
|
+
self.logger.error(f"Error parsing API response in _compute_best_identity: {e}", exc_info=True)
|
|
168
183
|
return None, "Unknown", 0.0, None, {}, "unknown"
|
|
169
184
|
|
|
170
185
|
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]:
|
|
@@ -172,9 +187,11 @@ class TemporalIdentityManager:
|
|
|
172
187
|
if not hist:
|
|
173
188
|
return None, "Unknown", 0.0, None, {}, "unknown"
|
|
174
189
|
try:
|
|
190
|
+
self.logger.debug(f"Computing identity from embedding history - history_size={len(hist)}")
|
|
175
191
|
proto = np.mean(np.asarray(list(hist), dtype=np.float32), axis=0)
|
|
176
192
|
proto_list = proto.tolist() if isinstance(proto, np.ndarray) else list(proto)
|
|
177
|
-
except Exception:
|
|
193
|
+
except Exception as e:
|
|
194
|
+
self.logger.error(f"Error computing prototype from history: {e}", exc_info=True)
|
|
178
195
|
proto_list = []
|
|
179
196
|
return await self._compute_best_identity(proto_list, location=location, timestamp=timestamp)
|
|
180
197
|
|
|
@@ -337,6 +354,7 @@ class FaceRecognitionEmbeddingConfig(BaseConfig):
|
|
|
337
354
|
|
|
338
355
|
facial_recognition_server_id: str = ""
|
|
339
356
|
session: Any = None # Matrice session for face recognition client
|
|
357
|
+
deployment_id: Optional[str] = None # deployment ID for update_deployment call
|
|
340
358
|
|
|
341
359
|
# Embedding configuration
|
|
342
360
|
embedding_config: Optional[Any] = None # Will be set to EmbeddingConfig instance
|
|
@@ -356,7 +374,7 @@ class FaceRecognitionEmbeddingUseCase(BaseProcessor):
|
|
|
356
374
|
# Human-friendly display names for categories
|
|
357
375
|
CATEGORY_DISPLAY = {"face": "face"}
|
|
358
376
|
|
|
359
|
-
def __init__(self):
|
|
377
|
+
def __init__(self, config: Optional[FaceRecognitionEmbeddingConfig] = None):
|
|
360
378
|
super().__init__("face_recognition")
|
|
361
379
|
self.category = "security"
|
|
362
380
|
|
|
@@ -413,11 +431,53 @@ class FaceRecognitionEmbeddingUseCase(BaseProcessor):
|
|
|
413
431
|
self._min_face_h: int = 30
|
|
414
432
|
|
|
415
433
|
self.start_timer = None
|
|
434
|
+
|
|
435
|
+
# Store config for async initialization
|
|
436
|
+
self._default_config = config
|
|
437
|
+
self._initialized = False
|
|
438
|
+
|
|
439
|
+
asyncio.run(self.initialize(config))
|
|
440
|
+
|
|
441
|
+
async def initialize(self, config: Optional[FaceRecognitionEmbeddingConfig] = None) -> None:
|
|
442
|
+
"""
|
|
443
|
+
Async initialization method to set up face client and deployment update.
|
|
444
|
+
Should be called after __init__ to perform async setup operations.
|
|
445
|
+
|
|
446
|
+
Args:
|
|
447
|
+
config: Optional config to use. If not provided, uses config from __init__.
|
|
448
|
+
"""
|
|
449
|
+
if self._initialized:
|
|
450
|
+
self.logger.debug("Use case already initialized, skipping")
|
|
451
|
+
return
|
|
452
|
+
|
|
453
|
+
# Use provided config or fall back to default config from __init__
|
|
454
|
+
init_config = config or self._default_config
|
|
455
|
+
|
|
456
|
+
if not init_config:
|
|
457
|
+
self.logger.warning("No config provided for initialization, will initialize on first process() call")
|
|
458
|
+
return
|
|
459
|
+
|
|
460
|
+
# Validate config type
|
|
461
|
+
if not isinstance(init_config, FaceRecognitionEmbeddingConfig):
|
|
462
|
+
self.logger.warning(f"Invalid config type for initialization: {type(init_config)}, skipping early initialization")
|
|
463
|
+
return
|
|
464
|
+
|
|
465
|
+
self.logger.info("Initializing face recognition use case with provided config")
|
|
466
|
+
|
|
467
|
+
# Initialize face client (includes deployment update)
|
|
468
|
+
try:
|
|
469
|
+
self.face_client = await self._get_facial_recognition_client(init_config)
|
|
470
|
+
self._initialized = True
|
|
471
|
+
self.logger.info("Face recognition use case initialized successfully")
|
|
472
|
+
|
|
473
|
+
except Exception as e:
|
|
474
|
+
self.logger.error(f"Error during use case initialization: {e}", exc_info=True)
|
|
475
|
+
# Don't mark as initialized so it can retry during process()
|
|
416
476
|
|
|
417
|
-
def _get_facial_recognition_client(
|
|
477
|
+
async def _get_facial_recognition_client(
|
|
418
478
|
self, config: FaceRecognitionEmbeddingConfig
|
|
419
479
|
) -> FacialRecognitionClient:
|
|
420
|
-
"""Get facial recognition client"""
|
|
480
|
+
"""Get facial recognition client and update deployment"""
|
|
421
481
|
# Initialize face recognition client if not already done
|
|
422
482
|
if self.face_client is None:
|
|
423
483
|
self.logger.info(
|
|
@@ -427,6 +487,20 @@ class FaceRecognitionEmbeddingUseCase(BaseProcessor):
|
|
|
427
487
|
server_id=config.facial_recognition_server_id, session=config.session
|
|
428
488
|
)
|
|
429
489
|
self.logger.info("Face recognition client initialized")
|
|
490
|
+
|
|
491
|
+
# Call update_deployment if deployment_id is provided
|
|
492
|
+
if config.deployment_id:
|
|
493
|
+
try:
|
|
494
|
+
self.logger.info(f"Updating deployment with ID: {config.deployment_id}")
|
|
495
|
+
response = await self.face_client.update_deployment(config.deployment_id)
|
|
496
|
+
if response.get('success', False):
|
|
497
|
+
self.logger.info(f"Successfully updated deployment {config.deployment_id}")
|
|
498
|
+
else:
|
|
499
|
+
self.logger.warning(f"Failed to update deployment: {response.get('error', 'Unknown error')}")
|
|
500
|
+
except Exception as e:
|
|
501
|
+
self.logger.error(f"Exception while updating deployment: {e}", exc_info=True)
|
|
502
|
+
else:
|
|
503
|
+
self.logger.debug("No deployment_id provided, skipping deployment update")
|
|
430
504
|
|
|
431
505
|
return self.face_client
|
|
432
506
|
|
|
@@ -454,22 +528,37 @@ class FaceRecognitionEmbeddingUseCase(BaseProcessor):
|
|
|
454
528
|
category=self.category,
|
|
455
529
|
context=context,
|
|
456
530
|
)
|
|
457
|
-
|
|
458
|
-
|
|
531
|
+
if context is None:
|
|
532
|
+
context = ProcessingContext()
|
|
533
|
+
|
|
534
|
+
# Defensive check: Ensure context is ProcessingContext object (production safety)
|
|
535
|
+
# This handles edge cases where parameter mismatch might pass a dict as context
|
|
536
|
+
if not isinstance(context, ProcessingContext):
|
|
537
|
+
self.logger.warning(
|
|
538
|
+
f"Context parameter is not ProcessingContext (got {type(context).__name__}, {context}). "
|
|
539
|
+
"Creating new ProcessingContext. This may indicate a parameter mismatch in the caller."
|
|
540
|
+
)
|
|
541
|
+
context = ProcessingContext()
|
|
459
542
|
|
|
543
|
+
# Ensure initialization is complete (handles lazy initialization if not done earlier)
|
|
544
|
+
if not self._initialized:
|
|
545
|
+
await self.initialize(config)
|
|
546
|
+
|
|
547
|
+
# Fallback: If initialization failed but we still need face_client, try to initialize it
|
|
460
548
|
if not self.face_client:
|
|
461
|
-
self.face_client = self._get_facial_recognition_client(config)
|
|
549
|
+
self.face_client = await self._get_facial_recognition_client(config)
|
|
462
550
|
|
|
551
|
+
# Ensure confidence threshold is set
|
|
463
552
|
if not config.confidence_threshold:
|
|
464
553
|
config.confidence_threshold = 0.35
|
|
465
554
|
|
|
466
|
-
#
|
|
555
|
+
# Ensure People activity logging is initialized if enabled
|
|
467
556
|
if config.enable_people_activity_logging and not self.people_activity_logging:
|
|
468
557
|
self.people_activity_logging = PeopleActivityLogging(self.face_client)
|
|
469
558
|
self.people_activity_logging.start_background_processing()
|
|
470
559
|
self.logger.info("People activity logging enabled and started")
|
|
471
560
|
|
|
472
|
-
#
|
|
561
|
+
# Ensure EmbeddingManager is initialized
|
|
473
562
|
if not self.embedding_manager:
|
|
474
563
|
# Create default embedding config if not provided
|
|
475
564
|
if not config.embedding_config:
|
|
@@ -482,7 +571,7 @@ class FaceRecognitionEmbeddingUseCase(BaseProcessor):
|
|
|
482
571
|
)
|
|
483
572
|
self.embedding_manager = EmbeddingManager(config.embedding_config, self.face_client)
|
|
484
573
|
|
|
485
|
-
#
|
|
574
|
+
# Ensure TemporalIdentityManager is initialized (top-1 via API with smoothing)
|
|
486
575
|
if not self.temporal_identity_manager:
|
|
487
576
|
self.temporal_identity_manager = TemporalIdentityManager(
|
|
488
577
|
face_client=self.face_client,
|
|
@@ -1285,23 +1374,23 @@ class FaceRecognitionEmbeddingUseCase(BaseProcessor):
|
|
|
1285
1374
|
detections = []
|
|
1286
1375
|
for detection in counting_summary.get("detections", []):
|
|
1287
1376
|
bbox = detection.get("bounding_box", {})
|
|
1288
|
-
category = detection.get("display_name", "")
|
|
1289
|
-
|
|
1377
|
+
category = detection.get("display_name", "")
|
|
1378
|
+
|
|
1290
1379
|
detection_obj = self.create_detection_object(category, bbox)
|
|
1291
1380
|
# Add face recognition specific fields
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1381
|
+
detection_obj.update(
|
|
1382
|
+
{
|
|
1383
|
+
"person_id": detection.get("person_id"),
|
|
1384
|
+
# Use display_name for front-end label suppression policy
|
|
1385
|
+
"person_name": detection.get("display_name", ""),
|
|
1386
|
+
# Explicit label field for UI overlays
|
|
1387
|
+
"label": detection.get("display_name", ""),
|
|
1388
|
+
"recognition_status": detection.get(
|
|
1389
|
+
"recognition_status", "unknown"
|
|
1390
|
+
),
|
|
1391
|
+
"enrolled": detection.get("enrolled", False),
|
|
1392
|
+
}
|
|
1393
|
+
)
|
|
1305
1394
|
detections.append(detection_obj)
|
|
1306
1395
|
|
|
1307
1396
|
# Build alert_settings array in expected format
|
|
@@ -1867,4 +1956,4 @@ class FaceRecognitionEmbeddingUseCase(BaseProcessor):
|
|
|
1867
1956
|
if hasattr(self, "people_activity_logging") and self.people_activity_logging:
|
|
1868
1957
|
self.people_activity_logging.stop_background_processing()
|
|
1869
1958
|
except:
|
|
1870
|
-
pass
|
|
1959
|
+
pass
|