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
@@ -85,7 +85,7 @@ from .usecases.leaf_disease import LeafDiseaseDetectionConfig, LeafDiseaseDetect
85
85
  from .usecases.parking import ParkingConfig
86
86
  from .usecases.abandoned_object_detection import AbandonedObjectConfig
87
87
  from .usecases.footfall import FootFallConfig
88
-
88
+ from .usecases.vehicle_monitoring import VehicleMonitoringConfig
89
89
 
90
90
  from .usecases.weld_defect_detection import WeldDefectConfig
91
91
  from .usecases.weapon_detection import WeaponDetectionConfig
@@ -130,6 +130,8 @@ from .usecases.underground_pipeline_defect_detection import UndergroundPipelineD
130
130
  from .usecases.suspicious_activity_detection import SusActivityConfig, SusActivityUseCase
131
131
  from .usecases.natural_disaster import NaturalDisasterConfig, NaturalDisasterUseCase
132
132
  from .usecases.footfall import FootFallUseCase
133
+ from .usecases.vehicle_monitoring_parking_lot import VehicleMonitoringParkingLotUseCase, VehicleMonitoringParkingLotConfig
134
+ from .usecases.vehicle_monitoring_drone_view import VehicleMonitoringDroneViewUseCase, VehicleMonitoringDroneViewConfig
133
135
 
134
136
  #Put all IMAGE based usecases here
135
137
  from .usecases.blood_cancer_detection_img import BloodCancerDetectionConfig, BloodCancerDetectionUseCase
@@ -208,6 +210,9 @@ from .usecases import (
208
210
  SusActivityUseCase,
209
211
  NaturalDisasterUseCase,
210
212
  FootFallUseCase,
213
+ VehicleMonitoringParkingLotUseCase,
214
+ VehicleMonitoringDroneViewUseCase,
215
+
211
216
  #Put all IMAGE based usecases here
212
217
  BloodCancerDetectionUseCase,
213
218
  SkinCancerClassificationUseCase,
@@ -289,6 +294,8 @@ _underground_pipeline_defect = UndergroundPipelineDefectUseCase()
289
294
  _suspicious_activity_detection = SusActivityUseCase()
290
295
  _natural_disaster = NaturalDisasterUseCase()
291
296
  _footfall = FootFallUseCase()
297
+ _vehicle_monitoring_parking_lot = VehicleMonitoringParkingLotUseCase()
298
+ _vehicle_monitoring_drone_view = VehicleMonitoringDroneViewUseCase()
292
299
 
293
300
  # Face recognition with embeddings
294
301
  _face_recognition = FaceRecognitionEmbeddingUseCase()
@@ -374,6 +381,8 @@ registry.register_use_case(_underground_pipeline_defect.category, _underground_p
374
381
  registry.register_use_case(_suspicious_activity_detection.category, _suspicious_activity_detection.name, SusActivityUseCase)
375
382
  registry.register_use_case(_natural_disaster.category, _natural_disaster.name, NaturalDisasterUseCase)
376
383
  registry.register_use_case(_footfall.category, _footfall.name, FaceEmotionUseCase)
384
+ registry.register_use_case(_vehicle_monitoring_parking_lot.category, _vehicle_monitoring_parking_lot.name, VehicleMonitoringParkingLotUseCase)
385
+ registry.register_use_case(_vehicle_monitoring_drone_view.category, _vehicle_monitoring_drone_view.name, VehicleMonitoringDroneViewUseCase)
377
386
 
378
387
  #Put all IMAGE based usecases here
379
388
  registry.register_use_case(_blood_cancer_detection.category, _blood_cancer_detection.name, BloodCancerDetectionUseCase)
@@ -580,6 +589,8 @@ __all__ = [
580
589
  'NaturalDisasterConfig',
581
590
  'VehiclePeopleDroneMonitoringConfig',
582
591
  'FootFallConfig',
592
+ 'VehicleMonitoringParkingLotConfig',
593
+ 'VehicleMonitoringDroneViewConfig',
583
594
  #Put all IMAGE based usecase CONFIGS here
584
595
  'BloodCancerDetectionConfig',
585
596
  'SkinCancerClassificationConfig',
@@ -654,6 +665,8 @@ __all__ = [
654
665
  'SusActivityUseCase',
655
666
  'NaturalDisasterUseCase',
656
667
  'FootFallUseCase',
668
+ 'VehicleMonitoringParkingLotUseCase',
669
+ 'VehicleMonitoringDroneViewUseCase',
657
670
 
658
671
  #Put all IMAGE based usecases here
659
672
  'BloodCancerDetectionUseCase',
@@ -1,6 +1,5 @@
1
1
  """
2
2
  Configuration classes for advanced tracker.
3
-
4
3
  This module provides configuration classes for the advanced tracker,
5
4
  including parameters for tracking algorithms and thresholds.
6
5
  """
@@ -8,7 +7,6 @@ including parameters for tracking algorithms and thresholds.
8
7
  from dataclasses import dataclass, field
9
8
  from typing import Optional
10
9
 
11
-
12
10
  @dataclass
13
11
  class TrackerConfig:
14
12
  """
@@ -46,6 +44,10 @@ class TrackerConfig:
46
44
  smoothing_window_size: int = 20
47
45
  smoothing_cooldown_frames: int = 5
48
46
 
47
+ # Class aggregation settings
48
+ enable_class_aggregation: bool = False
49
+ class_aggregation_window_size: int = 30
50
+
49
51
  def __post_init__(self):
50
52
  """Validate configuration parameters."""
51
53
  if not 0.0 <= self.track_high_thresh <= 1.0:
@@ -72,6 +74,8 @@ class TrackerConfig:
72
74
  if self.output_format not in ["tracking", "detection"]:
73
75
  raise ValueError(f"Invalid output_format: {self.output_format}")
74
76
 
75
- # Calculate max_time_lost if not explicitly set
76
77
  if self.max_time_lost == 30: # Default value
77
- self.max_time_lost = int(self.frame_rate / 30.0 * self.track_buffer)
78
+ self.max_time_lost = int(self.frame_rate / 30.0 * self.track_buffer)
79
+
80
+ if self.class_aggregation_window_size <= 0:
81
+ raise ValueError(f"class_aggregation_window_size must be positive, got {self.class_aggregation_window_size}")
@@ -0,0 +1,128 @@
1
+ """
2
+ Track class aggregation for object tracking.
3
+
4
+ This module provides a sliding-window-based mechanism to aggregate class labels
5
+ across frames, reducing label flickering in tracking outputs through temporal voting.
6
+ """
7
+
8
+ from typing import Any, Dict
9
+ from collections import deque, Counter
10
+ import logging
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ class TrackClassAggregator:
16
+ """
17
+ Maintains per-track sliding windows of class labels and returns the most frequent.
18
+
19
+ This aggregator reduces class label flickering in tracking results by applying
20
+ temporal voting based on historical observations within a sliding window.
21
+
22
+ Attributes:
23
+ window_size (int): Maximum number of frames to keep in the sliding window.
24
+ track_windows (Dict[int, deque]): Per-track sliding windows of class labels.
25
+ """
26
+
27
+ def __init__(self, window_size: int = 30):
28
+ """
29
+ Initialize the TrackClassAggregator.
30
+
31
+ Args:
32
+ window_size (int): Number of recent frames to consider for aggregation.
33
+ Must be positive. Larger windows provide more stability but slower
34
+ adaptation to genuine class changes.
35
+ """
36
+ if window_size <= 0:
37
+ raise ValueError(f"window_size must be positive, got {window_size}")
38
+
39
+ self.window_size = window_size
40
+ self.track_windows: Dict[int, deque] = {}
41
+
42
+ def update_and_aggregate(self, track_id: int, observed_class: Any) -> Any:
43
+ """
44
+ Update the sliding window for a track and return the aggregated class label.
45
+
46
+ This method:
47
+ 1. Adds the new observation to the track's window
48
+ 2. Maintains window size by removing oldest entries if needed
49
+ 3. Returns the most frequent class in the window
50
+
51
+ Args:
52
+ track_id (int): Unique identifier for the track.
53
+ observed_class (Any): The class label observed in the current frame.
54
+
55
+ Returns:
56
+ Any: The aggregated class label (most frequent in the window).
57
+ If there's a tie, returns the most recent among tied classes.
58
+ """
59
+ # Initialize window for new tracks
60
+ if track_id not in self.track_windows:
61
+ self.track_windows[track_id] = deque(maxlen=self.window_size)
62
+
63
+ # Add current observation
64
+ window = self.track_windows[track_id]
65
+ window.append(observed_class)
66
+
67
+ # Return most frequent class
68
+ if len(window) == 0:
69
+ return observed_class
70
+
71
+ # Count frequencies and return most common
72
+ class_counts = Counter(window)
73
+ most_common = class_counts.most_common(1)[0][0]
74
+
75
+ return most_common
76
+
77
+ def get_aggregated_class(self, track_id: int, fallback_class: Any) -> Any:
78
+ """
79
+ Get the aggregated class for a track without updating the window.
80
+
81
+ Args:
82
+ track_id (int): Unique identifier for the track.
83
+ fallback_class (Any): Class to return if track has no history.
84
+
85
+ Returns:
86
+ Any: The aggregated class label, or fallback_class if no history exists.
87
+ """
88
+ if track_id not in self.track_windows:
89
+ return fallback_class
90
+
91
+ window = self.track_windows[track_id]
92
+ if len(window) == 0:
93
+ return fallback_class
94
+
95
+ class_counts = Counter(window)
96
+ return class_counts.most_common(1)[0][0]
97
+
98
+ def remove_track(self, track_id: int) -> None:
99
+ """
100
+ Remove a track's window from memory.
101
+
102
+ Args:
103
+ track_id (int): Unique identifier for the track to remove.
104
+ """
105
+ if track_id in self.track_windows:
106
+ del self.track_windows[track_id]
107
+
108
+ def remove_tracks(self, track_ids: list) -> None:
109
+ """
110
+ Remove multiple tracks' windows from memory (batch operation).
111
+
112
+ Args:
113
+ track_ids (list): List of track IDs to remove.
114
+ """
115
+ for track_id in track_ids:
116
+ self.remove_track(track_id)
117
+
118
+ def reset(self) -> None:
119
+ """Clear all track windows."""
120
+ self.track_windows.clear()
121
+
122
+ def get_active_track_count(self) -> int:
123
+ """Get the number of tracks currently being aggregated."""
124
+ return len(self.track_windows)
125
+
126
+ def __repr__(self) -> str:
127
+ """String representation for debugging."""
128
+ return f"TrackClassAggregator(window_size={self.window_size}, active_tracks={len(self.track_windows)})"
@@ -34,6 +34,7 @@ class AdvancedTracker:
34
34
  config (TrackerConfig): Tracker configuration.
35
35
  max_time_lost (int): The maximum frames for a track to be considered as 'lost'.
36
36
  kalman_filter (KalmanFilterXYAH): Kalman Filter object.
37
+ class_smoother (Optional[ClassSmoother]): Optional class smoother for class label smoothing over flicker.
37
38
  """
38
39
 
39
40
  def __init__(self, config: TrackerConfig):
@@ -53,6 +54,11 @@ class AdvancedTracker:
53
54
  self.kalman_filter = self.get_kalmanfilter()
54
55
  self.reset_id()
55
56
 
57
+ self.class_aggregator = None
58
+ if config.enable_class_aggregation:
59
+ from .track_class_aggregator import TrackClassAggregator
60
+ self.class_aggregator = TrackClassAggregator(window_size=config.class_aggregation_window_size)
61
+
56
62
  def update(self, detections: Union[List[Dict], Dict[str, List[Dict]]],
57
63
  img: Optional[np.ndarray] = None) -> Union[List[Dict], Dict[str, List[Dict]]]:
58
64
  """
@@ -186,7 +192,15 @@ class AdvancedTracker:
186
192
  }
187
193
 
188
194
  detections.append(detection)
189
-
195
+
196
+ if self.class_aggregator is not None:
197
+ for detection in detections:
198
+ aggregated_class = self.class_aggregator.update_and_aggregate(
199
+ track_id=detection['track_id'],
200
+ observed_class=detection['category']
201
+ )
202
+ detection['category'] = aggregated_class
203
+
190
204
  return detections
191
205
 
192
206
  def _perform_tracking_update(self, detections: List[STrack],
@@ -296,6 +310,10 @@ class AdvancedTracker:
296
310
 
297
311
  if len(self.removed_stracks) > 1000:
298
312
  self.removed_stracks = self.removed_stracks[-999:]
313
+
314
+ # Clean up aggregator windows for removed tracks
315
+ if self.class_aggregator is not None and removed_stracks:
316
+ self.class_aggregator.remove_tracks([t.track_id for t in removed_stracks])
299
317
 
300
318
  return [x for x in self.tracked_stracks if x.is_activated]
301
319
 
@@ -327,6 +345,9 @@ class AdvancedTracker:
327
345
  self.frame_id = 0
328
346
  self.kalman_filter = self.get_kalmanfilter()
329
347
  self.reset_id()
348
+
349
+ if self.class_aggregator is not None:
350
+ self.class_aggregator.reset()
330
351
 
331
352
  @staticmethod
332
353
  def joint_stracks(tlista: List[STrack], tlistb: List[STrack]) -> List[STrack]:
@@ -65,7 +65,9 @@ APP_NAME_TO_USECASE = {
65
65
  "underground_pipeline_defect" : "underground_pipeline_defect",
66
66
  "suspicious_activity_detection": "suspicious_activity_detection",
67
67
  "natural_disaster_detection": "natural_disaster_detection",
68
- "Foot Fall": "footfall"
68
+ "Foot Fall": "footfall",
69
+ "Parking Lot Vehicle Monitoring": "vehicle_monitoring_parking_lot",
70
+ "Drone view vehicle monitoring": "vehicle_monitoring_drone_view",
69
71
  }
70
72
 
71
73
  APP_NAME_TO_CATEGORY = {
@@ -136,7 +138,9 @@ APP_NAME_TO_CATEGORY = {
136
138
  "underground_pipeline_defect" : "general",
137
139
  "suspicious_activity_detection": "security",
138
140
  "natural_disaster_detection": "environmental",
139
- "Foot Fall": "retail"
141
+ "Foot Fall": "retail",
142
+ "Parking Lot Vehicle Monitoring": "traffic",
143
+ "Drone view vehicle monitoring": "traffic",
140
144
  }
141
145
 
142
146
  def get_usecase_from_app_name(app_name: str) -> str:
@@ -907,6 +907,8 @@ class ConfigManager:
907
907
  'suspicious_activity_detection': None,
908
908
  'natural_disaster_detection': None,
909
909
  'footfall': None,
910
+ 'vehicle_monitoring_parking_lot': None,
911
+ 'vehicle_monitoring_drone_view': None,
910
912
 
911
913
  #Put all image based usecases here::
912
914
  'blood_cancer_detection_img': None,
@@ -1420,6 +1422,22 @@ class ConfigManager:
1420
1422
  except ImportError:
1421
1423
  return None
1422
1424
 
1425
+ def vehicle_monitoring_parking_lot_config_class(self):
1426
+ """Register a configuration class for a use case."""
1427
+ try:
1428
+ from ..usecases.vehicle_monitoring_parking_lot import VehicleMonitoringParkingLotConfig
1429
+ return VehicleMonitoringParkingLotConfig
1430
+ except ImportError:
1431
+ return None
1432
+
1433
+ def vehicle_monitoring_drone_view_config_class(self):
1434
+ """Register a configuration class for a use case."""
1435
+ try:
1436
+ from ..usecases.vehicle_monitoring_drone_view import VehicleMonitoringDroneViewConfig
1437
+ return VehicleMonitoringDroneViewConfig
1438
+ except ImportError:
1439
+ return None
1440
+
1423
1441
  #put all image based usecases here::
1424
1442
  def blood_cancer_detection_config_class(self):
1425
1443
  """Register a configuration class for a use case."""
@@ -2713,6 +2731,38 @@ class ConfigManager:
2713
2731
  alert_config=alert_config,
2714
2732
  **kwargs
2715
2733
  )
2734
+
2735
+ elif usecase == "vehicle_monitoring_parking_lot":
2736
+ # Import here to avoid circular import
2737
+ from ..usecases.vehicle_monitoring_parking_lot import VehicleMonitoringParkingLotConfig
2738
+
2739
+ # Handle nested configurations
2740
+ alert_config = kwargs.pop("alert_config", None)
2741
+ if alert_config and isinstance(alert_config, dict):
2742
+ alert_config = AlertConfig(**alert_config)
2743
+
2744
+ config = VehicleMonitoringParkingLotConfig(
2745
+ category=category or "traffic",
2746
+ usecase=usecase,
2747
+ alert_config=alert_config,
2748
+ **kwargs
2749
+ )
2750
+
2751
+ elif usecase == "vehicle_monitoring_drone_view":
2752
+ # Import here to avoid circular import
2753
+ from ..usecases.vehicle_monitoring_drone_view import VehicleMonitoringDroneViewConfig
2754
+
2755
+ # Handle nested configurations
2756
+ alert_config = kwargs.pop("alert_config", None)
2757
+ if alert_config and isinstance(alert_config, dict):
2758
+ alert_config = AlertConfig(**alert_config)
2759
+
2760
+ config = VehicleMonitoringDroneViewConfig(
2761
+ category=category or "traffic",
2762
+ usecase=usecase,
2763
+ alert_config=alert_config,
2764
+ **kwargs
2765
+ )
2716
2766
 
2717
2767
  #Add IMAGE based usecases here::
2718
2768
  elif usecase == "blood_cancer_detection_img":
@@ -3271,6 +3321,18 @@ class ConfigManager:
3271
3321
  default_config = FootFallConfig()
3272
3322
  return default_config.to_dict()
3273
3323
 
3324
+ elif usecase == "vehicle_monitoring_parking_lot":
3325
+ # Import here to avoid circular import
3326
+ from ..usecases.vehicle_monitoring_parking_lot import VehicleMonitoringParkingLotConfig
3327
+ default_config = VehicleMonitoringParkingLotConfig()
3328
+ return default_config.to_dict()
3329
+
3330
+ elif usecase == "vehicle_monitoring_drone_view":
3331
+ # Import here to avoid circular import
3332
+ from ..usecases.vehicle_monitoring_drone_view import VehicleMonitoringDroneViewConfig
3333
+ default_config = VehicleMonitoringDroneViewConfig()
3334
+ return default_config.to_dict()
3335
+
3274
3336
 
3275
3337
  elif usecase == "underground_pipeline_defect":
3276
3338
  # Import here to avoid circular import