matrice-analytics 0.1.60__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/__init__.py +28 -0
- matrice_analytics/boundary_drawing_internal/README.md +305 -0
- matrice_analytics/boundary_drawing_internal/__init__.py +45 -0
- matrice_analytics/boundary_drawing_internal/boundary_drawing_internal.py +1207 -0
- matrice_analytics/boundary_drawing_internal/boundary_drawing_tool.py +429 -0
- matrice_analytics/boundary_drawing_internal/boundary_tool_template.html +1036 -0
- matrice_analytics/boundary_drawing_internal/data/.gitignore +12 -0
- matrice_analytics/boundary_drawing_internal/example_usage.py +206 -0
- matrice_analytics/boundary_drawing_internal/usage/README.md +110 -0
- matrice_analytics/boundary_drawing_internal/usage/boundary_drawer_launcher.py +102 -0
- matrice_analytics/boundary_drawing_internal/usage/simple_boundary_launcher.py +107 -0
- matrice_analytics/post_processing/README.md +455 -0
- matrice_analytics/post_processing/__init__.py +732 -0
- matrice_analytics/post_processing/advanced_tracker/README.md +650 -0
- matrice_analytics/post_processing/advanced_tracker/__init__.py +17 -0
- matrice_analytics/post_processing/advanced_tracker/base.py +99 -0
- matrice_analytics/post_processing/advanced_tracker/config.py +77 -0
- matrice_analytics/post_processing/advanced_tracker/kalman_filter.py +370 -0
- matrice_analytics/post_processing/advanced_tracker/matching.py +195 -0
- matrice_analytics/post_processing/advanced_tracker/strack.py +230 -0
- matrice_analytics/post_processing/advanced_tracker/tracker.py +367 -0
- matrice_analytics/post_processing/config.py +146 -0
- matrice_analytics/post_processing/core/__init__.py +63 -0
- matrice_analytics/post_processing/core/base.py +704 -0
- matrice_analytics/post_processing/core/config.py +3291 -0
- matrice_analytics/post_processing/core/config_utils.py +925 -0
- matrice_analytics/post_processing/face_reg/__init__.py +43 -0
- matrice_analytics/post_processing/face_reg/compare_similarity.py +556 -0
- matrice_analytics/post_processing/face_reg/embedding_manager.py +950 -0
- matrice_analytics/post_processing/face_reg/face_recognition.py +2234 -0
- matrice_analytics/post_processing/face_reg/face_recognition_client.py +606 -0
- matrice_analytics/post_processing/face_reg/people_activity_logging.py +321 -0
- matrice_analytics/post_processing/ocr/__init__.py +0 -0
- matrice_analytics/post_processing/ocr/easyocr_extractor.py +250 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/__init__.py +9 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/__init__.py +4 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/cli.py +33 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/dataset_stats.py +139 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/export.py +398 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/train.py +447 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/utils.py +129 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/valid.py +93 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/validate_dataset.py +240 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/visualize_augmentation.py +176 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/visualize_predictions.py +96 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/core/__init__.py +3 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/core/process.py +246 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/core/types.py +60 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/core/utils.py +87 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/inference/__init__.py +3 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/inference/config.py +82 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/inference/hub.py +141 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/inference/plate_recognizer.py +323 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/py.typed +0 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/__init__.py +0 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/data/__init__.py +0 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/data/augmentation.py +101 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/data/dataset.py +97 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/__init__.py +0 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/config.py +114 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/layers.py +553 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/loss.py +55 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/metric.py +86 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/model_builders.py +95 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/model_schema.py +395 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/utilities/__init__.py +0 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/utilities/backend_utils.py +38 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/utilities/utils.py +214 -0
- matrice_analytics/post_processing/ocr/postprocessing.py +270 -0
- matrice_analytics/post_processing/ocr/preprocessing.py +52 -0
- matrice_analytics/post_processing/post_processor.py +1175 -0
- matrice_analytics/post_processing/test_cases/__init__.py +1 -0
- matrice_analytics/post_processing/test_cases/run_tests.py +143 -0
- matrice_analytics/post_processing/test_cases/test_advanced_customer_service.py +841 -0
- matrice_analytics/post_processing/test_cases/test_basic_counting_tracking.py +523 -0
- matrice_analytics/post_processing/test_cases/test_comprehensive.py +531 -0
- matrice_analytics/post_processing/test_cases/test_config.py +852 -0
- matrice_analytics/post_processing/test_cases/test_customer_service.py +585 -0
- matrice_analytics/post_processing/test_cases/test_data_generators.py +583 -0
- matrice_analytics/post_processing/test_cases/test_people_counting.py +510 -0
- matrice_analytics/post_processing/test_cases/test_processor.py +524 -0
- matrice_analytics/post_processing/test_cases/test_usecases.py +165 -0
- matrice_analytics/post_processing/test_cases/test_utilities.py +356 -0
- matrice_analytics/post_processing/test_cases/test_utils.py +743 -0
- matrice_analytics/post_processing/usecases/Histopathological_Cancer_Detection_img.py +604 -0
- matrice_analytics/post_processing/usecases/__init__.py +267 -0
- matrice_analytics/post_processing/usecases/abandoned_object_detection.py +797 -0
- matrice_analytics/post_processing/usecases/advanced_customer_service.py +1601 -0
- matrice_analytics/post_processing/usecases/age_detection.py +842 -0
- matrice_analytics/post_processing/usecases/age_gender_detection.py +1085 -0
- matrice_analytics/post_processing/usecases/anti_spoofing_detection.py +656 -0
- matrice_analytics/post_processing/usecases/assembly_line_detection.py +841 -0
- matrice_analytics/post_processing/usecases/banana_defect_detection.py +624 -0
- matrice_analytics/post_processing/usecases/basic_counting_tracking.py +667 -0
- matrice_analytics/post_processing/usecases/blood_cancer_detection_img.py +881 -0
- matrice_analytics/post_processing/usecases/car_damage_detection.py +834 -0
- matrice_analytics/post_processing/usecases/car_part_segmentation.py +946 -0
- matrice_analytics/post_processing/usecases/car_service.py +1601 -0
- matrice_analytics/post_processing/usecases/cardiomegaly_classification.py +864 -0
- matrice_analytics/post_processing/usecases/cell_microscopy_segmentation.py +897 -0
- matrice_analytics/post_processing/usecases/chicken_pose_detection.py +648 -0
- matrice_analytics/post_processing/usecases/child_monitoring.py +814 -0
- matrice_analytics/post_processing/usecases/color/clip.py +660 -0
- matrice_analytics/post_processing/usecases/color/clip_processor/merges.txt +48895 -0
- matrice_analytics/post_processing/usecases/color/clip_processor/preprocessor_config.json +28 -0
- matrice_analytics/post_processing/usecases/color/clip_processor/special_tokens_map.json +30 -0
- matrice_analytics/post_processing/usecases/color/clip_processor/tokenizer.json +245079 -0
- matrice_analytics/post_processing/usecases/color/clip_processor/tokenizer_config.json +32 -0
- matrice_analytics/post_processing/usecases/color/clip_processor/vocab.json +1 -0
- matrice_analytics/post_processing/usecases/color/color_map_utils.py +70 -0
- matrice_analytics/post_processing/usecases/color/color_mapper.py +468 -0
- matrice_analytics/post_processing/usecases/color_detection.py +1936 -0
- matrice_analytics/post_processing/usecases/color_map_utils.py +70 -0
- matrice_analytics/post_processing/usecases/concrete_crack_detection.py +827 -0
- matrice_analytics/post_processing/usecases/crop_weed_detection.py +781 -0
- matrice_analytics/post_processing/usecases/customer_service.py +1008 -0
- matrice_analytics/post_processing/usecases/defect_detection_products.py +936 -0
- matrice_analytics/post_processing/usecases/distracted_driver_detection.py +822 -0
- matrice_analytics/post_processing/usecases/drone_traffic_monitoring.py +585 -0
- matrice_analytics/post_processing/usecases/drowsy_driver_detection.py +829 -0
- matrice_analytics/post_processing/usecases/dwell_detection.py +829 -0
- matrice_analytics/post_processing/usecases/emergency_vehicle_detection.py +827 -0
- matrice_analytics/post_processing/usecases/face_emotion.py +813 -0
- matrice_analytics/post_processing/usecases/face_recognition.py +827 -0
- matrice_analytics/post_processing/usecases/fashion_detection.py +835 -0
- matrice_analytics/post_processing/usecases/field_mapping.py +902 -0
- matrice_analytics/post_processing/usecases/fire_detection.py +1146 -0
- matrice_analytics/post_processing/usecases/flare_analysis.py +836 -0
- matrice_analytics/post_processing/usecases/flower_segmentation.py +1006 -0
- matrice_analytics/post_processing/usecases/gas_leak_detection.py +837 -0
- matrice_analytics/post_processing/usecases/gender_detection.py +832 -0
- matrice_analytics/post_processing/usecases/human_activity_recognition.py +871 -0
- matrice_analytics/post_processing/usecases/intrusion_detection.py +1672 -0
- matrice_analytics/post_processing/usecases/leaf.py +821 -0
- matrice_analytics/post_processing/usecases/leaf_disease.py +840 -0
- matrice_analytics/post_processing/usecases/leak_detection.py +837 -0
- matrice_analytics/post_processing/usecases/license_plate_detection.py +1188 -0
- matrice_analytics/post_processing/usecases/license_plate_monitoring.py +1781 -0
- matrice_analytics/post_processing/usecases/litter_monitoring.py +717 -0
- matrice_analytics/post_processing/usecases/mask_detection.py +869 -0
- matrice_analytics/post_processing/usecases/natural_disaster.py +907 -0
- matrice_analytics/post_processing/usecases/parking.py +787 -0
- matrice_analytics/post_processing/usecases/parking_space_detection.py +822 -0
- matrice_analytics/post_processing/usecases/pcb_defect_detection.py +888 -0
- matrice_analytics/post_processing/usecases/pedestrian_detection.py +808 -0
- matrice_analytics/post_processing/usecases/people_counting.py +706 -0
- matrice_analytics/post_processing/usecases/people_counting_bckp.py +1683 -0
- matrice_analytics/post_processing/usecases/people_tracking.py +1842 -0
- matrice_analytics/post_processing/usecases/pipeline_detection.py +605 -0
- matrice_analytics/post_processing/usecases/plaque_segmentation_img.py +874 -0
- matrice_analytics/post_processing/usecases/pothole_segmentation.py +915 -0
- matrice_analytics/post_processing/usecases/ppe_compliance.py +645 -0
- matrice_analytics/post_processing/usecases/price_tag_detection.py +822 -0
- matrice_analytics/post_processing/usecases/proximity_detection.py +1901 -0
- matrice_analytics/post_processing/usecases/road_lane_detection.py +623 -0
- matrice_analytics/post_processing/usecases/road_traffic_density.py +832 -0
- matrice_analytics/post_processing/usecases/road_view_segmentation.py +915 -0
- matrice_analytics/post_processing/usecases/shelf_inventory_detection.py +583 -0
- matrice_analytics/post_processing/usecases/shoplifting_detection.py +822 -0
- matrice_analytics/post_processing/usecases/shopping_cart_analysis.py +899 -0
- matrice_analytics/post_processing/usecases/skin_cancer_classification_img.py +864 -0
- matrice_analytics/post_processing/usecases/smoker_detection.py +833 -0
- matrice_analytics/post_processing/usecases/solar_panel.py +810 -0
- matrice_analytics/post_processing/usecases/suspicious_activity_detection.py +1030 -0
- matrice_analytics/post_processing/usecases/template_usecase.py +380 -0
- matrice_analytics/post_processing/usecases/theft_detection.py +648 -0
- matrice_analytics/post_processing/usecases/traffic_sign_monitoring.py +724 -0
- matrice_analytics/post_processing/usecases/underground_pipeline_defect_detection.py +775 -0
- matrice_analytics/post_processing/usecases/underwater_pollution_detection.py +842 -0
- matrice_analytics/post_processing/usecases/vehicle_monitoring.py +1029 -0
- matrice_analytics/post_processing/usecases/warehouse_object_segmentation.py +899 -0
- matrice_analytics/post_processing/usecases/waterbody_segmentation.py +923 -0
- matrice_analytics/post_processing/usecases/weapon_detection.py +771 -0
- matrice_analytics/post_processing/usecases/weld_defect_detection.py +615 -0
- matrice_analytics/post_processing/usecases/wildlife_monitoring.py +898 -0
- matrice_analytics/post_processing/usecases/windmill_maintenance.py +834 -0
- matrice_analytics/post_processing/usecases/wound_segmentation.py +856 -0
- matrice_analytics/post_processing/utils/__init__.py +150 -0
- matrice_analytics/post_processing/utils/advanced_counting_utils.py +400 -0
- matrice_analytics/post_processing/utils/advanced_helper_utils.py +317 -0
- matrice_analytics/post_processing/utils/advanced_tracking_utils.py +461 -0
- matrice_analytics/post_processing/utils/alerting_utils.py +213 -0
- matrice_analytics/post_processing/utils/category_mapping_utils.py +94 -0
- matrice_analytics/post_processing/utils/color_utils.py +592 -0
- matrice_analytics/post_processing/utils/counting_utils.py +182 -0
- matrice_analytics/post_processing/utils/filter_utils.py +261 -0
- matrice_analytics/post_processing/utils/format_utils.py +293 -0
- matrice_analytics/post_processing/utils/geometry_utils.py +300 -0
- matrice_analytics/post_processing/utils/smoothing_utils.py +358 -0
- matrice_analytics/post_processing/utils/tracking_utils.py +234 -0
- matrice_analytics/py.typed +0 -0
- matrice_analytics-0.1.60.dist-info/METADATA +481 -0
- matrice_analytics-0.1.60.dist-info/RECORD +196 -0
- matrice_analytics-0.1.60.dist-info/WHEEL +5 -0
- matrice_analytics-0.1.60.dist-info/licenses/LICENSE.txt +21 -0
- matrice_analytics-0.1.60.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,461 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Advanced tracking utilities with Kalman filter support for post-processing.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
from typing import List, Dict, Tuple, Optional, Any, Union
|
|
7
|
+
import time
|
|
8
|
+
import logging
|
|
9
|
+
from collections import defaultdict, deque
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
# Try to import optional dependencies
|
|
14
|
+
try:
|
|
15
|
+
from filterpy.kalman import KalmanFilter
|
|
16
|
+
KALMAN_AVAILABLE = True
|
|
17
|
+
except ImportError:
|
|
18
|
+
KALMAN_AVAILABLE = False
|
|
19
|
+
logger.warning("filterpy not available. Advanced Kalman tracking disabled. Install with: pip install filterpy")
|
|
20
|
+
|
|
21
|
+
try:
|
|
22
|
+
from scipy.optimize import linear_sum_assignment
|
|
23
|
+
SCIPY_AVAILABLE = True
|
|
24
|
+
except ImportError:
|
|
25
|
+
SCIPY_AVAILABLE = False
|
|
26
|
+
logger.warning("scipy not available. Optimal assignment disabled. Install with: pip install scipy")
|
|
27
|
+
|
|
28
|
+
from .geometry_utils import get_bbox_center, calculate_iou
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def convert_bbox_to_z(bbox):
|
|
32
|
+
"""Convert bounding box to Kalman filter state vector."""
|
|
33
|
+
if isinstance(bbox, dict):
|
|
34
|
+
# Handle dict format
|
|
35
|
+
if "xmin" in bbox:
|
|
36
|
+
x1, y1, x2, y2 = bbox["xmin"], bbox["ymin"], bbox["xmax"], bbox["ymax"]
|
|
37
|
+
elif "x1" in bbox:
|
|
38
|
+
x1, y1, x2, y2 = bbox["x1"], bbox["y1"], bbox["x2"], bbox["y2"]
|
|
39
|
+
else:
|
|
40
|
+
values = list(bbox.values())
|
|
41
|
+
x1, y1, x2, y2 = values[0], values[1], values[2], values[3]
|
|
42
|
+
bbox = [x1, y1, x2, y2]
|
|
43
|
+
|
|
44
|
+
w = bbox[2] - bbox[0]
|
|
45
|
+
h = bbox[3] - bbox[1]
|
|
46
|
+
x = bbox[0] + w/2.
|
|
47
|
+
y = bbox[1] + h/2.
|
|
48
|
+
s = w * h
|
|
49
|
+
r = w / float(h) if h > 0 else 1.0
|
|
50
|
+
return np.array([x, y, s, r]).reshape((4, 1))
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def convert_x_to_bbox(x, score=None):
|
|
54
|
+
"""Convert Kalman filter state vector to bounding box."""
|
|
55
|
+
w = np.sqrt(x[2] * x[3])
|
|
56
|
+
h = x[2] / w if w > 0 else x[2]
|
|
57
|
+
if score is None:
|
|
58
|
+
return np.array([x[0]-w/2., x[1]-h/2., x[0]+w/2., x[1]+h/2.]).reshape((1, 4))
|
|
59
|
+
else:
|
|
60
|
+
return np.array([x[0]-w/2., x[1]-h/2., x[0]+w/2., x[1]+h/2., score]).reshape((1, 5))
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def convert_detection_to_tracking_format(detection: Dict) -> Dict:
|
|
64
|
+
"""Convert detection format to tracking format."""
|
|
65
|
+
tracking_detection = detection.copy()
|
|
66
|
+
|
|
67
|
+
# Ensure bbox format consistency
|
|
68
|
+
if 'bounding_box' in detection and 'bbox' not in detection:
|
|
69
|
+
tracking_detection['bbox'] = detection['bounding_box']
|
|
70
|
+
elif 'bbox' in detection and 'bounding_box' not in detection:
|
|
71
|
+
tracking_detection['bounding_box'] = detection['bbox']
|
|
72
|
+
|
|
73
|
+
# Ensure category/class consistency
|
|
74
|
+
if 'category' in detection and 'class' not in detection:
|
|
75
|
+
tracking_detection['class'] = detection['category']
|
|
76
|
+
elif 'class' in detection and 'category' not in detection:
|
|
77
|
+
tracking_detection['category'] = detection['class']
|
|
78
|
+
|
|
79
|
+
return tracking_detection
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def convert_tracking_to_detection_format(tracking_result: Dict) -> Dict:
|
|
83
|
+
"""Convert tracking result back to detection format."""
|
|
84
|
+
detection = tracking_result.copy()
|
|
85
|
+
|
|
86
|
+
# Ensure standard detection format
|
|
87
|
+
if 'bbox' in tracking_result and 'bounding_box' not in tracking_result:
|
|
88
|
+
detection['bounding_box'] = tracking_result['bbox']
|
|
89
|
+
|
|
90
|
+
if 'class' in tracking_result and 'category' not in tracking_result:
|
|
91
|
+
detection['category'] = tracking_result['class']
|
|
92
|
+
|
|
93
|
+
return detection
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class KalmanBoxTracker:
|
|
97
|
+
"""Individual object tracker using Kalman filter."""
|
|
98
|
+
|
|
99
|
+
count = 0
|
|
100
|
+
|
|
101
|
+
def __init__(self, bbox, class_name, confidence=0.0, features=None):
|
|
102
|
+
"""Initialize Kalman filter tracker."""
|
|
103
|
+
if not KALMAN_AVAILABLE:
|
|
104
|
+
raise ImportError("filterpy is required for Kalman tracking. Install with: pip install filterpy")
|
|
105
|
+
|
|
106
|
+
self.kf = KalmanFilter(dim_x=7, dim_z=4)
|
|
107
|
+
self.kf.F = np.array([[1,0,0,0,1,0,0],
|
|
108
|
+
[0,1,0,0,0,1,0],
|
|
109
|
+
[0,0,1,0,0,0,1],
|
|
110
|
+
[0,0,0,1,0,0,0],
|
|
111
|
+
[0,0,0,0,1,0,0],
|
|
112
|
+
[0,0,0,0,0,1,0],
|
|
113
|
+
[0,0,0,0,0,0,1]])
|
|
114
|
+
self.kf.H = np.array([[1,0,0,0,0,0,0],
|
|
115
|
+
[0,1,0,0,0,0,0],
|
|
116
|
+
[0,0,1,0,0,0,0],
|
|
117
|
+
[0,0,0,1,0,0,0]])
|
|
118
|
+
|
|
119
|
+
self.kf.R[2:,2:] *= 10.
|
|
120
|
+
self.kf.P[4:,4:] *= 1000.
|
|
121
|
+
self.kf.P *= 10.
|
|
122
|
+
self.kf.Q[-1,-1] *= 0.01
|
|
123
|
+
self.kf.Q[4:,4:] *= 0.01
|
|
124
|
+
|
|
125
|
+
self.kf.x[:4] = convert_bbox_to_z(bbox)
|
|
126
|
+
self.time_since_update = 0
|
|
127
|
+
self.id = KalmanBoxTracker.count
|
|
128
|
+
KalmanBoxTracker.count += 1
|
|
129
|
+
self.history = []
|
|
130
|
+
self.hits = 0
|
|
131
|
+
self.hit_streak = 0
|
|
132
|
+
self.age = 0
|
|
133
|
+
self.class_name = class_name
|
|
134
|
+
self.confidence = confidence
|
|
135
|
+
self.features = features or []
|
|
136
|
+
|
|
137
|
+
# Enhanced tracking attributes
|
|
138
|
+
self.disappeared_frames = 0
|
|
139
|
+
self.max_disappeared = 15
|
|
140
|
+
self.trajectory = []
|
|
141
|
+
self.velocity = (0, 0)
|
|
142
|
+
self.center_history = []
|
|
143
|
+
|
|
144
|
+
def update(self, bbox, confidence=None, features=None):
|
|
145
|
+
"""Update tracker with new detection."""
|
|
146
|
+
self.time_since_update = 0
|
|
147
|
+
self.history = []
|
|
148
|
+
self.hits += 1
|
|
149
|
+
self.hit_streak += 1
|
|
150
|
+
self.disappeared_frames = 0
|
|
151
|
+
|
|
152
|
+
if confidence is not None:
|
|
153
|
+
self.confidence = confidence
|
|
154
|
+
if features is not None:
|
|
155
|
+
self.features = features
|
|
156
|
+
|
|
157
|
+
# Update center history
|
|
158
|
+
if isinstance(bbox, dict):
|
|
159
|
+
center = get_bbox_center(bbox)
|
|
160
|
+
else:
|
|
161
|
+
center_x = (bbox[0] + bbox[2]) / 2
|
|
162
|
+
center_y = (bbox[1] + bbox[3]) / 2
|
|
163
|
+
center = (center_x, center_y)
|
|
164
|
+
|
|
165
|
+
self.center_history.append((center[0], center[1], time.time()))
|
|
166
|
+
|
|
167
|
+
# Keep only recent history
|
|
168
|
+
if len(self.center_history) > 20:
|
|
169
|
+
self.center_history = self.center_history[-20:]
|
|
170
|
+
|
|
171
|
+
# Calculate velocity
|
|
172
|
+
if len(self.center_history) >= 2:
|
|
173
|
+
curr_pos = self.center_history[-1]
|
|
174
|
+
prev_pos = self.center_history[-2]
|
|
175
|
+
dt = curr_pos[2] - prev_pos[2]
|
|
176
|
+
if dt > 0:
|
|
177
|
+
self.velocity = ((curr_pos[0] - prev_pos[0]) / dt,
|
|
178
|
+
(curr_pos[1] - prev_pos[1]) / dt)
|
|
179
|
+
|
|
180
|
+
self.kf.update(convert_bbox_to_z(bbox))
|
|
181
|
+
|
|
182
|
+
def predict(self):
|
|
183
|
+
"""Predict next state."""
|
|
184
|
+
if (self.kf.x[6] + self.kf.x[2]) <= 0:
|
|
185
|
+
self.kf.x[6] *= 0.0
|
|
186
|
+
self.kf.predict()
|
|
187
|
+
self.age += 1
|
|
188
|
+
if self.time_since_update > 0:
|
|
189
|
+
self.hit_streak = 0
|
|
190
|
+
self.time_since_update += 1
|
|
191
|
+
self.disappeared_frames += 1
|
|
192
|
+
self.history.append(convert_x_to_bbox(self.kf.x))
|
|
193
|
+
return self.history[-1]
|
|
194
|
+
|
|
195
|
+
def get_state(self):
|
|
196
|
+
"""Get current bounding box."""
|
|
197
|
+
return convert_x_to_bbox(self.kf.x)
|
|
198
|
+
|
|
199
|
+
def get_center(self):
|
|
200
|
+
"""Get current center point."""
|
|
201
|
+
bbox = self.get_state()[0]
|
|
202
|
+
return ((bbox[0] + bbox[2]) / 2, (bbox[1] + bbox[3]) / 2)
|
|
203
|
+
|
|
204
|
+
def is_active(self):
|
|
205
|
+
"""Check if tracker is still active."""
|
|
206
|
+
return self.disappeared_frames < self.max_disappeared
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
class AdvancedTrackingLibrary:
|
|
210
|
+
"""Advanced tracking library with Kalman filter support."""
|
|
211
|
+
|
|
212
|
+
def __init__(self,
|
|
213
|
+
tracking_method: str = 'kalman',
|
|
214
|
+
max_age: int = 30,
|
|
215
|
+
min_hits: int = 3,
|
|
216
|
+
iou_threshold: float = 0.3,
|
|
217
|
+
target_classes: List[str] = None):
|
|
218
|
+
"""Initialize advanced tracking library."""
|
|
219
|
+
self.tracking_method = tracking_method
|
|
220
|
+
self.max_age = max_age
|
|
221
|
+
self.min_hits = min_hits
|
|
222
|
+
self.iou_threshold = iou_threshold
|
|
223
|
+
self.target_classes = target_classes or []
|
|
224
|
+
|
|
225
|
+
if tracking_method == 'kalman' and not KALMAN_AVAILABLE:
|
|
226
|
+
logger.warning("Kalman tracking requested but filterpy not available. Falling back to basic tracking.")
|
|
227
|
+
self.tracking_method = 'basic'
|
|
228
|
+
|
|
229
|
+
self.trackers = []
|
|
230
|
+
self.frame_count = 0
|
|
231
|
+
self.total_counts = defaultdict(int)
|
|
232
|
+
self.class_tracks = defaultdict(set)
|
|
233
|
+
self.active_tracks = {}
|
|
234
|
+
|
|
235
|
+
def process(self, detections: List[Dict], frame_id: str = None) -> Dict[str, Any]:
|
|
236
|
+
"""Process detections and return tracking results."""
|
|
237
|
+
if frame_id is None:
|
|
238
|
+
frame_id = str(self.frame_count)
|
|
239
|
+
|
|
240
|
+
self.frame_count += 1
|
|
241
|
+
|
|
242
|
+
# Format detections for tracking
|
|
243
|
+
formatted_detections = self._format_detections(detections)
|
|
244
|
+
|
|
245
|
+
# Update tracking
|
|
246
|
+
tracked_detections = self._update_tracking(formatted_detections)
|
|
247
|
+
|
|
248
|
+
# Convert to tracking format
|
|
249
|
+
tracking_results = {
|
|
250
|
+
frame_id: []
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
for detection in tracked_detections:
|
|
254
|
+
tracking_detection = convert_detection_to_tracking_format(detection)
|
|
255
|
+
tracking_results[frame_id].append(tracking_detection)
|
|
256
|
+
|
|
257
|
+
return tracking_results
|
|
258
|
+
|
|
259
|
+
def _format_detections(self, detections: List[Dict]) -> List[Dict]:
|
|
260
|
+
"""Format detections for tracking."""
|
|
261
|
+
formatted = []
|
|
262
|
+
|
|
263
|
+
for detection in detections:
|
|
264
|
+
# Skip if not target class
|
|
265
|
+
class_name = detection.get('category', detection.get('class', 'unknown'))
|
|
266
|
+
if self.target_classes and class_name not in self.target_classes:
|
|
267
|
+
continue
|
|
268
|
+
|
|
269
|
+
# Ensure required fields
|
|
270
|
+
formatted_detection = {
|
|
271
|
+
'bbox': detection.get('bounding_box', detection.get('bbox')),
|
|
272
|
+
'confidence': detection.get('confidence', 1.0),
|
|
273
|
+
'class': class_name,
|
|
274
|
+
'category': class_name
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
# Add optional fields
|
|
278
|
+
if 'features' in detection:
|
|
279
|
+
formatted_detection['features'] = detection['features']
|
|
280
|
+
|
|
281
|
+
formatted.append(formatted_detection)
|
|
282
|
+
|
|
283
|
+
return formatted
|
|
284
|
+
|
|
285
|
+
def _update_tracking(self, detections: List[Dict]) -> List[Dict]:
|
|
286
|
+
"""Update tracking with new detections."""
|
|
287
|
+
if self.tracking_method == 'kalman':
|
|
288
|
+
return self._update_kalman_tracking(detections)
|
|
289
|
+
else:
|
|
290
|
+
return self._update_basic_tracking(detections)
|
|
291
|
+
|
|
292
|
+
def _update_kalman_tracking(self, detections: List[Dict]) -> List[Dict]:
|
|
293
|
+
"""Update Kalman filter based tracking."""
|
|
294
|
+
# Predict for all trackers
|
|
295
|
+
for tracker in self.trackers:
|
|
296
|
+
tracker.predict()
|
|
297
|
+
|
|
298
|
+
# Associate detections to trackers
|
|
299
|
+
matched, unmatched_dets, unmatched_trks = self._associate_detections_to_trackers(
|
|
300
|
+
detections, self.trackers, self.iou_threshold
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
# Update matched trackers
|
|
304
|
+
for m in matched:
|
|
305
|
+
self.trackers[m[1]].update(detections[m[0]]['bbox'],
|
|
306
|
+
detections[m[0]]['confidence'],
|
|
307
|
+
detections[m[0]].get('features'))
|
|
308
|
+
|
|
309
|
+
# Create new trackers for unmatched detections
|
|
310
|
+
for i in unmatched_dets:
|
|
311
|
+
det = detections[i]
|
|
312
|
+
tracker = KalmanBoxTracker(
|
|
313
|
+
det['bbox'],
|
|
314
|
+
det['class'],
|
|
315
|
+
det['confidence'],
|
|
316
|
+
det.get('features')
|
|
317
|
+
)
|
|
318
|
+
self.trackers.append(tracker)
|
|
319
|
+
|
|
320
|
+
# Remove dead trackers
|
|
321
|
+
active_trackers = []
|
|
322
|
+
tracked_detections = []
|
|
323
|
+
|
|
324
|
+
for tracker in self.trackers:
|
|
325
|
+
if tracker.time_since_update < self.max_age and tracker.hit_streak >= self.min_hits:
|
|
326
|
+
# Create tracked detection
|
|
327
|
+
bbox = tracker.get_state()[0]
|
|
328
|
+
tracked_det = {
|
|
329
|
+
'bbox': {
|
|
330
|
+
'xmin': int(bbox[0]),
|
|
331
|
+
'ymin': int(bbox[1]),
|
|
332
|
+
'xmax': int(bbox[2]),
|
|
333
|
+
'ymax': int(bbox[3])
|
|
334
|
+
},
|
|
335
|
+
'bounding_box': {
|
|
336
|
+
'xmin': int(bbox[0]),
|
|
337
|
+
'ymin': int(bbox[1]),
|
|
338
|
+
'xmax': int(bbox[2]),
|
|
339
|
+
'ymax': int(bbox[3])
|
|
340
|
+
},
|
|
341
|
+
'track_id': tracker.id,
|
|
342
|
+
'confidence': tracker.confidence,
|
|
343
|
+
'class': tracker.class_name,
|
|
344
|
+
'category': tracker.class_name,
|
|
345
|
+
'velocity': tracker.velocity
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
tracked_detections.append(tracked_det)
|
|
349
|
+
self.active_tracks[tracker.id] = tracked_det
|
|
350
|
+
self.class_tracks[tracker.class_name].add(tracker.id)
|
|
351
|
+
|
|
352
|
+
if tracker.is_active():
|
|
353
|
+
active_trackers.append(tracker)
|
|
354
|
+
|
|
355
|
+
self.trackers = active_trackers
|
|
356
|
+
return tracked_detections
|
|
357
|
+
|
|
358
|
+
def _update_basic_tracking(self, detections: List[Dict]) -> List[Dict]:
|
|
359
|
+
"""Basic tracking fallback when Kalman is not available."""
|
|
360
|
+
# Simple tracking based on IoU matching
|
|
361
|
+
tracked_detections = []
|
|
362
|
+
|
|
363
|
+
for i, detection in enumerate(detections):
|
|
364
|
+
# Add basic track ID (frame-based)
|
|
365
|
+
track_id = f"{self.frame_count}_{i}"
|
|
366
|
+
|
|
367
|
+
tracked_det = detection.copy()
|
|
368
|
+
tracked_det['track_id'] = track_id
|
|
369
|
+
tracked_detections.append(tracked_det)
|
|
370
|
+
|
|
371
|
+
return tracked_detections
|
|
372
|
+
|
|
373
|
+
def _associate_detections_to_trackers(self, detections, trackers, iou_threshold=0.3):
|
|
374
|
+
"""Associate detections to trackers using IoU."""
|
|
375
|
+
if len(trackers) == 0:
|
|
376
|
+
return np.empty((0, 2), dtype=int), np.arange(len(detections)), np.empty((0, 5), dtype=int)
|
|
377
|
+
|
|
378
|
+
# Create IoU matrix
|
|
379
|
+
iou_matrix = np.zeros((len(detections), len(trackers)), dtype=np.float32)
|
|
380
|
+
|
|
381
|
+
for d, det in enumerate(detections):
|
|
382
|
+
det_bbox = det['bbox']
|
|
383
|
+
if isinstance(det_bbox, dict):
|
|
384
|
+
det_bbox = [det_bbox.get('xmin', 0), det_bbox.get('ymin', 0),
|
|
385
|
+
det_bbox.get('xmax', 0), det_bbox.get('ymax', 0)]
|
|
386
|
+
|
|
387
|
+
for t, trk in enumerate(trackers):
|
|
388
|
+
trk_bbox = trk.get_state()[0]
|
|
389
|
+
iou_matrix[d, t] = calculate_iou(det_bbox, trk_bbox)
|
|
390
|
+
|
|
391
|
+
# Use Hungarian algorithm if available, otherwise greedy matching
|
|
392
|
+
if SCIPY_AVAILABLE:
|
|
393
|
+
matched_indices = linear_sum_assignment(-iou_matrix)
|
|
394
|
+
matched_indices = np.array(list(zip(matched_indices[0], matched_indices[1])))
|
|
395
|
+
else:
|
|
396
|
+
matched_indices = self._greedy_assignment(iou_matrix)
|
|
397
|
+
|
|
398
|
+
unmatched_detections = []
|
|
399
|
+
for d, det in enumerate(detections):
|
|
400
|
+
if len(matched_indices) == 0 or d not in matched_indices[:, 0]:
|
|
401
|
+
unmatched_detections.append(d)
|
|
402
|
+
|
|
403
|
+
unmatched_trackers = []
|
|
404
|
+
for t, trk in enumerate(trackers):
|
|
405
|
+
if len(matched_indices) == 0 or t not in matched_indices[:, 1]:
|
|
406
|
+
unmatched_trackers.append(t)
|
|
407
|
+
|
|
408
|
+
# Filter out matched with low IoU
|
|
409
|
+
matches = []
|
|
410
|
+
for m in matched_indices:
|
|
411
|
+
if iou_matrix[m[0], m[1]] < iou_threshold:
|
|
412
|
+
unmatched_detections.append(m[0])
|
|
413
|
+
unmatched_trackers.append(m[1])
|
|
414
|
+
else:
|
|
415
|
+
matches.append(m.reshape(1, 2))
|
|
416
|
+
|
|
417
|
+
if len(matches) == 0:
|
|
418
|
+
matches = np.empty((0, 2), dtype=int)
|
|
419
|
+
else:
|
|
420
|
+
matches = np.concatenate(matches, axis=0)
|
|
421
|
+
|
|
422
|
+
return matches, np.array(unmatched_detections), np.array(unmatched_trackers)
|
|
423
|
+
|
|
424
|
+
def _greedy_assignment(self, cost_matrix):
|
|
425
|
+
"""Greedy assignment when scipy is not available."""
|
|
426
|
+
matches = []
|
|
427
|
+
used_rows = set()
|
|
428
|
+
used_cols = set()
|
|
429
|
+
|
|
430
|
+
# Sort by cost (descending for IoU)
|
|
431
|
+
costs = []
|
|
432
|
+
for i in range(cost_matrix.shape[0]):
|
|
433
|
+
for j in range(cost_matrix.shape[1]):
|
|
434
|
+
costs.append((cost_matrix[i, j], i, j))
|
|
435
|
+
|
|
436
|
+
costs.sort(reverse=True) # Highest IoU first
|
|
437
|
+
|
|
438
|
+
for cost, i, j in costs:
|
|
439
|
+
if i not in used_rows and j not in used_cols:
|
|
440
|
+
matches.append([i, j])
|
|
441
|
+
used_rows.add(i)
|
|
442
|
+
used_cols.add(j)
|
|
443
|
+
|
|
444
|
+
return np.array(matches)
|
|
445
|
+
|
|
446
|
+
def get_track_counts(self) -> Dict[str, int]:
|
|
447
|
+
"""Get total track counts by class."""
|
|
448
|
+
return dict(self.total_counts)
|
|
449
|
+
|
|
450
|
+
def get_active_tracks(self) -> Dict[int, Dict]:
|
|
451
|
+
"""Get currently active tracks."""
|
|
452
|
+
return self.active_tracks.copy()
|
|
453
|
+
|
|
454
|
+
def reset(self):
|
|
455
|
+
"""Reset tracking state."""
|
|
456
|
+
self.trackers = []
|
|
457
|
+
self.frame_count = 0
|
|
458
|
+
self.total_counts.clear()
|
|
459
|
+
self.class_tracks.clear()
|
|
460
|
+
self.active_tracks.clear()
|
|
461
|
+
KalmanBoxTracker.count = 0
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Alerting utilities for post-processing operations.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import time
|
|
6
|
+
from typing import List, Dict, Any
|
|
7
|
+
from .filter_utils import filter_by_confidence
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class AlertingLibrary:
|
|
11
|
+
"""Library class for handling alerting and event triggering."""
|
|
12
|
+
|
|
13
|
+
def __init__(self):
|
|
14
|
+
self.alert_history = []
|
|
15
|
+
|
|
16
|
+
def filter_by_confidence(self, results: Any, threshold: float) -> Any:
|
|
17
|
+
"""Filter results by confidence threshold."""
|
|
18
|
+
return filter_by_confidence(results, threshold)
|
|
19
|
+
|
|
20
|
+
def trigger_events(self, results: Any, category_count_threshold: Dict[str, int] = None,
|
|
21
|
+
category_triggers: List[str] = None) -> List[Dict]:
|
|
22
|
+
"""Trigger events based on detection conditions."""
|
|
23
|
+
triggered_events = []
|
|
24
|
+
|
|
25
|
+
# Count-based triggers
|
|
26
|
+
if category_count_threshold:
|
|
27
|
+
category_counts = self._count_by_category(results)
|
|
28
|
+
|
|
29
|
+
# Handle "all" threshold
|
|
30
|
+
if "all" in category_count_threshold:
|
|
31
|
+
total_detections = sum(category_counts.values())
|
|
32
|
+
if total_detections >= category_count_threshold["all"]:
|
|
33
|
+
event = {
|
|
34
|
+
"event_type": "count_threshold_exceeded",
|
|
35
|
+
"threshold": category_count_threshold["all"],
|
|
36
|
+
"actual_count": total_detections,
|
|
37
|
+
"timestamp": time.time()
|
|
38
|
+
}
|
|
39
|
+
triggered_events.append(event)
|
|
40
|
+
self.alert_history.append(event)
|
|
41
|
+
|
|
42
|
+
# Category-specific count thresholds
|
|
43
|
+
for category, threshold in category_count_threshold.items():
|
|
44
|
+
if category != "all" and category_counts.get(category, 0) >= threshold:
|
|
45
|
+
event = {
|
|
46
|
+
"event_type": "category_count_threshold_exceeded",
|
|
47
|
+
"category": category,
|
|
48
|
+
"threshold": threshold,
|
|
49
|
+
"actual_count": category_counts[category],
|
|
50
|
+
"timestamp": time.time()
|
|
51
|
+
}
|
|
52
|
+
triggered_events.append(event)
|
|
53
|
+
self.alert_history.append(event)
|
|
54
|
+
|
|
55
|
+
# Category-based triggers
|
|
56
|
+
if category_triggers:
|
|
57
|
+
detected_categories = self._get_detected_categories(results)
|
|
58
|
+
for trigger_category in category_triggers:
|
|
59
|
+
if trigger_category in detected_categories:
|
|
60
|
+
event = {
|
|
61
|
+
"event_type": "category_detected",
|
|
62
|
+
"category": trigger_category,
|
|
63
|
+
"timestamp": time.time()
|
|
64
|
+
}
|
|
65
|
+
triggered_events.append(event)
|
|
66
|
+
self.alert_history.append(event)
|
|
67
|
+
|
|
68
|
+
return triggered_events
|
|
69
|
+
|
|
70
|
+
def _count_total_detections(self, results: Any) -> int:
|
|
71
|
+
"""Count total detections in results."""
|
|
72
|
+
total_detections = 0
|
|
73
|
+
if isinstance(results, list):
|
|
74
|
+
total_detections = len(results)
|
|
75
|
+
elif isinstance(results, dict):
|
|
76
|
+
for detections in results.values():
|
|
77
|
+
if isinstance(detections, list):
|
|
78
|
+
total_detections += len(detections)
|
|
79
|
+
return total_detections
|
|
80
|
+
|
|
81
|
+
def _get_detected_categories(self, results: Any) -> set:
|
|
82
|
+
"""Get set of detected categories from results."""
|
|
83
|
+
detected_categories = set()
|
|
84
|
+
if isinstance(results, list):
|
|
85
|
+
detected_categories.update(r.get("category", "") for r in results)
|
|
86
|
+
elif isinstance(results, dict):
|
|
87
|
+
for detections in results.values():
|
|
88
|
+
if isinstance(detections, list):
|
|
89
|
+
detected_categories.update(d.get("category", "") for d in detections)
|
|
90
|
+
return detected_categories
|
|
91
|
+
|
|
92
|
+
def _count_by_category(self, results: Any) -> Dict[str, int]:
|
|
93
|
+
"""Count detections by category."""
|
|
94
|
+
category_counts = {}
|
|
95
|
+
if isinstance(results, list):
|
|
96
|
+
for result in results:
|
|
97
|
+
category = result.get("category", "unknown")
|
|
98
|
+
category_counts[category] = category_counts.get(category, 0) + 1
|
|
99
|
+
elif isinstance(results, dict):
|
|
100
|
+
for detections in results.values():
|
|
101
|
+
if isinstance(detections, list):
|
|
102
|
+
for detection in detections:
|
|
103
|
+
category = detection.get("category", "unknown")
|
|
104
|
+
category_counts[category] = category_counts.get(category, 0) + 1
|
|
105
|
+
return category_counts
|
|
106
|
+
|
|
107
|
+
def get_alert_history(self) -> List[Dict]:
|
|
108
|
+
"""Get history of triggered alerts."""
|
|
109
|
+
return self.alert_history.copy()
|
|
110
|
+
|
|
111
|
+
def clear_alert_history(self):
|
|
112
|
+
"""Clear alert history."""
|
|
113
|
+
self.alert_history.clear()
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
# Convenience alerting functions
|
|
117
|
+
class SimpleAlerter:
|
|
118
|
+
"""Simple alerter for common use cases."""
|
|
119
|
+
|
|
120
|
+
def __init__(self):
|
|
121
|
+
self.alerting_lib = AlertingLibrary()
|
|
122
|
+
|
|
123
|
+
def check_threshold_alert(self, results: Any, threshold: int, category: str = "all") -> Dict:
|
|
124
|
+
"""Check if count exceeds threshold."""
|
|
125
|
+
alerts = self.alerting_lib.trigger_events(
|
|
126
|
+
results,
|
|
127
|
+
category_count_threshold={category: threshold}
|
|
128
|
+
)
|
|
129
|
+
return {
|
|
130
|
+
"alert_triggered": len(alerts) > 0,
|
|
131
|
+
"alerts": alerts,
|
|
132
|
+
"threshold": threshold,
|
|
133
|
+
"category": category
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
def check_zone_occupancy_alert(self, zone_counts: Dict[str, int],
|
|
137
|
+
zone_thresholds: Dict[str, int]) -> Dict:
|
|
138
|
+
"""Check zone occupancy alerts."""
|
|
139
|
+
alerts = []
|
|
140
|
+
for zone_name, count in zone_counts.items():
|
|
141
|
+
if zone_name in zone_thresholds and count >= zone_thresholds[zone_name]:
|
|
142
|
+
alert = {
|
|
143
|
+
"event_type": "zone_occupancy_exceeded",
|
|
144
|
+
"zone": zone_name,
|
|
145
|
+
"count": count,
|
|
146
|
+
"threshold": zone_thresholds[zone_name],
|
|
147
|
+
"timestamp": time.time()
|
|
148
|
+
}
|
|
149
|
+
alerts.append(alert)
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
"alert_triggered": len(alerts) > 0,
|
|
153
|
+
"alerts": alerts,
|
|
154
|
+
"zone_thresholds": zone_thresholds
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
def check_dwell_time_alert(self, track_dwell_times: Dict[int, float],
|
|
158
|
+
max_dwell_time: float) -> Dict:
|
|
159
|
+
"""Check dwell time alerts."""
|
|
160
|
+
alerts = []
|
|
161
|
+
for track_id, dwell_time in track_dwell_times.items():
|
|
162
|
+
if dwell_time >= max_dwell_time:
|
|
163
|
+
alert = {
|
|
164
|
+
"event_type": "dwell_time_exceeded",
|
|
165
|
+
"track_id": track_id,
|
|
166
|
+
"dwell_time": dwell_time,
|
|
167
|
+
"threshold": max_dwell_time,
|
|
168
|
+
"timestamp": time.time()
|
|
169
|
+
}
|
|
170
|
+
alerts.append(alert)
|
|
171
|
+
|
|
172
|
+
return {
|
|
173
|
+
"alert_triggered": len(alerts) > 0,
|
|
174
|
+
"alerts": alerts,
|
|
175
|
+
"max_dwell_time": max_dwell_time
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def trigger_alerts(results: Any, category_count_threshold: Dict[str, int] = None,
|
|
180
|
+
category_triggers: List[str] = None) -> List[Dict]:
|
|
181
|
+
"""
|
|
182
|
+
Convenience function to trigger alerts.
|
|
183
|
+
|
|
184
|
+
Args:
|
|
185
|
+
results: Detection/tracking results
|
|
186
|
+
category_count_threshold: Count thresholds by category
|
|
187
|
+
category_triggers: Categories that should trigger alerts
|
|
188
|
+
|
|
189
|
+
Returns:
|
|
190
|
+
List of triggered alert events
|
|
191
|
+
"""
|
|
192
|
+
alerter = AlertingLibrary()
|
|
193
|
+
return alerter.trigger_events(results, category_count_threshold, category_triggers)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def check_threshold_alert(results: Any, threshold: int, category: str = "all") -> Dict:
|
|
197
|
+
"""Check if count exceeds threshold."""
|
|
198
|
+
alerter = SimpleAlerter()
|
|
199
|
+
return alerter.check_threshold_alert(results, threshold, category)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def check_zone_occupancy_alert(zone_counts: Dict[str, int],
|
|
203
|
+
zone_thresholds: Dict[str, int]) -> Dict:
|
|
204
|
+
"""Check zone occupancy alerts."""
|
|
205
|
+
alerter = SimpleAlerter()
|
|
206
|
+
return alerter.check_zone_occupancy_alert(zone_counts, zone_thresholds)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def check_dwell_time_alert(track_dwell_times: Dict[int, float],
|
|
210
|
+
max_dwell_time: float) -> Dict:
|
|
211
|
+
"""Check dwell time alerts."""
|
|
212
|
+
alerter = SimpleAlerter()
|
|
213
|
+
return alerter.check_dwell_time_alert(track_dwell_times, max_dwell_time)
|