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.
- matrice_analytics/post_processing/__init__.py +22 -0
- matrice_analytics/post_processing/config.py +15 -0
- matrice_analytics/post_processing/core/config.py +107 -1
- matrice_analytics/post_processing/face_reg/face_recognition.py +2 -2
- matrice_analytics/post_processing/post_processor.py +16 -0
- matrice_analytics/post_processing/usecases/__init__.py +9 -0
- matrice_analytics/post_processing/usecases/crowdflow.py +1088 -0
- matrice_analytics/post_processing/usecases/footfall.py +103 -62
- matrice_analytics/post_processing/usecases/license_plate_monitoring.py +2 -1
- matrice_analytics/post_processing/usecases/parking_lot_analytics.py +1137 -0
- matrice_analytics/post_processing/usecases/vehicle_monitoring.py +30 -4
- matrice_analytics/post_processing/usecases/vehicle_monitoring_drone_view.py +33 -6
- matrice_analytics/post_processing/usecases/vehicle_monitoring_parking_lot.py +18 -2
- matrice_analytics/post_processing/usecases/vehicle_monitoring_wrong_way.py +1021 -0
- matrice_analytics/post_processing/utils/alert_instance_utils.py +18 -5
- matrice_analytics/post_processing/utils/business_metrics_manager_utils.py +25 -2
- matrice_analytics/post_processing/utils/incident_manager_utils.py +12 -1
- matrice_analytics/post_processing/utils/parking_analytics_tracker.py +359 -0
- matrice_analytics/post_processing/utils/wrong_way_tracker.py +670 -0
- {matrice_analytics-0.1.106.dist-info → matrice_analytics-0.1.124.dist-info}/METADATA +1 -1
- {matrice_analytics-0.1.106.dist-info → matrice_analytics-0.1.124.dist-info}/RECORD +24 -19
- {matrice_analytics-0.1.106.dist-info → matrice_analytics-0.1.124.dist-info}/WHEEL +0 -0
- {matrice_analytics-0.1.106.dist-info → matrice_analytics-0.1.124.dist-info}/licenses/LICENSE.txt +0 -0
- {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),
|
|
34
|
-
"
|
|
33
|
+
"centers": deque(maxlen=10),
|
|
34
|
+
"angles": deque(maxlen=5),
|
|
35
|
+
"smooth_angle": None
|
|
35
36
|
})
|
|
36
37
|
|
|
37
|
-
def
|
|
38
|
+
def get_direction_label(self, angle):
|
|
38
39
|
"""
|
|
39
|
-
|
|
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
|
|
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
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
#
|
|
54
|
-
|
|
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
|
-
#
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
75
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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("
|
|
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(
|
|
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}")
|