matrice-analytics 0.1.89__py3-none-any.whl → 0.1.96__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 +8 -2
  2. matrice_analytics/post_processing/config.py +2 -0
  3. matrice_analytics/post_processing/core/config.py +40 -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 +4 -0
  8. matrice_analytics/post_processing/usecases/__init__.py +4 -1
  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 +1 -0
  16. matrice_analytics/post_processing/usecases/weapon_detection.py +2 -1
  17. matrice_analytics/post_processing/utils/alert_instance_utils.py +94 -26
  18. matrice_analytics/post_processing/utils/business_metrics_manager_utils.py +97 -4
  19. matrice_analytics/post_processing/utils/incident_manager_utils.py +103 -6
  20. {matrice_analytics-0.1.89.dist-info → matrice_analytics-0.1.96.dist-info}/METADATA +1 -1
  21. {matrice_analytics-0.1.89.dist-info → matrice_analytics-0.1.96.dist-info}/RECORD +24 -23
  22. {matrice_analytics-0.1.89.dist-info → matrice_analytics-0.1.96.dist-info}/WHEEL +0 -0
  23. {matrice_analytics-0.1.89.dist-info → matrice_analytics-0.1.96.dist-info}/licenses/LICENSE.txt +0 -0
  24. {matrice_analytics-0.1.89.dist-info → matrice_analytics-0.1.96.dist-info}/top_level.txt +0 -0
@@ -99,6 +99,7 @@ from .usecases import (
99
99
  UndergroundPipelineDefectUseCase,
100
100
  SusActivityUseCase,
101
101
  NaturalDisasterUseCase,
102
+ FootFallUseCase,
102
103
  # Put all IMAGE based usecases here
103
104
  BloodCancerDetectionUseCase,
104
105
  SkinCancerClassificationUseCase,
@@ -569,6 +570,9 @@ class PostProcessor:
569
570
  registry.register_use_case(
570
571
  "environmental", "natural_disaster_detection", NaturalDisasterUseCase
571
572
  )
573
+ registry.register_use_case(
574
+ "retail", "footfall", FootFallUseCase
575
+ )
572
576
 
573
577
  # Put all IMAGE based usecases here
574
578
  registry.register_use_case(
@@ -85,6 +85,8 @@ from .pcb_defect_detection import PCBDefectConfig, PCBDefectUseCase
85
85
  from .underground_pipeline_defect_detection import UndergroundPipelineDefectConfig,UndergroundPipelineDefectUseCase
86
86
  from .suspicious_activity_detection import SusActivityConfig, SusActivityUseCase
87
87
  from .natural_disaster import NaturalDisasterConfig, NaturalDisasterUseCase
88
+ from .footfall import FootFallConfig, FootFallUseCase
89
+
88
90
 
89
91
  #Put all IMAGE based usecases here
90
92
  from .blood_cancer_detection_img import BloodCancerDetectionConfig, BloodCancerDetectionUseCase
@@ -172,6 +174,7 @@ __all__ = [
172
174
  'UndergroundPipelineDefectUseCase',
173
175
  'SusActivityUseCase',
174
176
  'NaturalDisasterUseCase',
177
+ 'FootFallUseCase',
175
178
 
176
179
  #Put all IMAGE based usecases here
177
180
  'BloodCancerDetectionUseCase',
@@ -254,7 +257,7 @@ __all__ = [
254
257
  'PCBDefectConfig',
255
258
  'SusActivityConfig',
256
259
  'NaturalDisasterConfig',
257
-
260
+ 'FootFallConfig',
258
261
  #Put all IMAGE based usecase CONFIGS here
259
262
  'BloodCancerDetectionConfig',
260
263
  'SkinCancerClassificationConfig',
@@ -74,6 +74,8 @@ class AdvancedCustomerServiceUseCase(BaseProcessor):
74
74
  """Initialize advanced customer service use case."""
75
75
  super().__init__("advanced_customer_service")
76
76
  self.category = "sales"
77
+ self.CASE_TYPE: Optional[str] = 'advanced_customer_service'
78
+ self.CASE_VERSION: Optional[str] = '1.3'
77
79
 
78
80
  # Advanced tracking structures
79
81
  self.customer_occupancy = {}
@@ -496,7 +498,7 @@ class AdvancedCustomerServiceUseCase(BaseProcessor):
496
498
  # Calculate current_counts (frame-wise counts)
497
499
  current_counts = [
498
500
  {"category": "staff", "count": staff_analytics.get("active_staff", 0)},
499
- {"category": "customer", "count": queue_analytics.get("active_customers", 0)}
501
+ {"category": "Active Customers", "count": queue_analytics.get("active_customers", 0)}
500
502
  ]
501
503
  # Detections: include all detections for this frame
502
504
  detection_objs = []
@@ -529,7 +531,8 @@ class AdvancedCustomerServiceUseCase(BaseProcessor):
529
531
  "alerts": alerts,
530
532
  "alert_settings": alert_settings,
531
533
  "reset_settings": reset_settings,
532
- "human_text": human_text
534
+ "human_text": human_text,
535
+ "target_categories": ['Staff', 'Active Customers']
533
536
  }
534
537
  # Patch: Build real_time_occupancy with correct service_areas info (not just empty lists)
535
538
  real_time_occupancy = analytics_results.get("real_time_occupancy", {}).copy()
@@ -1202,6 +1202,7 @@ class ColorDetectionUseCase(BaseProcessor):
1202
1202
  detections=detections, human_text=human_text, camera_info=camera_info, alerts=alerts, alert_settings=alert_settings,
1203
1203
  reset_settings=reset_settings, start_time=high_precision_start_timestamp ,
1204
1204
  reset_time=high_precision_reset_timestamp)
1205
+ tracking_stat['target_categories'] = self.target_categories
1205
1206
 
1206
1207
  tracking_stats.append(tracking_stat)
1207
1208
  return tracking_stats
@@ -107,7 +107,7 @@ class FireSmokeUseCase(BaseProcessor):
107
107
 
108
108
  self.smoothing_tracker = None # Required for bbox smoothing
109
109
  self._fire_smoke_recent_history = []
110
- self.target_categories=['fire']
110
+ self.target_categories=['fire', 'smoke'] # Lowercase to match filtering logic at line 276
111
111
 
112
112
  self._ascending_alert_list: List[str] = []
113
113
  self.current_incident_end_timestamp: str = "N/A"
@@ -538,9 +538,21 @@ class FireSmokeUseCase(BaseProcessor):
538
538
  count_thresholds = {}
539
539
  if config.alert_config and hasattr(config.alert_config, "count_thresholds"):
540
540
  count_thresholds = config.alert_config.count_thresholds or {}
541
+
542
+ # CRITICAL FIX: Ensure we have at least one category to process
543
+ # If count_thresholds is empty, use detected categories from per_category_count
544
+ # This ensures incidents are always generated when detections exist
545
+ per_category_count = summary.get("per_category_count", {})
546
+ if not count_thresholds and per_category_count:
547
+ # Create thresholds for all detected categories with threshold=0 (always trigger)
548
+ count_thresholds = {cat: 0 for cat in per_category_count.keys()}
549
+ self.logger.debug(f"[INCIDENT] count_thresholds was empty, using detected categories: {count_thresholds}")
550
+
551
+ # Flag to track if we generated any incident
552
+ incident_generated = False
541
553
 
542
554
  for category, threshold in count_thresholds.items():
543
- if category in summary.get("per_category_count", {}):
555
+ if category in per_category_count:
544
556
 
545
557
  #count = summary.get("per_category_count", {})[category]
546
558
  start_timestamp = self._get_start_timestamp_str(stream_info)
@@ -643,6 +655,55 @@ class FireSmokeUseCase(BaseProcessor):
643
655
  event['duration'] = self.get_duration_seconds(start_timestamp, self.current_incident_end_timestamp)
644
656
  event['incident_quant'] = intensity_pct
645
657
  incidents.append(event)
658
+ incident_generated = True
659
+
660
+ # CRITICAL FALLBACK: If no incident was generated despite having detections,
661
+ # generate a basic incident to ensure the incident manager receives data
662
+ if not incident_generated and total > 0:
663
+ self.logger.warning(f"[INCIDENT] No incident generated despite {total} detections. Generating fallback incident.")
664
+ # Calculate area and intensity for fallback
665
+ for det in detections:
666
+ bbox = det.get("bounding_box") or det.get("bbox")
667
+ if bbox:
668
+ xmin, ymin = bbox.get("xmin"), bbox.get("ymin")
669
+ xmax, ymax = bbox.get("xmax"), bbox.get("ymax")
670
+ if None not in (xmin, ymin, xmax, ymax):
671
+ width, height = xmax - xmin, ymax - ymin
672
+ if width > 0 and height > 0:
673
+ total_area += width * height
674
+
675
+ threshold_area = config.threshold_area or 250200.0
676
+ intensity_pct = min(100.0, (total_area / threshold_area) * 100)
677
+
678
+ # Determine severity level
679
+ if intensity_pct >= 30:
680
+ level = "critical"
681
+ elif intensity_pct >= 13:
682
+ level = "significant"
683
+ elif intensity_pct >= 3:
684
+ level = "medium"
685
+ else:
686
+ level = "low"
687
+ self._ascending_alert_list.append(level)
688
+
689
+ start_timestamp = self._get_start_timestamp_str(stream_info)
690
+ human_text = f"INCIDENTS DETECTED @ {current_timestamp}:\n\tSeverity Level: {(self.CASE_TYPE, level)}"
691
+
692
+ event = self.create_incident(
693
+ incident_id='incident_' + self.CASE_TYPE + '_fallback',
694
+ incident_type=self.CASE_TYPE,
695
+ severity_level=level,
696
+ human_text=human_text,
697
+ camera_info=camera_info,
698
+ alerts=alerts,
699
+ alert_settings=[],
700
+ start_time=start_timestamp,
701
+ end_time='Incident still active',
702
+ level_settings={"low": 3, "medium": 5, "significant": 15, "critical": 30}
703
+ )
704
+ event['incident_quant'] = intensity_pct
705
+ incidents.append(event)
706
+ self.logger.info(f"[INCIDENT] Generated fallback incident with level={level}, intensity={intensity_pct:.2f}%")
646
707
 
647
708
  else:
648
709
  #self._ascending_alert_list.append(level)
@@ -689,20 +750,39 @@ class FireSmokeUseCase(BaseProcessor):
689
750
 
690
751
 
691
752
  # Build total_counts array in expected format
753
+ # ALWAYS populate with all target categories to avoid empty arrays downstream
692
754
  total_counts = []
693
755
  if total > 0:
694
- total_counts.append({
695
- "category": 'Fire/Smoke', #TODO: Discuss and fix what to do with this
696
- "count": 1
697
- })
756
+ total_counts.append({
757
+ "category": 'Fire/Smoke',
758
+ "count": 1
759
+ })
760
+ else:
761
+ # When no detections, send count=0 for each category to avoid empty array
762
+ total_counts.append({
763
+ "category": 'Fire',
764
+ "count": 0
765
+ })
766
+ total_counts.append({
767
+ "category": 'Smoke',
768
+ "count": 0
769
+ })
698
770
 
699
- # Build current_counts array in expected format
771
+ # Build current_counts array in expected format
772
+ # ALWAYS populate with all target categories to avoid empty arrays downstream
700
773
  current_counts = []
701
- if total > 0: # Include even if 0 when there are detections
702
- current_counts.append({
703
- "category": 'Fire/Smoke', #TODO: Discuss and fix what to do with this
704
- "count": 1
705
- })
774
+
775
+ # Add Fire entry (count=1 if detected, count=0 if not)
776
+ current_counts.append({
777
+ "category": 'Fire',
778
+ "count": 1 if total_fire > 0 else 0
779
+ })
780
+
781
+ # Add Smoke entry (count=1 if detected, count=0 if not)
782
+ current_counts.append({
783
+ "category": 'Smoke',
784
+ "count": 1 if total_smoke > 0 else 0
785
+ })
706
786
 
707
787
  human_lines = [f"CURRENT FRAME @ {current_timestamp}:"]
708
788
  if total_fire > 0:
@@ -773,8 +853,8 @@ class FireSmokeUseCase(BaseProcessor):
773
853
  detections=detections, human_text=human_text, camera_info=camera_info, alerts=alerts, alert_settings=alert_settings,
774
854
  reset_settings=reset_settings, start_time=high_precision_start_timestamp ,
775
855
  reset_time=high_precision_reset_timestamp)
776
-
777
-
856
+
857
+ tracking_stat['target_categories'] = self.target_categories
778
858
  tracking_stats.append(tracking_stat)
779
859
 
780
860
  if len(self.id_hit_list)==1: