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,36 +30,70 @@ class TrajectoryCorrector:
30
30
  def __init__(self):
31
31
  # track_id -> { "centers": deque, "smooth_angle": float }
32
32
  self.history = defaultdict(lambda: {
33
- "centers": deque(maxlen=10), # Lookback for velocity
34
- "smooth_angle": None # For EMA smoothing
33
+ "centers": deque(maxlen=10),
34
+ "angles": deque(maxlen=5),
35
+ "smooth_angle": None
35
36
  })
36
37
 
37
- def update_and_correct(self, track_id, center, raw_angle_deg):
38
+ def get_direction_label(self, angle):
38
39
  """
39
- Returns the corrected angle based on velocity fusion.
40
+ Your custom logic for Front/Back/Left/Right
41
+ """
42
+ if angle is None: return "unknown"
43
+ angle = angle % 360
44
+ if 45 <= angle < 135: return "back"
45
+ elif 135 <= angle < 225: return "left"
46
+ elif 225 <= angle < 315: return "front"
47
+ else: return "right"
48
+
49
+ def update_and_get_label(self, track_id, center, raw_angle_deg):
50
+ """
51
+ 1. Fixes Angle (+90)
52
+ 2. Calculates Velocity
53
+ 3. Applies EMA Smoothing
54
+ 4. Returns (Smooth_Angle, Label_String)
40
55
  """
41
56
  state = self.history[track_id]
42
57
  state["centers"].append(center)
43
-
44
- # 1. Calculate Velocity Angle
45
- velocity_angle = self._compute_velocity_angle(state["centers"])
46
-
47
- # 2. Apply +90 Fix to Raw Model Angle (Matches your successful tests)
48
- # Note: raw_angle_deg comes from predict.py
58
+
59
+ # --- FIX 1: ROTATE MODEL ANGLE ---
49
60
  if raw_angle_deg is None: raw_angle_deg = 0.0
50
- model_angle = (raw_angle_deg + 90) % 360
51
-
52
- # 3. Determine Target (Velocity vs Model)
53
- # Hybrid Logic: If moving (velocity valid), use Physics. Else, use Visuals.
54
- target_angle = velocity_angle if velocity_angle is not None else model_angle
61
+ fixed_raw_angle = (raw_angle_deg + 90) % 360
62
+ state["angles"].append(fixed_raw_angle)
63
+
64
+ # --- FIX 2: CALCULATE VELOCITY ---
65
+ motion_angle = self._compute_motion_angle(state["centers"])
55
66
 
56
- # 4. Apply EMA Smoothing (The Jitter Killer)
57
- # alpha=0.2 means we trust new data 20%, old history 80%
58
- state["smooth_angle"] = self._apply_ema(state["smooth_angle"], target_angle, alpha=0.2)
67
+ # Decide Target Angle
68
+ if motion_angle is not None:
69
+ target_angle = motion_angle
70
+ elif fixed_raw_angle is not None:
71
+ target_angle = fixed_raw_angle
72
+ elif state["smooth_angle"] is not None:
73
+ target_angle = state["smooth_angle"]
74
+ else:
75
+ target_angle = 0.0
76
+
77
+ # --- FIX 3: EMA SMOOTHING ---
78
+ alpha = 0.2
59
79
 
60
- return state["smooth_angle"]
80
+ if state["smooth_angle"] is None:
81
+ state["smooth_angle"] = target_angle
82
+ else:
83
+ prev_rad = math.radians(state["smooth_angle"])
84
+ curr_rad = math.radians(target_angle)
85
+
86
+ new_sin = (1 - alpha) * math.sin(prev_rad) + alpha * math.sin(curr_rad)
87
+ new_cos = (1 - alpha) * math.cos(prev_rad) + alpha * math.cos(curr_rad)
88
+
89
+ state["smooth_angle"] = math.degrees(math.atan2(new_sin, new_cos)) % 360
61
90
 
62
- def _compute_velocity_angle(self, centers):
91
+ final_angle = state["smooth_angle"]
92
+ label = self.get_direction_label(final_angle)
93
+
94
+ return final_angle, label
95
+
96
+ def _compute_motion_angle(self, centers):
63
97
  if len(centers) < 2:
64
98
  return None
65
99
 
@@ -71,27 +105,12 @@ class TrajectoryCorrector:
71
105
  dx = x_now - x_past
72
106
  dy = y_now - y_past
73
107
 
74
- # THRESHOLD: 2.5 pixels (Validated in your tests)
75
- # If moving less than this, velocity is noise.
76
- if math.hypot(dx, dy) < 2.5:
108
+ # THRESHOLD: 2.5 pixels
109
+ if math.hypot(dx, dy) < 0.5:
77
110
  return None
78
111
 
79
- # Angle calculation (0-360)
80
112
  return math.degrees(math.atan2(-dy, dx)) % 360
81
113
 
82
- def _apply_ema(self, current_smooth, new_target, alpha=0.2):
83
- if current_smooth is None:
84
- return new_target
85
-
86
- # Vector smoothing to handle 0/360 wrap-around correctly
87
- prev_rad = math.radians(current_smooth)
88
- curr_rad = math.radians(new_target)
89
-
90
- new_sin = (1 - alpha) * math.sin(prev_rad) + alpha * math.sin(curr_rad)
91
- new_cos = (1 - alpha) * math.cos(prev_rad) + alpha * math.cos(curr_rad)
92
-
93
- return math.degrees(math.atan2(new_sin, new_cos)) % 360
94
-
95
114
  @dataclass
96
115
  class FootFallConfig(BaseConfig):
97
116
  """Configuration for footfall use case."""
@@ -119,6 +138,7 @@ class FootFallConfig(BaseConfig):
119
138
 
120
139
  target_categories: List[str] = field(
121
140
  default_factory=lambda: ['person']
141
+
122
142
  )
123
143
 
124
144
  def validate(self) -> List[str]:
@@ -181,33 +201,17 @@ class FootFallUseCase(BaseProcessor):
181
201
  context.input_format = input_format
182
202
  context.confidence_threshold = config.confidence_threshold
183
203
 
204
+ # ... [Keep your standard filtering logic here: confidence, mapping, categories] ...
184
205
  if config.confidence_threshold is not None:
185
206
  processed_data = filter_by_confidence(data, config.confidence_threshold)
186
- self.logger.debug(f"Applied confidence filtering with threshold {config.confidence_threshold}")
187
207
  else:
188
208
  processed_data = data
189
- self.logger.debug("Did not apply confidence filtering since no threshold provided")
190
209
 
191
210
  if config.index_to_category:
192
211
  processed_data = apply_category_mapping(processed_data, config.index_to_category)
193
- self.logger.debug("Applied category mapping")
194
212
 
195
213
  if config.target_categories:
196
214
  processed_data = [d for d in processed_data if d.get('category') in self.target_categories]
197
- self.logger.debug("Applied category filtering")
198
-
199
- # if config.enable_smoothing:
200
- # if self.smoothing_tracker is None:
201
- # smoothing_config = BBoxSmoothingConfig(
202
- # smoothing_algorithm=config.smoothing_algorithm,
203
- # window_size=config.smoothing_window_size,
204
- # cooldown_frames=config.smoothing_cooldown_frames,
205
- # confidence_threshold=config.confidence_threshold,
206
- # confidence_range_factor=config.smoothing_confidence_range_factor,
207
- # enable_smoothing=True
208
- # )
209
- # self.smoothing_tracker = BBoxSmoothingTracker(smoothing_config)
210
- # processed_data = bbox_smoothing(processed_data, self.smoothing_tracker.config, self.smoothing_tracker)
211
215
 
212
216
  try:
213
217
  from ..advanced_tracker import AdvancedTracker
@@ -225,14 +229,29 @@ class FootFallUseCase(BaseProcessor):
225
229
  processed_data = self.tracker.update(processed_data)
226
230
 
227
231
  # =========================================================
228
- # NEW: INJECT VELOCITY FUSION LOGIC
232
+ # NEW: INJECT VELOCITY FUSION LOGIC (CORRECTED)
229
233
  # =========================================================
230
234
  for det in processed_data:
231
235
  track_id = det.get("track_id")
232
- bbox = det.get("bounding_box", det.get("bbox"))
236
+
237
+ # STREAM-SAFE bbox normalization
238
+ bbox = det.get("bbox") or det.get("bounding_box")
239
+
240
+ if isinstance(bbox, dict):
241
+ bbox = [
242
+ bbox.get("xmin"),
243
+ bbox.get("ymin"),
244
+ bbox.get("xmax"),
245
+ bbox.get("ymax"),
246
+ ]
247
+
248
+ # Hard safety guard
249
+ if not bbox or len(bbox) < 4:
250
+ continue
251
+
233
252
 
234
253
  # Check for 'raw_angle' (from predict.py) or 'orientation'
235
- raw_angle = det.get("raw_angle", det.get("orientation", 0.0))
254
+ raw_angle = det.get("angle", det.get("raw_angle", det.get("orientation", 0.0)))
236
255
 
237
256
  if track_id is not None and bbox:
238
257
  # Calculate Center (cx, cy)
@@ -240,21 +259,27 @@ class FootFallUseCase(BaseProcessor):
240
259
  cy = int((bbox[1] + bbox[3]) / 2)
241
260
 
242
261
  # Run Correction (Velocity + EMA + 90 Fix)
243
- final_angle = self.trajectory_corrector.update_and_correct(
262
+ # FIX: Unpack both values (Angle AND Label)
263
+ final_angle, direction_label = self.trajectory_corrector.update_and_get_label(
244
264
  track_id,
245
265
  (cx, cy),
246
266
  raw_angle
247
267
  )
248
268
 
249
269
  # OVERWRITE the detection angle
250
- # This ensures _generate_tracking_stats uses YOUR logic
251
270
  det["orientation"] = final_angle # For UI
252
271
  det["angle"] = final_angle # For Analytics
272
+
273
+ # FIX: SAVE THE DIRECTION LABEL
274
+ det["direction"] = direction_label # "front", "back", etc.
253
275
  # =========================================================
254
276
 
255
277
  except Exception as e:
256
278
  self.logger.warning(f"AdvancedTracker/Velocity failed: {e}")
257
279
 
280
+ # ... [The rest of your process method remains exactly the same] ...
281
+
282
+
258
283
  self._update_tracking_state(processed_data)
259
284
  self._total_frame_counter += 1
260
285
 
@@ -568,8 +593,21 @@ class FootFallUseCase(BaseProcessor):
568
593
  raw_track_id = det.get("track_id")
569
594
  if cat not in self.target_categories or raw_track_id is None:
570
595
  continue
571
- bbox = det.get("bounding_box", det.get("bbox"))
596
+ bbox = det.get("bbox") or det.get("bounding_box")
597
+
598
+ if isinstance(bbox, dict):
599
+ bbox = [
600
+ bbox.get("xmin"),
601
+ bbox.get("ymin"),
602
+ bbox.get("xmax"),
603
+ bbox.get("ymax"),
604
+ ]
605
+
606
+ if not bbox or len(bbox) < 4:
607
+ continue
608
+
572
609
  canonical_id = self._merge_or_register_track(raw_track_id, bbox)
610
+
573
611
  det["track_id"] = canonical_id
574
612
  self._per_category_total_track_ids.setdefault(cat, set()).add(canonical_id)
575
613
  self._current_frame_track_ids[cat].add(canonical_id)
@@ -753,7 +791,7 @@ class FootFallUseCase(BaseProcessor):
753
791
  def _count_categories(self, detections: list, config: FootFallConfig) -> dict:
754
792
  counts = {}
755
793
  for det in detections:
756
- cat = det.get('category', 'unknown')
794
+ cat = det.get("direction") or "unknown"
757
795
  counts[cat] = counts.get(cat, 0) + 1
758
796
  return {
759
797
  "total_count": sum(counts.values()),
@@ -762,9 +800,12 @@ class FootFallUseCase(BaseProcessor):
762
800
  {
763
801
  "bounding_box": det.get("bounding_box"),
764
802
  "category": det.get("category"),
803
+ "direction": det.get("direction"),
765
804
  "confidence": det.get("confidence"),
766
805
  "track_id": det.get("track_id"),
767
- "frame_id": det.get("frame_id")
806
+ "frame_id": det.get("frame_id"),
807
+ "angle": det.get("angle"),
808
+ "orientation": det.get("orientation") #for UI arrows
768
809
  }
769
810
  for det in detections
770
811
  ]
@@ -1112,7 +1112,7 @@ class LicensePlateMonitorUseCase(BaseProcessor):
1112
1112
  # Send to alert manager for evaluation
1113
1113
  try:
1114
1114
  self.logger.info(f"[ALERT_DEBUG] Sending detection event #{i+1} to alert manager...")
1115
- self.alert_manager.process_detection_event(detection_event)
1115
+ self.alert_manager.process_detection_event(detection_event, stream_info)
1116
1116
  self.logger.info(f"[ALERT_DEBUG] ✓ Sent detection event to alert manager: plate={plate_text}, confidence={confidence:.2f}")
1117
1117
  sent_count += 1
1118
1118
  except Exception as e:
@@ -1316,6 +1316,7 @@ class LicensePlateMonitorUseCase(BaseProcessor):
1316
1316
 
1317
1317
  input_format = match_results_structure(data)
1318
1318
  context.input_format = input_format
1319
+ config.confidence_threshold = 0.1
1319
1320
  context.confidence_threshold = config.confidence_threshold
1320
1321
  self._ocr_mode = config.ocr_mode
1321
1322
  self.logger.info(f"Processing license plate monitoring with format: {input_format.value}")