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

@@ -49,7 +49,10 @@ class EmbeddingConfig:
49
49
 
50
50
  # Background embedding refresh settings
51
51
  enable_background_refresh: bool = True
52
- background_refresh_interval: int = 600 # Refresh embeddings every 10 minutes (600 seconds)
52
+ # Refresh embeddings every 12 hours by default
53
+ background_refresh_interval: int = 43200
54
+ # TTL for cached staff embeddings (controls on-demand refresh checks)
55
+ staff_embeddings_cache_ttl: int = 43200
53
56
 
54
57
 
55
58
  class EmbeddingManager:
@@ -67,7 +70,8 @@ class EmbeddingManager:
67
70
  # Staff embeddings cache for local similarity search
68
71
  self.staff_embeddings: List[StaffEmbedding] = []
69
72
  self.staff_embeddings_last_update = 0
70
- self.staff_embeddings_cache_ttl = 3600 # 1 hour
73
+ # Use configured TTL (default: 12 hours)
74
+ self.staff_embeddings_cache_ttl = int(self.config.staff_embeddings_cache_ttl)
71
75
 
72
76
  # Numpy arrays for fast similarity computation
73
77
  self.embeddings_matrix = None
@@ -1,18 +1,27 @@
1
1
  """
2
- Face Recognition with Track ID Cache Optimization
2
+ Face Recognition with Local Embedding Search Optimization
3
3
 
4
- This module includes an optimization that caches face recognition results by track ID
5
- to reduce redundant API calls. When a face detection is processed:
4
+ This module uses local similarity search for face recognition to achieve high performance:
6
5
 
7
- 1. It checks the cache for existing results using track_id
8
- 2. If track_id found in cache, uses cached result instead of API call
9
- 3. If track_id not found, makes API call and caches the result
10
- 4. Cache includes automatic cleanup with TTL and size limits
6
+ Performance Optimization Strategy:
7
+ 1. EmbeddingManager loads all staff embeddings from API at startup (~1 API call)
8
+ 2. Embeddings are cached in memory as a normalized numpy matrix
9
+ 3. Face recognition uses fast local cosine similarity search (~1-5ms per face)
10
+ 4. API calls are avoided during normal operation (2000x speedup vs API calls)
11
+ 5. Background refresh updates embeddings periodically (configurable TTL)
12
+
13
+ Processing Flow:
14
+ 1. TemporalIdentityManager receives face embedding from detection
15
+ 2. Uses EmbeddingManager._find_best_local_match() for fast local similarity search
16
+ 3. Returns best match if similarity >= threshold, otherwise returns "Unknown"
17
+ 4. Only falls back to API if EmbeddingManager unavailable (rare)
11
18
 
12
19
  Configuration options:
13
- - enable_track_id_cache: Enable/disable the optimization
14
- - cache_max_size: Maximum number of cached track IDs (default: 1000)
20
+ - enable_track_id_cache: Enable/disable track-level caching
21
+ - cache_max_size: Maximum number of cached track IDs (default: 3000)
15
22
  - cache_ttl: Cache time-to-live in seconds (default: 3600)
23
+ - background_refresh_interval: Embedding refresh interval (default: 43200s = 12h)
24
+ - similarity_threshold: Minimum similarity for recognition (default: 0.45)
16
25
  """
17
26
  import subprocess
18
27
  import logging
@@ -81,13 +90,14 @@ class TemporalIdentityManager:
81
90
  """
82
91
  Maintains stable identity labels per tracker ID using temporal smoothing and embedding history.
83
92
 
84
- Adaptation for production: _compute_best_identity queries the face recognition API
85
- via search_similar_faces(embedding, threshold=0.01, limit=1) to obtain top-1 match and score.
93
+ Adaptation for production: _compute_best_identity uses EmbeddingManager for local similarity
94
+ search first (fast), then falls back to API only if needed (slow).
86
95
  """
87
96
 
88
97
  def __init__(
89
98
  self,
90
99
  face_client: FacialRecognitionClient,
100
+ embedding_manager = None,
91
101
  recognition_threshold: float = 0.35,
92
102
  history_size: int = 20,
93
103
  unknown_patience: int = 7,
@@ -96,6 +106,7 @@ class TemporalIdentityManager:
96
106
  ) -> None:
97
107
  self.logger = logging.getLogger(__name__)
98
108
  self.face_client = face_client
109
+ self.embedding_manager = embedding_manager
99
110
  self.threshold = float(recognition_threshold)
100
111
  self.history_size = int(history_size)
101
112
  self.unknown_patience = int(unknown_patience)
@@ -119,14 +130,70 @@ class TemporalIdentityManager:
119
130
 
120
131
  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
132
  """
122
- Query backend for top-1 match for the given embedding.
133
+ Find best identity match using local similarity search (fast) with optional API fallback.
123
134
  Returns (staff_id, person_name, score, employee_id, staff_details, detection_type).
124
- Robust to varying response shapes.
135
+
136
+ Performance optimization: Uses EmbeddingManager for local similarity search to avoid
137
+ slow API calls (~2000ms). Only falls back to API if local search is unavailable.
125
138
  """
126
139
  if not emb or not isinstance(emb, list):
127
140
  return None, "Unknown", 0.0, None, {}, "unknown"
141
+
142
+ st10 = time.time()
143
+
144
+ # PRIMARY PATH: Local similarity search using EmbeddingManager (FAST - ~1-5ms)
145
+ if self.embedding_manager:
146
+ try:
147
+ local_match = self.embedding_manager._find_best_local_match(emb)
148
+
149
+ if local_match:
150
+ staff_embedding, similarity_score = local_match
151
+
152
+ # Extract person name from staff details
153
+ person_name = "Unknown"
154
+ staff_details = staff_embedding.staff_details if isinstance(staff_embedding.staff_details, dict) else {}
155
+
156
+ if staff_details:
157
+ first_name = staff_details.get("firstName")
158
+ last_name = staff_details.get("lastName")
159
+ name = staff_details.get("name")
160
+ if name:
161
+ person_name = str(name)
162
+ elif first_name or last_name:
163
+ person_name = f"{first_name or ''} {last_name or ''}".strip() or "Unknown"
164
+
165
+ print("------------------FACE RECOG TEMPORAL IDENTITY MANAGER UPDATE - COMPUTE BEST IDENTITY (LOCAL)----------------------------")
166
+ print("LATENCY:",(time.time() - st10)*1000,"| Throughput fps:",(1.0 / (time.time() - st10)) if (time.time() - st10) > 0 else None)
167
+ print(f"LOCAL MATCH: staff_id={staff_embedding.staff_id}, similarity={similarity_score:.3f}")
168
+ print("------------------FACE RECOG TEMPORAL IDENTITY MANAGER UPDATE - COMPUTE BEST IDENTITY (LOCAL)----------------------------")
169
+
170
+ self.logger.info(f"Local embedding match - staff_id={staff_embedding.staff_id}, person_name={person_name}, score={similarity_score:.3f}")
171
+
172
+ return (
173
+ str(staff_embedding.staff_id),
174
+ person_name,
175
+ float(similarity_score),
176
+ str(staff_embedding.employee_id),
177
+ staff_details,
178
+ "known"
179
+ )
180
+ else:
181
+ # No local match found
182
+ self.logger.debug("No local match found - returning unknown")
183
+ print("------------------FACE RECOG TEMPORAL IDENTITY MANAGER UPDATE - COMPUTE BEST IDENTITY (LOCAL - NO MATCH)----------------------------")
184
+ print("LATENCY:",(time.time() - st10)*1000,"| Throughput fps:",(1.0 / (time.time() - st10)) if (time.time() - st10) > 0 else None)
185
+ print("------------------FACE RECOG TEMPORAL IDENTITY MANAGER UPDATE - COMPUTE BEST IDENTITY (LOCAL - NO MATCH)----------------------------")
186
+
187
+ return None, "Unknown", 0.0, None, {}, "unknown"
188
+
189
+ except Exception as e:
190
+ self.logger.warning(f"Local similarity search failed, falling back to API: {e}")
191
+ # Fall through to API call below
192
+
193
+ # FALLBACK PATH: API call (SLOW - ~2000ms) - only if embedding manager not available
194
+ # This path should rarely be used in production
128
195
  try:
129
- st10=time.time()
196
+ self.logger.warning("Using slow API fallback for identity search - consider checking embedding manager initialization")
130
197
  resp = await self.face_client.search_similar_faces(
131
198
  face_embedding=emb,
132
199
  threshold=0.01, # low threshold to always get top-1
@@ -135,9 +202,10 @@ class TemporalIdentityManager:
135
202
  location=location,
136
203
  timestamp=timestamp,
137
204
  )
138
- print("------------------FACE RECOG TEMPORAL IDENTITY MANAGER UPDATE - COMPUTE BEST IDENTITY_0G----------------------------")
205
+ print("------------------FACE RECOG TEMPORAL IDENTITY MANAGER UPDATE - COMPUTE BEST IDENTITY (API FALLBACK)----------------------------")
139
206
  print("LATENCY:",(time.time() - st10)*1000,"| Throughput fps:",(1.0 / (time.time() - st10)) if (time.time() - st10) > 0 else None)
140
- print("------------------FACE RECOG TEMPORAL IDENTITY MANAGER UPDATE - COMPUTE BEST IDENTITY_0G----------------------------")
207
+ print("WARNING: Using slow API fallback!")
208
+ print("------------------FACE RECOG TEMPORAL IDENTITY MANAGER UPDATE - COMPUTE BEST IDENTITY (API FALLBACK)----------------------------")
141
209
 
142
210
  except Exception as e:
143
211
  self.logger.error(f"API ERROR: Failed to search similar faces in _compute_best_identity: {e}", exc_info=True)
@@ -492,7 +560,7 @@ class FaceRecognitionEmbeddingUseCase(BaseProcessor):
492
560
  # Initialize People activity logging if enabled
493
561
  if init_config.enable_people_activity_logging:
494
562
  self.people_activity_logging = PeopleActivityLogging(self.face_client)
495
- self.people_activity_logging.start_background_processing()
563
+ # PeopleActivityLogging starts its background thread in __init__
496
564
  self.logger.info("People activity logging enabled and started")
497
565
 
498
566
  # Initialize EmbeddingManager
@@ -502,21 +570,31 @@ class FaceRecognitionEmbeddingUseCase(BaseProcessor):
502
570
  confidence_threshold=init_config.confidence_threshold,
503
571
  enable_track_id_cache=init_config.enable_track_id_cache,
504
572
  cache_max_size=init_config.cache_max_size,
505
- cache_ttl=3600
573
+ cache_ttl=3600,
574
+ background_refresh_interval=43200,
575
+ staff_embeddings_cache_ttl=43200,
506
576
  )
507
577
  self.embedding_manager = EmbeddingManager(init_config.embedding_config, self.face_client)
508
578
  self.logger.info("Embedding manager initialized")
509
579
 
510
- # Initialize TemporalIdentityManager
580
+ # Load staff embeddings immediately for fast startup (avoid race conditions)
581
+ embeddings_loaded = await self.embedding_manager._load_staff_embeddings()
582
+ if embeddings_loaded:
583
+ self.logger.info(f"Loaded {len(self.embedding_manager.staff_embeddings)} staff embeddings at initialization")
584
+ else:
585
+ self.logger.warning("Failed to load staff embeddings at initialization - will retry in background")
586
+
587
+ # Initialize TemporalIdentityManager with EmbeddingManager for fast local search
511
588
  self.temporal_identity_manager = TemporalIdentityManager(
512
589
  face_client=self.face_client,
590
+ embedding_manager=self.embedding_manager,
513
591
  recognition_threshold=float(init_config.similarity_threshold),
514
592
  history_size=20,
515
593
  unknown_patience=7,
516
594
  switch_patience=5,
517
595
  fallback_margin=0.05,
518
596
  )
519
- self.logger.info("Temporal identity manager initialized")
597
+ self.logger.info("Temporal identity manager initialized with embedding manager for local similarity search")
520
598
 
521
599
  self._initialized = True
522
600
  self.logger.info("Face recognition use case fully initialized")
@@ -591,12 +669,20 @@ class FaceRecognitionEmbeddingUseCase(BaseProcessor):
591
669
  )
592
670
  context = ProcessingContext()
593
671
 
594
- # Ensure use case is initialized (should be done in _get_use_case_instance, not lazy loaded)
672
+ # Lazy initialization on first process() call (similar to tracker initialization pattern)
595
673
  if not self._initialized:
596
- raise RuntimeError(
597
- "Face recognition use case not initialized. "
598
- "This should be initialized eagerly in PostProcessor._get_use_case_instance()"
599
- )
674
+ self.logger.info("Initializing face recognition use case on first process() call...")
675
+ try:
676
+ await self.initialize(config)
677
+ self.logger.info("Face recognition use case initialized successfully")
678
+ except Exception as e:
679
+ self.logger.error(f"Failed to initialize face recognition use case: {e}", exc_info=True)
680
+ return self.create_error_result(
681
+ f"Initialization failed: {e}",
682
+ usecase=self.name,
683
+ category=self.category,
684
+ context=context,
685
+ )
600
686
 
601
687
  # Ensure confidence threshold is set
602
688
  if not config.confidence_threshold:
@@ -901,7 +987,7 @@ class FaceRecognitionEmbeddingUseCase(BaseProcessor):
901
987
  ) -> List[Dict]:
902
988
  """Process face recognition for each detection with embeddings"""
903
989
 
904
- # Initialize face client only when needed and if credentials are available
990
+ # Expect client to be initialized in initialize(); fail fast if not
905
991
  st0=time.time()
906
992
  if not self.face_client:
907
993
  try:
@@ -568,7 +568,7 @@ class FacialRecognitionClient:
568
568
  if response:
569
569
  return response
570
570
  else:
571
- error_msg = response.get("error", "Unknown RPC error")
571
+ error_msg = response #.get("error", "Unknown RPC error")
572
572
  self.logger.error(f"RPC Error: {error_msg}", exc_info=True)
573
573
  return {"success": False, "error": error_msg}
574
574
  except Exception as e:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: matrice_analytics
3
- Version: 0.1.51
3
+ Version: 0.1.53
4
4
  Summary: Common server utilities for Matrice.ai services
5
5
  Author-email: "Matrice.ai" <dipendra@matrice.ai>
6
6
  License-Expression: MIT
@@ -28,9 +28,9 @@ matrice_analytics/post_processing/core/config.py,sha256=uyxWndO-DE9PeGD_h5K3TeB0
28
28
  matrice_analytics/post_processing/core/config_utils.py,sha256=QuAS-_JKSoNOtfUWgr7Alf_wsqODzN2rHlQu-cHRK0s,34311
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
- 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=zBE9qW6_aP4vDdMZvJn3IiJ51zjWczBRDW_fTxARyOg,98258
33
- matrice_analytics/post_processing/face_reg/face_recognition_client.py,sha256=aWuGC6dRGaf5nN2xhyDIZOeg59r3DFNajcmc3AOxIdI,27594
31
+ matrice_analytics/post_processing/face_reg/embedding_manager.py,sha256=hv4WIUQg-PiSZ_H_jo8bbU8hvsAdF7E9KxUhdYcqi7Y,35815
32
+ matrice_analytics/post_processing/face_reg/face_recognition.py,sha256=rmBwr725V849OfMmQfxGEvGkPacV9ztc547b-Gms3kY,103735
33
+ matrice_analytics/post_processing/face_reg/face_recognition_client.py,sha256=eF2NYju1uWKXhILndI1rh4_VhWrKSGidui2jjbPQXgM,27596
34
34
  matrice_analytics/post_processing/face_reg/people_activity_logging.py,sha256=vZbIvkK1h3h58ROeF0_ygF3lqr19O2h5222bN8XyIis,13675
35
35
  matrice_analytics/post_processing/ocr/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
36
36
  matrice_analytics/post_processing/ocr/easyocr_extractor.py,sha256=RMrRoGb2gMcJEGouQn8U9cCgCLXPT7qRa8liI4LNxFM,11555
@@ -189,8 +189,8 @@ matrice_analytics/post_processing/utils/format_utils.py,sha256=UTF7A5h9j0_S12xH9
189
189
  matrice_analytics/post_processing/utils/geometry_utils.py,sha256=BWfdM6RsdJTTLR1GqkWfdwpjMEjTCJyuBxA4zVGKdfk,9623
190
190
  matrice_analytics/post_processing/utils/smoothing_utils.py,sha256=78U-yucAcjUiZ0NIAc9NOUSIT0PWP1cqyIPA_Fdrjp0,14699
191
191
  matrice_analytics/post_processing/utils/tracking_utils.py,sha256=rWxuotnJ3VLMHIBOud2KLcu4yZfDp7hVPWUtNAq_2xw,8288
192
- matrice_analytics-0.1.51.dist-info/licenses/LICENSE.txt,sha256=_uQUZpgO0mRYL5-fPoEvLSbNnLPv6OmbeEDCHXhK6Qc,1066
193
- matrice_analytics-0.1.51.dist-info/METADATA,sha256=3R94oOATZxOUUWavAyVi_PrYjx7qmgHIDxQ_vx_kBuY,14378
194
- matrice_analytics-0.1.51.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
195
- matrice_analytics-0.1.51.dist-info/top_level.txt,sha256=STAPEU-e-rWTerXaspdi76T_eVRSrEfFpURSP7_Dt8E,18
196
- matrice_analytics-0.1.51.dist-info/RECORD,,
192
+ matrice_analytics-0.1.53.dist-info/licenses/LICENSE.txt,sha256=_uQUZpgO0mRYL5-fPoEvLSbNnLPv6OmbeEDCHXhK6Qc,1066
193
+ matrice_analytics-0.1.53.dist-info/METADATA,sha256=CkxvpyDD9QLg9z3vnu-T-PECVIdiqUh6Hoy_CMN0kCM,14378
194
+ matrice_analytics-0.1.53.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
195
+ matrice_analytics-0.1.53.dist-info/top_level.txt,sha256=STAPEU-e-rWTerXaspdi76T_eVRSrEfFpURSP7_Dt8E,18
196
+ matrice_analytics-0.1.53.dist-info/RECORD,,