matrice-analytics 0.1.106__py3-none-any.whl → 0.1.124__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 (24) hide show
  1. matrice_analytics/post_processing/__init__.py +22 -0
  2. matrice_analytics/post_processing/config.py +15 -0
  3. matrice_analytics/post_processing/core/config.py +107 -1
  4. matrice_analytics/post_processing/face_reg/face_recognition.py +2 -2
  5. matrice_analytics/post_processing/post_processor.py +16 -0
  6. matrice_analytics/post_processing/usecases/__init__.py +9 -0
  7. matrice_analytics/post_processing/usecases/crowdflow.py +1088 -0
  8. matrice_analytics/post_processing/usecases/footfall.py +103 -62
  9. matrice_analytics/post_processing/usecases/license_plate_monitoring.py +2 -1
  10. matrice_analytics/post_processing/usecases/parking_lot_analytics.py +1137 -0
  11. matrice_analytics/post_processing/usecases/vehicle_monitoring.py +30 -4
  12. matrice_analytics/post_processing/usecases/vehicle_monitoring_drone_view.py +33 -6
  13. matrice_analytics/post_processing/usecases/vehicle_monitoring_parking_lot.py +18 -2
  14. matrice_analytics/post_processing/usecases/vehicle_monitoring_wrong_way.py +1021 -0
  15. matrice_analytics/post_processing/utils/alert_instance_utils.py +18 -5
  16. matrice_analytics/post_processing/utils/business_metrics_manager_utils.py +25 -2
  17. matrice_analytics/post_processing/utils/incident_manager_utils.py +12 -1
  18. matrice_analytics/post_processing/utils/parking_analytics_tracker.py +359 -0
  19. matrice_analytics/post_processing/utils/wrong_way_tracker.py +670 -0
  20. {matrice_analytics-0.1.106.dist-info → matrice_analytics-0.1.124.dist-info}/METADATA +1 -1
  21. {matrice_analytics-0.1.106.dist-info → matrice_analytics-0.1.124.dist-info}/RECORD +24 -19
  22. {matrice_analytics-0.1.106.dist-info → matrice_analytics-0.1.124.dist-info}/WHEEL +0 -0
  23. {matrice_analytics-0.1.106.dist-info → matrice_analytics-0.1.124.dist-info}/licenses/LICENSE.txt +0 -0
  24. {matrice_analytics-0.1.106.dist-info → matrice_analytics-0.1.124.dist-info}/top_level.txt +0 -0
@@ -30,6 +30,10 @@ class VehicleMonitoringConfig(BaseConfig):
30
30
  smoothing_confidence_range_factor: float = 0.5
31
31
  confidence_threshold: float = 0.6
32
32
 
33
+ # Class Aggregation: Configuration parameters
34
+ enable_class_aggregation: bool = True
35
+ class_aggregation_window_size: int = 30 # 30 frames ≈ 1 second at 30 FPS
36
+
33
37
  #JBK_720_GATE POLYGON = [[86, 328], [844, 317], [1277, 520], [1273, 707], [125, 713]]
34
38
  zone_config: Optional[Dict[str, List[List[float]]]] = None #field(
35
39
  # default_factory=lambda: {
@@ -107,8 +111,24 @@ class VehicleMonitoringUseCase(BaseProcessor):
107
111
  def process(self, data: Any, config: ConfigProtocol, context: Optional[ProcessingContext] = None,
108
112
  stream_info: Optional[Dict[str, Any]] = None) -> ProcessingResult:
109
113
  processing_start = time.time()
110
- if not isinstance(config, VehicleMonitoringConfig):
111
- return self.create_error_result("Invalid config type", usecase=self.name, category=self.category, context=context)
114
+ # Relaxed check: Accept VehicleMonitoringConfig OR any config with matching usecase/category
115
+ # This handles multiprocessing module path mismatches while maintaining type safety
116
+ is_valid_config = (
117
+ isinstance(config, VehicleMonitoringConfig) or
118
+ (hasattr(config, 'usecase') and config.usecase == 'vehicle_monitoring' and
119
+ hasattr(config, 'category') and config.category == 'traffic')
120
+ )
121
+ if not is_valid_config:
122
+ self.logger.error(
123
+ f"Config validation failed in vehicle_monitoring. "
124
+ f"Got type={type(config).__name__}, module={type(config).__module__}, "
125
+ f"usecase={getattr(config, 'usecase', 'N/A')}, category={getattr(config, 'category', 'N/A')}"
126
+ )
127
+ return self.create_error_result(
128
+ f"Invalid config type: expected VehicleMonitoringConfig or config with usecase='vehicle_monitoring', "
129
+ f"got {type(config).__name__} with usecase={getattr(config, 'usecase', 'N/A')}",
130
+ usecase=self.name, category=self.category, context=context
131
+ )
112
132
  if context is None:
113
133
  context = ProcessingContext()
114
134
 
@@ -158,9 +178,15 @@ class VehicleMonitoringUseCase(BaseProcessor):
158
178
  from ..advanced_tracker import AdvancedTracker
159
179
  from ..advanced_tracker.config import TrackerConfig
160
180
  if self.tracker is None:
161
- tracker_config = TrackerConfig()
181
+ tracker_config = TrackerConfig(
182
+ enable_class_aggregation=config.enable_class_aggregation,
183
+ class_aggregation_window_size=config.class_aggregation_window_size
184
+ )
162
185
  self.tracker = AdvancedTracker(tracker_config)
163
- self.logger.info("Initialized AdvancedTracker for Vehicle Monitoring")
186
+ self.logger.info(
187
+ f"Initialized AdvancedTracker for Vehicle Monitoring "
188
+ f"(class_aggregation={config.enable_class_aggregation})"
189
+ )
164
190
  processed_data = self.tracker.update(processed_data)
165
191
  except Exception as e:
166
192
  self.logger.warning(f"AdvancedTracker failed: {e}")
@@ -1115,18 +1115,33 @@ class VehicleMonitoringDroneViewUseCase(BaseProcessor):
1115
1115
  self._tracking_start_time = time.time()
1116
1116
 
1117
1117
 
1118
- def _log_detection_stats(self, data: Any, stage_name: str, show_samples: bool = False) -> None:
1118
+ def _log_detection_stats(
1119
+ self,
1120
+ data: Any,
1121
+ stage_name: str,
1122
+ show_samples: bool = False
1123
+ ) -> None:
1119
1124
  """
1120
- Log detailed detection statistics at any pipeline stage.
1125
+ Log detailed detection statistics at any pipeline stage with frame identification.
1121
1126
 
1122
1127
  Args:
1123
1128
  data: Detection data (list or dict format)
1124
1129
  stage_name: Name of the pipeline stage for identification
1125
1130
  show_samples: If True, show sample detection structure
1126
1131
  """
1132
+ if not getattr(self, '_debug_detection_flow', True):
1133
+ return
1134
+
1135
+ # Use existing frame counter
1136
+ frame_id = self._total_frame_counter
1137
+ timestamp_str = datetime.now(timezone.utc).strftime('%H:%M:%S.%f')[:-3]
1138
+
1139
+ # Create unique log identifier for grep
1140
+ log_id = f"FRAME_{frame_id:06d}"
1141
+
1127
1142
  separator = "=" * 80
1128
1143
  print(f"\n{separator}")
1129
- print(f"[DETECTION_STATS] Stage: {stage_name}")
1144
+ print(f"[DETECTION_STATS] {log_id} | Time: {timestamp_str} | Stage: {stage_name}")
1130
1145
  print(separator)
1131
1146
 
1132
1147
  # Handle different data formats
@@ -1135,12 +1150,12 @@ class VehicleMonitoringDroneViewUseCase(BaseProcessor):
1135
1150
  detections = data
1136
1151
  elif isinstance(data, dict):
1137
1152
  # Frame-based format
1138
- for frame_id, frame_dets in data.items():
1153
+ for fid, frame_dets in data.items():
1139
1154
  if isinstance(frame_dets, list):
1140
1155
  detections.extend(frame_dets)
1141
1156
 
1142
1157
  if not detections:
1143
- print(f" Total Detections: 0")
1158
+ print(f" Frame: #{frame_id} | Total Detections: 0")
1144
1159
  print(separator)
1145
1160
  return
1146
1161
 
@@ -1153,6 +1168,7 @@ class VehicleMonitoringDroneViewUseCase(BaseProcessor):
1153
1168
  confidence_min = {}
1154
1169
  confidence_max = {}
1155
1170
  bbox_format_count = {"x1/y1/x2/y2": 0, "xmin/ymin/xmax/ymax": 0, "other": 0}
1171
+ has_track_id = 0
1156
1172
 
1157
1173
  for det in detections:
1158
1174
  if not isinstance(det, dict):
@@ -1181,9 +1197,17 @@ class VehicleMonitoringDroneViewUseCase(BaseProcessor):
1181
1197
  bbox_format_count["xmin/ymin/xmax/ymax"] += 1
1182
1198
  else:
1183
1199
  bbox_format_count["other"] += 1
1200
+
1201
+ # Track ID presence
1202
+ if det.get('track_id') is not None:
1203
+ has_track_id += 1
1184
1204
 
1185
1205
  # Print summary
1186
- print(f" Total Detections: {total_count}")
1206
+ print(f" Frame: #{frame_id} | Total Detections: {total_count}")
1207
+
1208
+ if has_track_id > 0:
1209
+ print(f" Detections with Track ID: {has_track_id} ({100*has_track_id/total_count:.1f}%)")
1210
+
1187
1211
  print(f"\n Category Distribution:")
1188
1212
 
1189
1213
  # Sort categories by count (descending)
@@ -1213,6 +1237,9 @@ class VehicleMonitoringDroneViewUseCase(BaseProcessor):
1213
1237
  print(f" Category: {sample.get('category')} (type: {type(sample.get('category')).__name__})")
1214
1238
  print(f" Confidence: {sample.get('confidence')} (type: {type(sample.get('confidence')).__name__})")
1215
1239
 
1240
+ if sample.get('track_id') is not None:
1241
+ print(f" Track ID: {sample.get('track_id')} (type: {type(sample.get('track_id')).__name__})")
1242
+
1216
1243
  bbox = sample.get('bounding_box', sample.get('bbox', {}))
1217
1244
  if isinstance(bbox, dict):
1218
1245
  print(f" BBox Keys: {list(bbox.keys())}")
@@ -110,8 +110,24 @@ class VehicleMonitoringParkingLotUseCase(BaseProcessor):
110
110
  def process(self, data: Any, config: ConfigProtocol, context: Optional[ProcessingContext] = None,
111
111
  stream_info: Optional[Dict[str, Any]] = None) -> ProcessingResult:
112
112
  processing_start = time.time()
113
- if not isinstance(config, VehicleMonitoringParkingLotConfig):
114
- return self.create_error_result("Invalid config type", usecase=self.name, category=self.category, context=context)
113
+ # Relaxed check: Accept VehicleMonitoringParkingLotConfig OR any config with matching usecase/category
114
+ # This handles multiprocessing module path mismatches while maintaining type safety
115
+ is_valid_config = (
116
+ isinstance(config, VehicleMonitoringParkingLotConfig) or
117
+ (hasattr(config, 'usecase') and config.usecase == 'vehicle_monitoring_parking_lot' and
118
+ hasattr(config, 'category') and config.category == 'traffic')
119
+ )
120
+ if not is_valid_config:
121
+ self.logger.error(
122
+ f"Config validation failed in vehicle_monitoring_parking_lot. "
123
+ f"Got type={type(config).__name__}, module={type(config).__module__}, "
124
+ f"usecase={getattr(config, 'usecase', 'N/A')}, category={getattr(config, 'category', 'N/A')}"
125
+ )
126
+ return self.create_error_result(
127
+ f"Invalid config type: expected VehicleMonitoringParkingLotConfig or config with usecase='vehicle_monitoring_parking_lot', "
128
+ f"got {type(config).__name__} with usecase={getattr(config, 'usecase', 'N/A')}",
129
+ usecase=self.name, category=self.category, context=context
130
+ )
115
131
  if context is None:
116
132
  context = ProcessingContext()
117
133