matrice-analytics 0.1.60__py3-none-any.whl → 0.1.89__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.
Files changed (21) hide show
  1. matrice_analytics/post_processing/config.py +2 -2
  2. matrice_analytics/post_processing/core/base.py +1 -1
  3. matrice_analytics/post_processing/face_reg/embedding_manager.py +8 -8
  4. matrice_analytics/post_processing/face_reg/face_recognition.py +886 -201
  5. matrice_analytics/post_processing/face_reg/face_recognition_client.py +68 -2
  6. matrice_analytics/post_processing/usecases/advanced_customer_service.py +908 -498
  7. matrice_analytics/post_processing/usecases/color_detection.py +18 -18
  8. matrice_analytics/post_processing/usecases/customer_service.py +356 -9
  9. matrice_analytics/post_processing/usecases/fire_detection.py +149 -11
  10. matrice_analytics/post_processing/usecases/license_plate_monitoring.py +548 -40
  11. matrice_analytics/post_processing/usecases/people_counting.py +11 -11
  12. matrice_analytics/post_processing/usecases/vehicle_monitoring.py +34 -34
  13. matrice_analytics/post_processing/usecases/weapon_detection.py +98 -22
  14. matrice_analytics/post_processing/utils/alert_instance_utils.py +950 -0
  15. matrice_analytics/post_processing/utils/business_metrics_manager_utils.py +1245 -0
  16. matrice_analytics/post_processing/utils/incident_manager_utils.py +1657 -0
  17. {matrice_analytics-0.1.60.dist-info → matrice_analytics-0.1.89.dist-info}/METADATA +1 -1
  18. {matrice_analytics-0.1.60.dist-info → matrice_analytics-0.1.89.dist-info}/RECORD +21 -18
  19. {matrice_analytics-0.1.60.dist-info → matrice_analytics-0.1.89.dist-info}/WHEEL +0 -0
  20. {matrice_analytics-0.1.60.dist-info → matrice_analytics-0.1.89.dist-info}/licenses/LICENSE.txt +0 -0
  21. {matrice_analytics-0.1.60.dist-info → matrice_analytics-0.1.89.dist-info}/top_level.txt +0 -0
@@ -27,6 +27,8 @@ from ..utils import (
27
27
  BBoxSmoothingConfig,
28
28
  BBoxSmoothingTracker
29
29
  )
30
+ # Import incident manager for publishing incidents on level change
31
+ from ..utils.incident_manager_utils import INCIDENT_MANAGER, IncidentManagerFactory
30
32
 
31
33
 
32
34
  # ======================
@@ -75,6 +77,10 @@ class FireSmokeConfig(BaseConfig):
75
77
  smoothing_cooldown_frames: int = 10
76
78
  smoothing_confidence_range_factor: float = 0.2
77
79
  threshold_area: Optional[float] = 250200.0
80
+
81
+ # Session and server configuration for incident manager
82
+ session: Optional[Any] = None # Matrice session for Redis/Kafka initialization
83
+ server_id: Optional[str] = None # Server ID for localhost/cloud detection
78
84
 
79
85
  def __post_init__(self):
80
86
  if not (0.0 <= self.confidence_threshold <= 1.0):
@@ -112,6 +118,114 @@ class FireSmokeUseCase(BaseProcessor):
112
118
  self.return_id_counter = 1
113
119
  self.start_timer = None
114
120
  self._tracking_start_time = None
121
+
122
+ # Incident manager for publishing incidents on severity level change
123
+ self._incident_manager_factory: Optional[IncidentManagerFactory] = None
124
+ self._incident_manager: Optional[INCIDENT_MANAGER] = None
125
+ self._incident_manager_initialized: bool = False
126
+
127
+ def _initialize_incident_manager_once(self, config: FireSmokeConfig) -> None:
128
+ """
129
+ Initialize incident manager ONCE with Redis OR Kafka clients (Environment based).
130
+ Called from process() on first invocation.
131
+ Uses config.session (existing session from pipeline) or creates from environment.
132
+ """
133
+ if self._incident_manager_initialized:
134
+ return
135
+
136
+ try:
137
+ self.logger.info("[INCIDENT_MANAGER] Starting incident manager initialization for fire detection...")
138
+
139
+ # Create factory if not exists
140
+ if self._incident_manager_factory is None:
141
+ self._incident_manager_factory = IncidentManagerFactory(logger=self.logger)
142
+
143
+ # Initialize using factory (handles session creation, Redis/Kafka setup)
144
+ self._incident_manager = self._incident_manager_factory.initialize(config)
145
+
146
+ if self._incident_manager:
147
+ self.logger.info("[INCIDENT_MANAGER] ✓ Incident manager initialized successfully for fire detection")
148
+ else:
149
+ self.logger.warning("[INCIDENT_MANAGER] Incident manager not available, incidents won't be published")
150
+
151
+ except Exception as e:
152
+ self.logger.error(f"[INCIDENT_MANAGER] Incident manager initialization failed: {e}", exc_info=True)
153
+ finally:
154
+ self._incident_manager_initialized = True # Mark as initialized (don't retry every frame)
155
+
156
+ def _send_incident_to_manager(
157
+ self,
158
+ incident: Dict,
159
+ stream_info: Optional[Dict[str, Any]] = None
160
+ ) -> None:
161
+ """
162
+ Send incident to incident manager for level tracking and publishing.
163
+
164
+ The incident manager will:
165
+ 1. Track the severity level
166
+ 2. Wait for consecutive frames before publishing:
167
+ - 5 frames for medium/significant/critical
168
+ - 10 frames for low (stricter to avoid false positives)
169
+ 3. Publish only when level changes
170
+ 4. Send 'info' level after 101 consecutive empty frames (incident ended)
171
+
172
+ Args:
173
+ incident: Incident dictionary from _generate_incidents
174
+ stream_info: Stream metadata containing camera info
175
+ """
176
+ if not self._incident_manager:
177
+ self.logger.debug("[INCIDENT_MANAGER] No incident manager available, skipping")
178
+ return
179
+
180
+ # Extract camera_id from stream_info
181
+ # Priority: camera_info.camera_id > stream_info.camera_id > extract from topic
182
+ camera_id = ""
183
+ if stream_info:
184
+ camera_info = stream_info.get("camera_info", {}) or {}
185
+ camera_id = camera_info.get("camera_id", "") or camera_info.get("cameraId", "")
186
+
187
+ if not camera_id:
188
+ camera_id = stream_info.get("camera_id", "") or stream_info.get("cameraId", "")
189
+
190
+ # Extract camera_id from topic if not found elsewhere
191
+ # Topic format: {camera_id}_input_topic
192
+ if not camera_id:
193
+ topic = stream_info.get("topic", "")
194
+ if topic:
195
+ if topic.endswith("_input_topic"):
196
+ camera_id = topic[: -len("_input_topic")]
197
+ self.logger.debug(f"[INCIDENT_MANAGER] Extracted camera_id from topic (underscore): {camera_id}")
198
+ elif topic.endswith("_input-topic"):
199
+ camera_id = topic[: -len("_input-topic")]
200
+ self.logger.debug(f"[INCIDENT_MANAGER] Extracted camera_id from topic (hyphen): {camera_id}")
201
+ else:
202
+ # Fallback: split on known markers if not strictly at the end
203
+ if "_input_topic" in topic:
204
+ camera_id = topic.split("_input_topic")[0]
205
+ self.logger.debug(f"[INCIDENT_MANAGER] Extracted camera_id from topic split (underscore): {camera_id}")
206
+ elif "_input-topic" in topic:
207
+ camera_id = topic.split("_input-topic")[0]
208
+ self.logger.debug(f"[INCIDENT_MANAGER] Extracted camera_id from topic split (hyphen): {camera_id}")
209
+
210
+ if not camera_id:
211
+ # Fallback to a default identifier
212
+ camera_id = "default_camera"
213
+ self.logger.debug(f"[INCIDENT_MANAGER] No camera_id found, using default: {camera_id}")
214
+ else:
215
+ self.logger.debug(f"[INCIDENT_MANAGER] Using camera_id: {camera_id}")
216
+
217
+ try:
218
+ # Process the incident through the manager
219
+ published = self._incident_manager.process_incident(
220
+ camera_id=camera_id,
221
+ incident_data=incident,
222
+ stream_info=stream_info
223
+ )
224
+
225
+ if published:
226
+ self.logger.info(f"[INCIDENT_MANAGER] Incident published for camera: {camera_id}")
227
+ except Exception as e:
228
+ self.logger.error(f"[INCIDENT_MANAGER] Error sending incident to manager: {e}", exc_info=True)
115
229
 
116
230
  def process(
117
231
  self,
@@ -135,6 +249,10 @@ class FireSmokeUseCase(BaseProcessor):
135
249
  context=context,
136
250
  )
137
251
 
252
+ # Step 0.5: Initialize incident manager once (for publishing incidents on level change)
253
+ if not self._incident_manager_initialized:
254
+ self._initialize_incident_manager_once(config)
255
+
138
256
  # Step 1: Init context
139
257
  if context is None:
140
258
  context = ProcessingContext()
@@ -225,6 +343,18 @@ class FireSmokeUseCase(BaseProcessor):
225
343
  stream_info=stream_info
226
344
  )
227
345
  business_analytics_list = self._generate_business_analytics(fire_smoke_summary, alerts, config, stream_info, is_empty=True)
346
+
347
+ # Step 8.5: Send incident to incident manager for level tracking and publishing
348
+ # The incident manager handles:
349
+ # - 5-consecutive-frame validation
350
+ # - Publishing only on level change
351
+ # - Skipping "low" level incidents
352
+ incidents = incidents_list[0] if incidents_list else {}
353
+ self._send_incident_to_manager(incidents, stream_info)
354
+ # if incidents_list and len(incidents_list) > 0:
355
+ # incident = incidents_list[0]
356
+ # if incident and incident != {}:
357
+ # self._send_incident_to_manager(incident, stream_info)
228
358
 
229
359
  # Step 9: Human-readable summary
230
360
  summary_list = self._generate_summary(fire_smoke_summary, general_summary, incidents_list, tracking_stats_list, business_analytics_list, alerts)
@@ -232,7 +362,7 @@ class FireSmokeUseCase(BaseProcessor):
232
362
  # Finalize context and return result
233
363
  context.processing_time = time.time() - start_time
234
364
 
235
- incidents = incidents_list[0] if incidents_list else {}
365
+
236
366
  tracking_stats = tracking_stats_list[0] if tracking_stats_list else {}
237
367
  #EVENT ENDED SIGNAL
238
368
 
@@ -245,8 +375,8 @@ class FireSmokeUseCase(BaseProcessor):
245
375
  business_analytics = business_analytics_list[0] if business_analytics_list else []
246
376
  summary = summary_list[0] if summary_list else {}
247
377
  agg_summary = {str(frame_number): {
248
- "incidents": [incidents],
249
- "tracking_stats": [tracking_stats],
378
+ "incidents": incidents,
379
+ "tracking_stats": tracking_stats,
250
380
  "business_analytics": business_analytics,
251
381
  "alerts": alerts,
252
382
  "human_text": summary}
@@ -363,6 +493,9 @@ class FireSmokeUseCase(BaseProcessor):
363
493
  ) -> Dict:
364
494
  """Generate structured events for fire and smoke detection output with frame-aware keys."""
365
495
 
496
+ level_params=[{"level":"low","percentage":0.0001},{"level":"medium","percentage":3},
497
+ {"level":"significant","percentage":13},{"level":"critical","percentage":30}]
498
+
366
499
  def get_trend_incident(data, lookback=23, prior=14):
367
500
  '''
368
501
  Determine if the trend is ascending or descending based on actual value progression.
@@ -474,7 +607,11 @@ class FireSmokeUseCase(BaseProcessor):
474
607
 
475
608
  # Generate human text in new format
476
609
  human_text_lines = [f"INCIDENTS DETECTED @ {current_timestamp}:"]
477
- human_text_lines.append(f"\tSeverity Level: {(self.CASE_TYPE,level)}")
610
+ if level=='significant':
611
+ print_level = "high"
612
+ else:
613
+ print_level = level
614
+ human_text_lines.append(f"\tSeverity Level: {(self.CASE_TYPE,print_level)}")
478
615
  human_text = "\n".join(human_text_lines)
479
616
 
480
617
  # Pass the last severity level **value** instead of a single-element list
@@ -504,6 +641,7 @@ class FireSmokeUseCase(BaseProcessor):
504
641
  start_time=start_timestamp, end_time=self.current_incident_end_timestamp,
505
642
  level_settings= {"low": 3, "medium": 5, "significant":15, "critical": 30})
506
643
  event['duration'] = self.get_duration_seconds(start_timestamp, self.current_incident_end_timestamp)
644
+ event['incident_quant'] = intensity_pct
507
645
  incidents.append(event)
508
646
 
509
647
  else:
@@ -575,17 +713,17 @@ class FireSmokeUseCase(BaseProcessor):
575
713
  human_lines.append(f"\t- No fire or smoke detected")
576
714
 
577
715
  human_lines.append("")
578
- human_lines.append(f"ALERTS SINCE @ {start_timestamp}:")
716
+ # human_lines.append(f"ALERTS SINCE @ {start_timestamp}:")
579
717
 
580
718
  recent_fire_detected = any(entry.get("fire", 0) > 0 for entry in self._fire_smoke_recent_history)
581
719
  recent_smoke_detected = any(entry.get("smoke", 0) > 0 for entry in self._fire_smoke_recent_history)
582
720
 
583
- if recent_fire_detected:
584
- human_lines.append(f"\t- Fire alert")
585
- if recent_smoke_detected:
586
- human_lines.append(f"\t- Smoke alert")
587
- if not recent_fire_detected and not recent_smoke_detected:
588
- human_lines.append(f"\t- No fire or smoke detected in recent frames")
721
+ # if recent_fire_detected:
722
+ # human_lines.append(f"\t- Fire alert")
723
+ # if recent_smoke_detected:
724
+ # human_lines.append(f"\t- Smoke alert")
725
+ # if not recent_fire_detected and not recent_smoke_detected:
726
+ # human_lines.append(f"\t- No fire or smoke detected in recent frames")
589
727
 
590
728
  human_text = "\n".join(human_lines)
591
729