matrice-analytics 0.1.89__py3-none-any.whl → 0.1.97__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 (26) hide show
  1. matrice_analytics/post_processing/__init__.py +21 -2
  2. matrice_analytics/post_processing/config.py +6 -0
  3. matrice_analytics/post_processing/core/config.py +102 -3
  4. matrice_analytics/post_processing/face_reg/face_recognition.py +146 -14
  5. matrice_analytics/post_processing/face_reg/face_recognition_client.py +116 -4
  6. matrice_analytics/post_processing/face_reg/people_activity_logging.py +19 -0
  7. matrice_analytics/post_processing/post_processor.py +12 -0
  8. matrice_analytics/post_processing/usecases/__init__.py +9 -0
  9. matrice_analytics/post_processing/usecases/advanced_customer_service.py +5 -2
  10. matrice_analytics/post_processing/usecases/color_detection.py +1 -0
  11. matrice_analytics/post_processing/usecases/fire_detection.py +94 -14
  12. matrice_analytics/post_processing/usecases/footfall.py +750 -0
  13. matrice_analytics/post_processing/usecases/license_plate_monitoring.py +91 -1
  14. matrice_analytics/post_processing/usecases/people_counting.py +55 -22
  15. matrice_analytics/post_processing/usecases/vehicle_monitoring.py +15 -32
  16. matrice_analytics/post_processing/usecases/vehicle_monitoring_drone_view.py +1007 -0
  17. matrice_analytics/post_processing/usecases/vehicle_monitoring_parking_lot.py +1011 -0
  18. matrice_analytics/post_processing/usecases/weapon_detection.py +2 -1
  19. matrice_analytics/post_processing/utils/alert_instance_utils.py +94 -26
  20. matrice_analytics/post_processing/utils/business_metrics_manager_utils.py +97 -4
  21. matrice_analytics/post_processing/utils/incident_manager_utils.py +103 -6
  22. {matrice_analytics-0.1.89.dist-info → matrice_analytics-0.1.97.dist-info}/METADATA +1 -1
  23. {matrice_analytics-0.1.89.dist-info → matrice_analytics-0.1.97.dist-info}/RECORD +26 -23
  24. {matrice_analytics-0.1.89.dist-info → matrice_analytics-0.1.97.dist-info}/WHEEL +0 -0
  25. {matrice_analytics-0.1.89.dist-info → matrice_analytics-0.1.97.dist-info}/licenses/LICENSE.txt +0 -0
  26. {matrice_analytics-0.1.89.dist-info → matrice_analytics-0.1.97.dist-info}/top_level.txt +0 -0
@@ -610,6 +610,68 @@ class LicensePlateMonitorUseCase(BaseProcessor):
610
610
  #self.logger.error(f"Error fetching external IP: {e}", exc_info=True)
611
611
  return "localhost"
612
612
 
613
+ def _fetch_location_name(self, location_id: str, session: Optional[Session] = None) -> str:
614
+ """
615
+ Fetch location name from API using location_id.
616
+
617
+ Args:
618
+ location_id: The location ID to look up
619
+ session: Matrice session for API calls
620
+
621
+ Returns:
622
+ Location name string, or 'Entry Reception' as default if API fails
623
+ """
624
+ default_location = "Entry Reception"
625
+
626
+ if not location_id:
627
+ self.logger.debug(f"[LOCATION] No location_id provided, using default: '{default_location}'")
628
+ return default_location
629
+
630
+ # Check cache first
631
+ if not hasattr(self, '_location_name_cache'):
632
+ self._location_name_cache: Dict[str, str] = {}
633
+
634
+ if location_id in self._location_name_cache:
635
+ cached_name = self._location_name_cache[location_id]
636
+ self.logger.debug(f"[LOCATION] Using cached location name for '{location_id}': '{cached_name}'")
637
+ return cached_name
638
+
639
+ if not session:
640
+ self.logger.warning(f"[LOCATION] No session provided, using default: '{default_location}'")
641
+ return default_location
642
+
643
+ try:
644
+ endpoint = f"/v1/inference/get_location/{location_id}"
645
+ self.logger.info(f"[LOCATION] Fetching location name from API: {endpoint}")
646
+
647
+ response = session.rpc.get(endpoint)
648
+
649
+ if response and isinstance(response, dict):
650
+ success = response.get("success", False)
651
+ if success:
652
+ data = response.get("data", {})
653
+ location_name = data.get("locationName", default_location)
654
+ self.logger.info(f"[LOCATION] ✓ Fetched location name: '{location_name}' for location_id: '{location_id}'")
655
+
656
+ # Cache the result
657
+ self._location_name_cache[location_id] = location_name
658
+ return location_name
659
+ else:
660
+ self.logger.warning(
661
+ f"[LOCATION] API returned success=false for location_id '{location_id}': "
662
+ f"{response.get('message', 'Unknown error')}"
663
+ )
664
+ else:
665
+ self.logger.warning(f"[LOCATION] Invalid response format from API: {response}")
666
+
667
+ except Exception as e:
668
+ self.logger.error(f"[LOCATION] Error fetching location name for '{location_id}': {e}", exc_info=True)
669
+
670
+ # Use default on any failure
671
+ self.logger.info(f"[LOCATION] Using default location name: '{default_location}'")
672
+ self._location_name_cache[location_id] = default_location
673
+ return default_location
674
+
613
675
  def _initialize_alert_manager_once(self, config: LicensePlateMonitorConfig) -> None:
614
676
  """
615
677
  Initialize alert manager ONCE with Redis OR Kafka clients (Environment based).
@@ -839,13 +901,18 @@ class LicensePlateMonitorUseCase(BaseProcessor):
839
901
 
840
902
  # Create alert manager if client is available
841
903
  if redis_client or kafka_client:
904
+ # Get app_deployment_id from action_details for filtering alerts
905
+ app_deployment_id_for_alert = getattr(self, '_app_deployment_id', None)
906
+ self.logger.info(f"[ALERT] Using app_deployment_id for alert filtering: {app_deployment_id_for_alert}")
907
+
842
908
  self.alert_manager = ALERT_INSTANCE(
843
909
  redis_client=redis_client,
844
910
  kafka_client=kafka_client,
845
911
  config_topic="alert_instant_config_request",
846
912
  trigger_topic="alert_instant_triggered",
847
913
  polling_interval=10, # Poll every 10 seconds
848
- logger=self.logger
914
+ logger=self.logger,
915
+ app_deployment_id=app_deployment_id_for_alert
849
916
  )
850
917
  self.alert_manager.start()
851
918
  transport = "Redis" if redis_client else "Kafka"
@@ -918,6 +985,8 @@ class LicensePlateMonitorUseCase(BaseProcessor):
918
985
  app_deployment_id = ""
919
986
  application_id = ""
920
987
  camera_name = ""
988
+ frame_id = ""
989
+ location_name = ""
921
990
 
922
991
  if stream_info:
923
992
  self.logger.debug(f"[ALERT_DEBUG] stream_info keys: {list(stream_info.keys())}")
@@ -933,13 +1002,31 @@ class LicensePlateMonitorUseCase(BaseProcessor):
933
1002
  app_deployment_id = stream_info.get("app_deployment_id", "")
934
1003
  application_id = stream_info.get("application_id", stream_info.get("app_id", ""))
935
1004
 
1005
+ # Extract frame_id - it's at root level of stream_info
1006
+ frame_id = stream_info.get("frame_id", "")
1007
+
1008
+ # Extract location_id and fetch location_name from API
1009
+ location_id = ""
1010
+ if "camera_info" in stream_info:
1011
+ location_id = stream_info.get("camera_info", {}).get("location", "")
1012
+
1013
+ if location_id:
1014
+ # Fetch location name from API
1015
+ location_name = self._fetch_location_name(location_id, config.session)
1016
+ else:
1017
+ location_name = "Entry Reception" # Default if no location_id
1018
+
936
1019
  self.logger.debug(f"[ALERT_DEBUG] Extracted metadata from stream_info:")
937
1020
  self.logger.debug(f"[ALERT_DEBUG] - camera_id: '{camera_id}'")
938
1021
  self.logger.debug(f"[ALERT_DEBUG] - camera_name: '{camera_name}'")
939
1022
  self.logger.debug(f"[ALERT_DEBUG] - app_deployment_id: '{app_deployment_id}'")
940
1023
  self.logger.debug(f"[ALERT_DEBUG] - application_id: '{application_id}'")
1024
+ self.logger.debug(f"[ALERT_DEBUG] - frame_id: '{frame_id}'")
1025
+ self.logger.debug(f"[ALERT_DEBUG] - location_id: '{location_id}'")
1026
+ self.logger.debug(f"[ALERT_DEBUG] - location_name: '{location_name}'")
941
1027
  else:
942
1028
  self.logger.warning("[ALERT_DEBUG] stream_info is None")
1029
+ location_name = "Entry Reception" # Default
943
1030
 
944
1031
  # Process each detection with a valid plate_text
945
1032
  sent_count = 0
@@ -1007,6 +1094,8 @@ class LicensePlateMonitorUseCase(BaseProcessor):
1007
1094
  "frameUrl": "", # Will be filled by analytics publisher if needed
1008
1095
  "coordinates": coordinates,
1009
1096
  "cameraName": camera_name,
1097
+ "locationName": location_name,
1098
+ "frame_id": frame_id,
1010
1099
  "vehicleType": detection.get('vehicle_type', ''),
1011
1100
  "vehicleColor": detection.get('vehicle_color', ''),
1012
1101
  "timestamp": datetime.now(timezone.utc).isoformat()
@@ -1752,6 +1841,7 @@ class LicensePlateMonitorUseCase(BaseProcessor):
1752
1841
  start_time=high_precision_start_timestamp,
1753
1842
  reset_time=high_precision_reset_timestamp
1754
1843
  )
1844
+ tracking_stat['target_categories'] = self.target_categories
1755
1845
  tracking_stats.append(tracking_stat)
1756
1846
  return tracking_stats
1757
1847
 
@@ -36,7 +36,7 @@ class PeopleCountingUseCase(BaseProcessor):
36
36
  self.category = "general"
37
37
  self.CASE_TYPE: Optional[str] = 'people_counting'
38
38
  self.CASE_VERSION: Optional[str] = '1.4'
39
- self.target_categories = ['person', 'people','human','man','woman','male','female']
39
+ self.target_categories = ['person'] #['person', 'people','human','man','woman','male','female']
40
40
  self.smoothing_tracker = None
41
41
  self.tracker = None
42
42
  self._total_frame_counter = 0
@@ -50,6 +50,19 @@ class PeopleCountingUseCase(BaseProcessor):
50
50
  self.current_incident_end_timestamp: str = "N/A"
51
51
  self.start_timer = None
52
52
 
53
+ def _simple_tracker_update(self, detections: list) -> list:
54
+ """
55
+ ====== PERFORMANCE: Lightweight tracker alternative ======
56
+ Simple tracker using frame-local indexing.
57
+ Much faster than AdvancedTracker - O(n) complexity.
58
+ Does not persist track IDs across frames.
59
+ Enable via config.enable_simple_tracker = True
60
+ """
61
+ for i, det in enumerate(detections):
62
+ if det.get('track_id') is None:
63
+ det['track_id'] = f"simple_{self._total_frame_counter}_{i}"
64
+ return detections
65
+
53
66
  def process(self, data: Any, config: ConfigProtocol, context: Optional[ProcessingContext] = None,
54
67
  stream_info: Optional[Dict[str, Any]] = None) -> ProcessingResult:
55
68
  processing_start = time.time()
@@ -90,20 +103,29 @@ class PeopleCountingUseCase(BaseProcessor):
90
103
  # self.smoothing_tracker = BBoxSmoothingTracker(smoothing_config)
91
104
  # processed_data = bbox_smoothing(processed_data, self.smoothing_tracker.config, self.smoothing_tracker)
92
105
 
93
- try:
94
- from ..advanced_tracker import AdvancedTracker
95
- from ..advanced_tracker.config import TrackerConfig
96
- if self.tracker is None:
97
- tracker_config = TrackerConfig(
98
- track_high_thresh=0.4,
99
- track_low_thresh=0.05,
100
- new_track_thresh=0.3,
101
- match_thresh=0.8)
102
- self.tracker = AdvancedTracker(tracker_config)
103
- self.logger.info("Initialized AdvancedTracker for People Counting")
104
- processed_data = self.tracker.update(processed_data)
105
- except Exception as e:
106
- self.logger.warning(f"AdvancedTracker failed: {e}")
106
+ # ====== TRACKER SELECTION (both disabled by default for max performance) ======
107
+ # Set config.enable_advanced_tracker=True or config.enable_simple_tracker=True to enable
108
+ if getattr(config, 'enable_advanced_tracker', False):
109
+ # Heavy O(n³) tracker - use only when tracking quality is critical
110
+ try:
111
+ from ..advanced_tracker import AdvancedTracker
112
+ from ..advanced_tracker.config import TrackerConfig
113
+ if self.tracker is None:
114
+ tracker_config = TrackerConfig(
115
+ track_high_thresh=0.4,
116
+ track_low_thresh=0.05,
117
+ new_track_thresh=0.3,
118
+ match_thresh=0.8)
119
+ self.tracker = AdvancedTracker(tracker_config)
120
+ self.logger.info("Initialized AdvancedTracker for People Counting")
121
+ processed_data = self.tracker.update(processed_data)
122
+ except Exception as e:
123
+ self.logger.warning(f"AdvancedTracker failed: {e}")
124
+ elif getattr(config, 'enable_simple_tracker', False):
125
+ # Lightweight O(n) tracker - fast but no cross-frame persistence
126
+ processed_data = self._simple_tracker_update(processed_data)
127
+ # else: No tracking - maximum performance, just use raw detections
128
+ # ====== END TRACKER SELECTION ======
107
129
 
108
130
  self._update_tracking_state(processed_data)
109
131
  self._total_frame_counter += 1
@@ -362,6 +384,7 @@ class PeopleCountingUseCase(BaseProcessor):
362
384
  start_time=high_precision_start_timestamp,
363
385
  reset_time=high_precision_reset_timestamp
364
386
  )
387
+ tracking_stat['target_categories'] = self.target_categories
365
388
  tracking_stats.append(tracking_stat)
366
389
  return tracking_stats
367
390
 
@@ -414,14 +437,24 @@ class PeopleCountingUseCase(BaseProcessor):
414
437
 
415
438
  for det in detections:
416
439
  cat = det.get("category")
417
- raw_track_id = det.get("track_id")
418
- if cat not in self.target_categories or raw_track_id is None:
440
+ track_id = det.get("track_id")
441
+ if cat not in self.target_categories:
419
442
  continue
420
- bbox = det.get("bounding_box", det.get("bbox"))
421
- canonical_id = self._merge_or_register_track(raw_track_id, bbox)
422
- det["track_id"] = canonical_id
423
- self._per_category_total_track_ids.setdefault(cat, set()).add(canonical_id)
424
- self._current_frame_track_ids[cat].add(canonical_id)
443
+
444
+ # ====== PERFORMANCE: Skip heavy track merging O(n*m) ======
445
+ # To enable track merging, uncomment below and comment the simple counting section
446
+ # bbox = det.get("bounding_box", det.get("bbox"))
447
+ # canonical_id = self._merge_or_register_track(track_id, bbox)
448
+ # det["track_id"] = canonical_id
449
+ # self._per_category_total_track_ids.setdefault(cat, set()).add(canonical_id)
450
+ # self._current_frame_track_ids[cat].add(canonical_id)
451
+ # ====== END HEAVY TRACK MERGING ======
452
+
453
+ # ====== SIMPLE COUNTING (default - no track merging overhead) ======
454
+ if track_id is not None:
455
+ self._per_category_total_track_ids.setdefault(cat, set()).add(track_id)
456
+ self._current_frame_track_ids[cat].add(track_id)
457
+ # ====== END SIMPLE COUNTING ======
425
458
 
426
459
  def get_total_counts(self):
427
460
  return {cat: len(ids) for cat, ids in getattr(self, '_per_category_total_track_ids', {}).items()}
@@ -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)
@@ -683,6 +665,7 @@ class VehicleMonitoringUseCase(BaseProcessor):
683
665
  start_time=high_precision_start_timestamp,
684
666
  reset_time=high_precision_reset_timestamp
685
667
  )
668
+ tracking_stat['target_categories'] = self.target_categories
686
669
  tracking_stats.append(tracking_stat)
687
670
  return tracking_stats
688
671