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.
- matrice_analytics/post_processing/__init__.py +21 -2
- matrice_analytics/post_processing/config.py +6 -0
- matrice_analytics/post_processing/core/config.py +102 -3
- matrice_analytics/post_processing/face_reg/face_recognition.py +146 -14
- matrice_analytics/post_processing/face_reg/face_recognition_client.py +116 -4
- matrice_analytics/post_processing/face_reg/people_activity_logging.py +19 -0
- matrice_analytics/post_processing/post_processor.py +12 -0
- matrice_analytics/post_processing/usecases/__init__.py +9 -0
- matrice_analytics/post_processing/usecases/advanced_customer_service.py +5 -2
- matrice_analytics/post_processing/usecases/color_detection.py +1 -0
- matrice_analytics/post_processing/usecases/fire_detection.py +94 -14
- matrice_analytics/post_processing/usecases/footfall.py +750 -0
- matrice_analytics/post_processing/usecases/license_plate_monitoring.py +91 -1
- matrice_analytics/post_processing/usecases/people_counting.py +55 -22
- matrice_analytics/post_processing/usecases/vehicle_monitoring.py +15 -32
- matrice_analytics/post_processing/usecases/vehicle_monitoring_drone_view.py +1007 -0
- matrice_analytics/post_processing/usecases/vehicle_monitoring_parking_lot.py +1011 -0
- matrice_analytics/post_processing/usecases/weapon_detection.py +2 -1
- matrice_analytics/post_processing/utils/alert_instance_utils.py +94 -26
- matrice_analytics/post_processing/utils/business_metrics_manager_utils.py +97 -4
- matrice_analytics/post_processing/utils/incident_manager_utils.py +103 -6
- {matrice_analytics-0.1.89.dist-info → matrice_analytics-0.1.97.dist-info}/METADATA +1 -1
- {matrice_analytics-0.1.89.dist-info → matrice_analytics-0.1.97.dist-info}/RECORD +26 -23
- {matrice_analytics-0.1.89.dist-info → matrice_analytics-0.1.97.dist-info}/WHEEL +0 -0
- {matrice_analytics-0.1.89.dist-info → matrice_analytics-0.1.97.dist-info}/licenses/LICENSE.txt +0 -0
- {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
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
-
|
|
418
|
-
if cat not in self.target_categories
|
|
440
|
+
track_id = det.get("track_id")
|
|
441
|
+
if cat not in self.target_categories:
|
|
419
442
|
continue
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
self.
|
|
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
|
-
|
|
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', '
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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', '
|
|
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
|
|