matrice-analytics 0.1.96__py3-none-any.whl → 0.1.106__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 (23) hide show
  1. matrice_analytics/post_processing/__init__.py +14 -1
  2. matrice_analytics/post_processing/advanced_tracker/config.py +8 -4
  3. matrice_analytics/post_processing/advanced_tracker/track_class_aggregator.py +128 -0
  4. matrice_analytics/post_processing/advanced_tracker/tracker.py +22 -1
  5. matrice_analytics/post_processing/config.py +6 -2
  6. matrice_analytics/post_processing/core/config.py +62 -0
  7. matrice_analytics/post_processing/face_reg/face_recognition.py +706 -73
  8. matrice_analytics/post_processing/face_reg/people_activity_logging.py +25 -14
  9. matrice_analytics/post_processing/post_processor.py +8 -0
  10. matrice_analytics/post_processing/usecases/__init__.py +7 -1
  11. matrice_analytics/post_processing/usecases/footfall.py +109 -2
  12. matrice_analytics/post_processing/usecases/license_plate_monitoring.py +55 -37
  13. matrice_analytics/post_processing/usecases/vehicle_monitoring.py +14 -32
  14. matrice_analytics/post_processing/usecases/vehicle_monitoring_drone_view.py +1223 -0
  15. matrice_analytics/post_processing/usecases/vehicle_monitoring_parking_lot.py +1028 -0
  16. matrice_analytics/post_processing/utils/__init__.py +5 -0
  17. matrice_analytics/post_processing/utils/agnostic_nms.py +759 -0
  18. matrice_analytics/post_processing/utils/alert_instance_utils.py +37 -2
  19. {matrice_analytics-0.1.96.dist-info → matrice_analytics-0.1.106.dist-info}/METADATA +1 -1
  20. {matrice_analytics-0.1.96.dist-info → matrice_analytics-0.1.106.dist-info}/RECORD +23 -19
  21. {matrice_analytics-0.1.96.dist-info → matrice_analytics-0.1.106.dist-info}/WHEEL +0 -0
  22. {matrice_analytics-0.1.96.dist-info → matrice_analytics-0.1.106.dist-info}/licenses/LICENSE.txt +0 -0
  23. {matrice_analytics-0.1.96.dist-info → matrice_analytics-0.1.106.dist-info}/top_level.txt +0 -0
@@ -154,33 +154,40 @@ class PeopleActivityLogging:
154
154
  except Exception as e:
155
155
  self.logger.error(f"Error enqueueing detection: {e}", exc_info=True)
156
156
 
157
- def _should_log_detection(self, employee_id: str) -> bool:
157
+ def _should_log_detection(self, employee_id: str, camera_id: str = "") -> bool:
158
158
  """
159
- Check if detection should be logged based on employee ID and time threshold.
160
- Only log if employee_id was not detected in the past 10 seconds.
159
+ Check if detection should be logged based on employee ID (+ camera ID) and time threshold.
160
+ Only log if the same (employee_id, camera_id) was not detected in the past N seconds.
161
+ If camera_id is empty, falls back to global employee_id de-duplication (backward compatible).
161
162
 
162
163
  TODO: Make this use track_id or similarity check instead of just employee_id in 10 secs window
163
164
  for better deduplication across different detection sessions.
164
165
  """
165
166
  current_time = time.time()
167
+ dedupe_key = f"{employee_id}::{camera_id}" if camera_id else employee_id
166
168
 
167
169
  # Clean up old entries (older than threshold)
168
170
  expired_keys = [
169
- emp_id for emp_id, timestamp in self.recent_employee_detections.items()
171
+ key for key, timestamp in self.recent_employee_detections.items()
170
172
  if current_time - timestamp > self.employee_detection_threshold
171
173
  ]
172
- for emp_id in expired_keys:
173
- del self.recent_employee_detections[emp_id]
174
+ for key in expired_keys:
175
+ del self.recent_employee_detections[key]
174
176
 
175
- # Check if employee was recently detected
176
- if employee_id in self.recent_employee_detections:
177
- last_detection = self.recent_employee_detections[employee_id]
177
+ # Check if employee was recently detected (per camera_id)
178
+ if dedupe_key in self.recent_employee_detections:
179
+ last_detection = self.recent_employee_detections[dedupe_key]
178
180
  if current_time - last_detection < self.employee_detection_threshold:
179
- self.logger.debug(f"Skipping logging for employee {employee_id} - detected {current_time - last_detection:.1f}s ago")
181
+ self.logger.debug(
182
+ "Skipping logging for employee %s (camera_id=%s) - detected %.1fs ago",
183
+ employee_id,
184
+ camera_id,
185
+ current_time - last_detection,
186
+ )
180
187
  return False
181
188
 
182
- # Update detection time for this employee
183
- self.recent_employee_detections[employee_id] = current_time
189
+ # Update detection time for this (employee, camera)
190
+ self.recent_employee_detections[dedupe_key] = current_time
184
191
  return True
185
192
 
186
193
  async def _process_activity(self, activity_data: Dict):
@@ -202,8 +209,12 @@ class PeopleActivityLogging:
202
209
  return
203
210
 
204
211
  # Check if we should log this detection (avoid duplicates within time window)
205
- if not self._should_log_detection(employee_id):
206
- self.logger.debug(f"Skipping activity log for employee_id={employee_id} (within cooldown period)")
212
+ if not self._should_log_detection(employee_id, camera_id=camera_id):
213
+ self.logger.debug(
214
+ "Skipping activity log for employee_id=%s (camera_id=%s) (within cooldown period)",
215
+ employee_id,
216
+ camera_id,
217
+ )
207
218
  return None
208
219
 
209
220
  # Encode frame as base64 JPEG
@@ -100,6 +100,8 @@ from .usecases import (
100
100
  SusActivityUseCase,
101
101
  NaturalDisasterUseCase,
102
102
  FootFallUseCase,
103
+ VehicleMonitoringParkingLotUseCase,
104
+ VehicleMonitoringDroneViewUseCase,
103
105
  # Put all IMAGE based usecases here
104
106
  BloodCancerDetectionUseCase,
105
107
  SkinCancerClassificationUseCase,
@@ -573,6 +575,12 @@ class PostProcessor:
573
575
  registry.register_use_case(
574
576
  "retail", "footfall", FootFallUseCase
575
577
  )
578
+ registry.register_use_case(
579
+ "traffic", "vehicle_monitoring_parking_lot", VehicleMonitoringParkingLotUseCase
580
+ )
581
+ registry.register_use_case(
582
+ "traffic", "vehicle_monitoring_drone_view", VehicleMonitoringDroneViewUseCase
583
+ )
576
584
 
577
585
  # Put all IMAGE based usecases here
578
586
  registry.register_use_case(
@@ -86,7 +86,8 @@ from .underground_pipeline_defect_detection import UndergroundPipelineDefectConf
86
86
  from .suspicious_activity_detection import SusActivityConfig, SusActivityUseCase
87
87
  from .natural_disaster import NaturalDisasterConfig, NaturalDisasterUseCase
88
88
  from .footfall import FootFallConfig, FootFallUseCase
89
-
89
+ from .vehicle_monitoring_parking_lot import VehicleMonitoringParkingLotUseCase, VehicleMonitoringParkingLotConfig
90
+ from .vehicle_monitoring_drone_view import VehicleMonitoringDroneViewUseCase, VehicleMonitoringDroneViewConfig
90
91
 
91
92
  #Put all IMAGE based usecases here
92
93
  from .blood_cancer_detection_img import BloodCancerDetectionConfig, BloodCancerDetectionUseCase
@@ -175,6 +176,8 @@ __all__ = [
175
176
  'SusActivityUseCase',
176
177
  'NaturalDisasterUseCase',
177
178
  'FootFallUseCase',
179
+ 'VehicleMonitoringParkingLotUseCase',
180
+ 'VehicleMonitoringDroneViewUseCase',
178
181
 
179
182
  #Put all IMAGE based usecases here
180
183
  'BloodCancerDetectionUseCase',
@@ -258,6 +261,9 @@ __all__ = [
258
261
  'SusActivityConfig',
259
262
  'NaturalDisasterConfig',
260
263
  'FootFallConfig',
264
+ 'VehicleMonitoringParkingLotConfig',
265
+ 'VehicleMonitoringDroneViewConfig',
266
+
261
267
  #Put all IMAGE based usecase CONFIGS here
262
268
  'BloodCancerDetectionConfig',
263
269
  'SkinCancerClassificationConfig',
@@ -1,7 +1,10 @@
1
1
  from typing import Any, Dict, List, Optional
2
2
  from dataclasses import asdict
3
3
  import time
4
+ import math
5
+ import numpy as np
4
6
  from datetime import datetime, timezone
7
+ from collections import defaultdict, deque
5
8
 
6
9
  from ..core.base import BaseProcessor, ProcessingContext, ProcessingResult, ConfigProtocol, ResultFormat
7
10
  from ..utils import (
@@ -19,6 +22,76 @@ from ..utils import (
19
22
  from dataclasses import dataclass, field
20
23
  from ..core.config import BaseConfig, AlertConfig, ZoneConfig
21
24
 
25
+ class TrajectoryCorrector:
26
+ """
27
+ Handles Velocity-Fusion logic to correct model orientation errors.
28
+ Stores history of track centers and applies EMA smoothing.
29
+ """
30
+ def __init__(self):
31
+ # track_id -> { "centers": deque, "smooth_angle": float }
32
+ self.history = defaultdict(lambda: {
33
+ "centers": deque(maxlen=10), # Lookback for velocity
34
+ "smooth_angle": None # For EMA smoothing
35
+ })
36
+
37
+ def update_and_correct(self, track_id, center, raw_angle_deg):
38
+ """
39
+ Returns the corrected angle based on velocity fusion.
40
+ """
41
+ state = self.history[track_id]
42
+ 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
49
+ 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
55
+
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)
59
+
60
+ return state["smooth_angle"]
61
+
62
+ def _compute_velocity_angle(self, centers):
63
+ if len(centers) < 2:
64
+ return None
65
+
66
+ # Look back 5 frames for stability
67
+ lookback = min(len(centers), 5)
68
+ (x_past, y_past) = centers[-lookback]
69
+ (x_now, y_now) = centers[-1]
70
+
71
+ dx = x_now - x_past
72
+ dy = y_now - y_past
73
+
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:
77
+ return None
78
+
79
+ # Angle calculation (0-360)
80
+ return math.degrees(math.atan2(-dy, dx)) % 360
81
+
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
+
22
95
  @dataclass
23
96
  class FootFallConfig(BaseConfig):
24
97
  """Configuration for footfall use case."""
@@ -79,9 +152,12 @@ class FootFallUseCase(BaseProcessor):
79
152
  self.category = "retail"
80
153
  self.CASE_TYPE: Optional[str] = 'footfall'
81
154
  self.CASE_VERSION: Optional[str] = '1.1'
82
- self.target_categories = ['person'] #['person', 'people','human','man','woman','male','female']
155
+ self.target_categories = ['person']
83
156
  self.smoothing_tracker = None
84
157
  self.tracker = None
158
+
159
+ # Initialize the Velocity Logic
160
+ self.trajectory_corrector = TrajectoryCorrector()
85
161
  self._total_frame_counter = 0
86
162
  self._global_frame_offset = 0
87
163
  self._tracking_start_time = None
@@ -144,9 +220,40 @@ class FootFallUseCase(BaseProcessor):
144
220
  match_thresh=0.8)
145
221
  self.tracker = AdvancedTracker(tracker_config)
146
222
  self.logger.info("Initialized AdvancedTracker for People Counting")
223
+
224
+ # 1. Run Standard Tracker (Assigns IDs)
147
225
  processed_data = self.tracker.update(processed_data)
226
+
227
+ # =========================================================
228
+ # NEW: INJECT VELOCITY FUSION LOGIC
229
+ # =========================================================
230
+ for det in processed_data:
231
+ track_id = det.get("track_id")
232
+ bbox = det.get("bounding_box", det.get("bbox"))
233
+
234
+ # Check for 'raw_angle' (from predict.py) or 'orientation'
235
+ raw_angle = det.get("raw_angle", det.get("orientation", 0.0))
236
+
237
+ if track_id is not None and bbox:
238
+ # Calculate Center (cx, cy)
239
+ cx = int((bbox[0] + bbox[2]) / 2)
240
+ cy = int((bbox[1] + bbox[3]) / 2)
241
+
242
+ # Run Correction (Velocity + EMA + 90 Fix)
243
+ final_angle = self.trajectory_corrector.update_and_correct(
244
+ track_id,
245
+ (cx, cy),
246
+ raw_angle
247
+ )
248
+
249
+ # OVERWRITE the detection angle
250
+ # This ensures _generate_tracking_stats uses YOUR logic
251
+ det["orientation"] = final_angle # For UI
252
+ det["angle"] = final_angle # For Analytics
253
+ # =========================================================
254
+
148
255
  except Exception as e:
149
- self.logger.warning(f"AdvancedTracker failed: {e}")
256
+ self.logger.warning(f"AdvancedTracker/Velocity failed: {e}")
150
257
 
151
258
  self._update_tracking_state(processed_data)
152
259
  self._total_frame_counter += 1
@@ -535,6 +535,10 @@ class LicensePlateMonitorUseCase(BaseProcessor):
535
535
  self.plate_logger: Optional[LicensePlateMonitorLogger] = None
536
536
  self._logging_enabled = True # False //ToDo: DISABLED FOR NOW, ENABLED FOR PRODUCTION. ##
537
537
  self._plate_logger_initialized = False # Track if plate logger has been initialized
538
+
539
+ # Track which track_ids have been logged to avoid duplicate logging
540
+ # Only log confirmed/consensus plates, not every OCR prediction
541
+ self._logged_track_ids: set = set()
538
542
 
539
543
  # Initialize instant alert manager (will be lazily initialized on first process() call)
540
544
  self.alert_manager: Optional[ALERT_INSTANCE] = None
@@ -942,6 +946,8 @@ class LicensePlateMonitorUseCase(BaseProcessor):
942
946
  self._unique_plate_texts = {}
943
947
  self.helper = {}
944
948
  self.unique_plate_track = {}
949
+ # Reset logged track_ids to allow fresh logging
950
+ self._logged_track_ids = set()
945
951
  self.logger.info("Plate tracking state reset")
946
952
 
947
953
  def reset_all_tracking(self) -> None:
@@ -1147,13 +1153,19 @@ class LicensePlateMonitorUseCase(BaseProcessor):
1147
1153
 
1148
1154
  async def _log_detected_plates(self, detections: List[Dict[str, Any]], config: LicensePlateMonitorConfig,
1149
1155
  stream_info: Optional[Dict[str, Any]], image_bytes: Optional[bytes] = None) -> None:
1150
- """Log all detected plates to RPC server with cooldown."""
1156
+ """
1157
+ Log confirmed/consensus plates to RPC server.
1158
+
1159
+ Only logs plates that have reached consensus (are in _tracked_plate_texts),
1160
+ and only logs each track_id once to avoid duplicate logging of garbage OCR predictions.
1161
+ Uses the confirmed consensus plate text, not the raw frame-by-frame OCR output.
1162
+ """
1151
1163
  # Enhanced logging for diagnostics
1152
1164
  print(f"[LP_LOGGING] Starting plate logging check - detections count: {len(detections)}")
1153
1165
  self.logger.info(f"[LP_LOGGING] Starting plate logging check - detections count: {len(detections)}")
1154
- self.logger.info(f"[LP_LOGGING] Logging enabled: {self._logging_enabled}, Plate logger exists: {self.plate_logger is not None}, Stream info exists: {stream_info is not None}")
1166
+ self.logger.info(f"[LP_LOGGING] Logging enabled: {self._logging_enabled}, Plate logger exists: {self.plate_logger is not None}")
1167
+ self.logger.info(f"[LP_LOGGING] Confirmed plates (tracked): {len(self._tracked_plate_texts)}, Already logged tracks: {len(self._logged_track_ids)}")
1155
1168
 
1156
- #self._logging_enabled = False # ToDo: DISABLED FOR NOW, ENABLED FOR PRODUCTION
1157
1169
  if not self._logging_enabled:
1158
1170
  print("[LP_LOGGING] Plate logging is DISABLED")
1159
1171
  self.logger.warning("[LP_LOGGING] Plate logging is DISABLED - logging_enabled flag is False")
@@ -1164,11 +1176,6 @@ class LicensePlateMonitorUseCase(BaseProcessor):
1164
1176
  self.logger.warning("[LP_LOGGING] Plate logging SKIPPED - plate_logger is not initialized (lpr_server_id may not be configured)")
1165
1177
  return
1166
1178
 
1167
- # if not stream_info:
1168
- # print("[LP_LOGGING] Plate logging SKIPPED - stream_info is None")
1169
- # self.logger.warning("[LP_LOGGING] Plate logging SKIPPED - stream_info is None")
1170
- # return
1171
-
1172
1179
  print("[LP_LOGGING] All pre-conditions met, proceeding with plate logging")
1173
1180
  self.logger.info(f"[LP_LOGGING] All pre-conditions met, proceeding with plate logging")
1174
1181
 
@@ -1195,35 +1202,45 @@ class LicensePlateMonitorUseCase(BaseProcessor):
1195
1202
  else:
1196
1203
  self.logger.warning(f"[LP_LOGGING] Failed to decode image bytes")
1197
1204
  except Exception as e:
1198
- #pass
1199
1205
  self.logger.error(f"[LP_LOGGING] Exception while encoding frame image: {e}", exc_info=True)
1200
1206
  else:
1201
1207
  self.logger.info(f"[LP_LOGGING] No image_bytes provided, sending without image")
1202
1208
 
1203
- # Collect all unique plates from current detections
1204
- plates_to_log = set()
1205
- detections_without_text = 0
1209
+ # Only log CONFIRMED/CONSENSUS plates from _tracked_plate_texts
1210
+ # Avoid logging every raw OCR prediction - only log final confirmed plate per track_id
1211
+ plates_to_log = {} # track_id -> consensus_plate_text
1212
+
1206
1213
  for det in detections:
1207
- plate_text = det.get('plate_text')
1208
- if not plate_text:
1209
- detections_without_text += 1
1214
+ track_id = det.get('track_id')
1215
+ if track_id is None:
1210
1216
  continue
1211
- plates_to_log.add(plate_text)
1217
+
1218
+ # Skip if this track_id has already been logged
1219
+ if track_id in self._logged_track_ids:
1220
+ self.logger.debug(f"[LP_LOGGING] Skipping track_id={track_id} - already logged")
1221
+ continue
1222
+
1223
+ # Only log if this track_id has a confirmed/consensus plate
1224
+ if track_id in self._tracked_plate_texts:
1225
+ consensus_plate = self._tracked_plate_texts[track_id]
1226
+ if consensus_plate:
1227
+ plates_to_log[track_id] = consensus_plate
1228
+ self.logger.debug(f"[LP_LOGGING] Found confirmed plate for track_id={track_id}: {consensus_plate}")
1212
1229
 
1213
- print(f"[LP_LOGGING] Collected {len(plates_to_log)} unique plates to log: {plates_to_log}")
1214
- self.logger.info(f"[LP_LOGGING] Collected {len(plates_to_log)} unique plates to log: {plates_to_log}")
1215
- if detections_without_text > 0:
1216
- self.logger.warning(f"[LP_LOGGING] {detections_without_text} detections have NO plate_text (OCR may have failed or not run yet)")
1230
+ confirmed_count = len(plates_to_log)
1231
+ raw_ocr_count = sum(1 for d in detections if d.get('plate_text'))
1232
+ print(f"[LP_LOGGING] Confirmed plates to log: {confirmed_count} (from {raw_ocr_count} raw OCR detections)")
1233
+ self.logger.info(f"[LP_LOGGING] Confirmed plates to log: {confirmed_count}, Raw OCR detections: {raw_ocr_count}")
1234
+ self.logger.info(f"[LP_LOGGING] Plates: {list(plates_to_log.values())}")
1217
1235
 
1218
- # Log each unique plate directly with await (respecting cooldown)
1236
+ # Log each confirmed plate (respecting cooldown)
1219
1237
  if plates_to_log:
1220
- print(f"[LP_LOGGING] Logging {len(plates_to_log)} plates with cooldown={config.plate_log_cooldown}s")
1221
- self.logger.info(f"[LP_LOGGING] Logging {len(plates_to_log)} plates with cooldown={config.plate_log_cooldown}s")
1238
+ print(f"[LP_LOGGING] Logging {len(plates_to_log)} confirmed plates with cooldown={config.plate_log_cooldown}s")
1239
+ self.logger.info(f"[LP_LOGGING] Logging {len(plates_to_log)} confirmed plates with cooldown={config.plate_log_cooldown}s")
1222
1240
  try:
1223
- # Call log_plate directly with await for each plate
1224
- for plate_text in plates_to_log:
1225
- print(f"[LP_LOGGING] Processing plate: {plate_text}")
1226
- self.logger.info(f"[LP_LOGGING] Processing plate: {plate_text}")
1241
+ for track_id, plate_text in plates_to_log.items():
1242
+ print(f"[LP_LOGGING] Processing confirmed plate: {plate_text} (track_id={track_id})")
1243
+ self.logger.info(f"[LP_LOGGING] Processing confirmed plate: {plate_text} (track_id={track_id})")
1227
1244
  try:
1228
1245
  result = await self.plate_logger.log_plate(
1229
1246
  plate_text=plate_text,
@@ -1232,25 +1249,26 @@ class LicensePlateMonitorUseCase(BaseProcessor):
1232
1249
  image_data=image_data,
1233
1250
  cooldown=config.plate_log_cooldown
1234
1251
  )
1235
- status = "SENT" if result else "SKIPPED (cooldown)"
1236
- print(f"[LP_LOGGING] Plate {plate_text}: {status}")
1237
- self.logger.info(f"[LP_LOGGING] Plate {plate_text}: {status}")
1252
+ if result:
1253
+ # Mark this track_id as logged to avoid duplicate logging
1254
+ self._logged_track_ids.add(track_id)
1255
+ print(f"[LP_LOGGING] Plate {plate_text}: SENT (track_id={track_id} marked as logged)")
1256
+ self.logger.info(f"[LP_LOGGING] Plate {plate_text}: SENT (track_id={track_id} marked as logged)")
1257
+ else:
1258
+ print(f"[LP_LOGGING] Plate {plate_text}: SKIPPED (cooldown)")
1259
+ self.logger.info(f"[LP_LOGGING] Plate {plate_text}: SKIPPED (cooldown)")
1238
1260
  except Exception as e:
1239
- #pass
1240
1261
  print(f"[LP_LOGGING] ERROR - Plate {plate_text} failed: {e}")
1241
1262
  self.logger.error(f"[LP_LOGGING] Plate {plate_text} raised exception: {e}", exc_info=True)
1242
1263
 
1243
1264
  print("[LP_LOGGING] Plate logging complete")
1244
- self.logger.info(f"[LP_LOGGING] Plate logging complete")
1265
+ self.logger.info(f"[LP_LOGGING] Plate logging complete - {len(self._logged_track_ids)} total tracks logged so far")
1245
1266
  except Exception as e:
1246
- print(f"[LP_LOGGING] CRITICAL ERROR during plate logging: {e}")
1247
-
1248
1267
  print(f"[LP_LOGGING] CRITICAL ERROR during plate logging: {e}")
1249
1268
  self.logger.error(f"[LP_LOGGING] CRITICAL ERROR during plate logging: {e}", exc_info=True)
1250
- pass
1251
1269
  else:
1252
- print("[LP_LOGGING] No plates to log")
1253
- self.logger.info(f"[LP_LOGGING] No plates to log (plates_to_log is empty)")
1270
+ print("[LP_LOGGING] No confirmed plates to log (plates may still be reaching consensus)")
1271
+ self.logger.info(f"[LP_LOGGING] No confirmed plates to log (waiting for consensus)")
1254
1272
 
1255
1273
  async def process(self, data: Any, config: ConfigProtocol, input_bytes: Optional[bytes] = None,
1256
1274
  context: Optional[ProcessingContext] = None, stream_info: Optional[Dict[str, Any]] = None) -> ProcessingResult:
@@ -40,54 +40,35 @@ class VehicleMonitoringConfig(BaseConfig):
40
40
  # )
41
41
  usecase_categories: List[str] = field(
42
42
  default_factory=lambda: [
43
- "person", "bicycle", "car", "motorcycle", "airplane", "bus", "train", "truck", "boat",
44
- "traffic light", "fire hydrant", "stop sign", "parking meter", "bench", "bird", "cat", "dog",
45
- "horse", "sheep", "cow", "elephant", "bear", "zebra", "giraffe", "backpack", "umbrella",
46
- "handbag", "tie", "suitcase", "frisbee", "skis", "snowboard", "sports ball", "kite",
47
- "baseball bat", "baseball glove", "skateboard", "surfboard", "tennis racket", "bottle",
48
- "wine glass", "cup", "fork", "knife", "spoon", "bowl", "banana", "apple", "sandwich",
49
- "orange", "broccoli", "carrot", "hot dog", "pizza", "donut", "cake", "chair", "couch",
50
- "potted plant", "bed", "dining table", "toilet", "tv", "laptop", "mouse", "remote",
51
- "keyboard", "cell phone", "microwave", "oven", "toaster", "sink", "refrigerator", "book",
52
- "clock", "vase", "scissors", "teddy bear", "hair drier", "toothbrush"
43
+ 'bicycle', 'motorcycle', 'car', 'van', 'bus', 'truck'
53
44
  ]
54
45
  )
55
46
  target_categories: List[str] = field(
56
47
  default_factory=lambda: [
57
- 'car', 'bicycle', 'bus','motorcycle']
48
+ 'bicycle', 'motorcycle', 'car', 'van', 'bus', 'truck'
49
+ ]
58
50
  )
59
51
  alert_config: Optional[AlertConfig] = None
60
52
  index_to_category: Optional[Dict[int, str]] = field(
61
53
  default_factory=lambda: {
62
- 0: "person", 1: "bicycle", 2: "car", 3: "motorcycle", 4: "airplane", 5: "bus",
63
- 6: "train", 7: "truck", 8: "boat", 9: "traffic light", 10: "fire hydrant",
64
- 11: "stop sign", 12: "parking meter", 13: "bench", 14: "bird", 15: "cat",
65
- 16: "dog", 17: "horse", 18: "sheep", 19: "cow", 20: "elephant", 21: "bear",
66
- 22: "zebra", 23: "giraffe", 24: "backpack", 25: "umbrella", 26: "handbag",
67
- 27: "tie", 28: "suitcase", 29: "frisbee", 30: "skis", 31: "snowboard",
68
- 32: "sports ball", 33: "kite", 34: "baseball bat", 35: "baseball glove",
69
- 36: "skateboard", 37: "surfboard", 38: "tennis racket", 39: "bottle",
70
- 40: "wine glass", 41: "cup", 42: "fork", 43: "knife", 44: "spoon", 45: "bowl",
71
- 46: "banana", 47: "apple", 48: "sandwich", 49: "orange", 50: "broccoli",
72
- 51: "carrot", 52: "hot dog", 53: "pizza", 54: "donut", 55: "cake", 56: "chair",
73
- 57: "couch", 58: "potted plant", 59: "bed", 60: "dining table", 61: "toilet",
74
- 62: "tv", 63: "laptop", 64: "mouse", 65: "remote", 66: "keyboard",
75
- 67: "cell phone", 68: "microwave", 69: "oven", 70: "toaster", 71: "sink",
76
- 72: "refrigerator", 73: "book", 74: "clock", 75: "vase", 76: "scissors",
77
- 77: "teddy bear", 78: "hair drier", 79: "toothbrush"
78
- }
54
+ 0: "bicycle",
55
+ 1: "motorcycle",
56
+ 2: "car",
57
+ 3: "van",
58
+ 4: "bus",
59
+ 5: "truck"
60
+ }
79
61
  )
80
62
 
81
63
  class VehicleMonitoringUseCase(BaseProcessor):
82
64
  CATEGORY_DISPLAY = {
83
65
  # Focus on vehicle-related COCO classes
84
66
  "bicycle": "Bicycle",
85
- "car": "Car",
86
67
  "motorcycle": "Motorcycle",
68
+ "car": "Car",
69
+ "van": "Van",
87
70
  "bus": "Bus",
88
71
  "truck": "Truck",
89
- "train": "Train",
90
- "boat": "Boat"
91
72
  }
92
73
 
93
74
  def __init__(self):
@@ -95,7 +76,7 @@ class VehicleMonitoringUseCase(BaseProcessor):
95
76
  self.category = "traffic"
96
77
  self.CASE_TYPE: Optional[str] = 'vehicle_monitoring'
97
78
  self.CASE_VERSION: Optional[str] = '1.0'
98
- self.target_categories = ['car', 'bicycle', 'bus', 'truck', 'motorcycle']
79
+ self.target_categories = ['bicycle', 'motorcycle', 'car', 'van', 'bus', 'truck' ]
99
80
  self.smoothing_tracker = None
100
81
  self.tracker = None
101
82
  self._total_frame_counter = 0
@@ -141,6 +122,7 @@ class VehicleMonitoringUseCase(BaseProcessor):
141
122
  context.input_format = input_format
142
123
  context.confidence_threshold = config.confidence_threshold
143
124
  config.confidence_threshold = 0.25
125
+ # param to be updated
144
126
 
145
127
  if config.confidence_threshold is not None:
146
128
  processed_data = filter_by_confidence(data, config.confidence_threshold)