matrice-analytics 0.1.70__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.
- matrice_analytics/post_processing/config.py +2 -2
- matrice_analytics/post_processing/core/base.py +1 -1
- matrice_analytics/post_processing/face_reg/face_recognition.py +871 -190
- matrice_analytics/post_processing/face_reg/face_recognition_client.py +55 -25
- matrice_analytics/post_processing/usecases/advanced_customer_service.py +908 -498
- matrice_analytics/post_processing/usecases/color_detection.py +18 -18
- matrice_analytics/post_processing/usecases/customer_service.py +356 -9
- matrice_analytics/post_processing/usecases/fire_detection.py +147 -9
- matrice_analytics/post_processing/usecases/license_plate_monitoring.py +549 -41
- matrice_analytics/post_processing/usecases/people_counting.py +11 -11
- matrice_analytics/post_processing/usecases/vehicle_monitoring.py +34 -34
- matrice_analytics/post_processing/utils/alert_instance_utils.py +950 -0
- matrice_analytics/post_processing/utils/business_metrics_manager_utils.py +1245 -0
- matrice_analytics/post_processing/utils/incident_manager_utils.py +1657 -0
- {matrice_analytics-0.1.70.dist-info → matrice_analytics-0.1.89.dist-info}/METADATA +1 -1
- {matrice_analytics-0.1.70.dist-info → matrice_analytics-0.1.89.dist-info}/RECORD +19 -16
- {matrice_analytics-0.1.70.dist-info → matrice_analytics-0.1.89.dist-info}/WHEEL +0 -0
- {matrice_analytics-0.1.70.dist-info → matrice_analytics-0.1.89.dist-info}/licenses/LICENSE.txt +0 -0
- {matrice_analytics-0.1.70.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
|
-
|
|
365
|
+
|
|
236
366
|
tracking_stats = tracking_stats_list[0] if tracking_stats_list else {}
|
|
237
367
|
#EVENT ENDED SIGNAL
|
|
238
368
|
|
|
@@ -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
|
-
|
|
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
|
-
|
|
585
|
-
if recent_smoke_detected:
|
|
586
|
-
|
|
587
|
-
if not recent_fire_detected and not recent_smoke_detected:
|
|
588
|
-
|
|
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
|
|