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.
Files changed (196) hide show
  1. matrice_analytics/__init__.py +28 -0
  2. matrice_analytics/boundary_drawing_internal/README.md +305 -0
  3. matrice_analytics/boundary_drawing_internal/__init__.py +45 -0
  4. matrice_analytics/boundary_drawing_internal/boundary_drawing_internal.py +1207 -0
  5. matrice_analytics/boundary_drawing_internal/boundary_drawing_tool.py +429 -0
  6. matrice_analytics/boundary_drawing_internal/boundary_tool_template.html +1036 -0
  7. matrice_analytics/boundary_drawing_internal/data/.gitignore +12 -0
  8. matrice_analytics/boundary_drawing_internal/example_usage.py +206 -0
  9. matrice_analytics/boundary_drawing_internal/usage/README.md +110 -0
  10. matrice_analytics/boundary_drawing_internal/usage/boundary_drawer_launcher.py +102 -0
  11. matrice_analytics/boundary_drawing_internal/usage/simple_boundary_launcher.py +107 -0
  12. matrice_analytics/post_processing/README.md +455 -0
  13. matrice_analytics/post_processing/__init__.py +732 -0
  14. matrice_analytics/post_processing/advanced_tracker/README.md +650 -0
  15. matrice_analytics/post_processing/advanced_tracker/__init__.py +17 -0
  16. matrice_analytics/post_processing/advanced_tracker/base.py +99 -0
  17. matrice_analytics/post_processing/advanced_tracker/config.py +77 -0
  18. matrice_analytics/post_processing/advanced_tracker/kalman_filter.py +370 -0
  19. matrice_analytics/post_processing/advanced_tracker/matching.py +195 -0
  20. matrice_analytics/post_processing/advanced_tracker/strack.py +230 -0
  21. matrice_analytics/post_processing/advanced_tracker/tracker.py +367 -0
  22. matrice_analytics/post_processing/config.py +146 -0
  23. matrice_analytics/post_processing/core/__init__.py +63 -0
  24. matrice_analytics/post_processing/core/base.py +704 -0
  25. matrice_analytics/post_processing/core/config.py +3291 -0
  26. matrice_analytics/post_processing/core/config_utils.py +925 -0
  27. matrice_analytics/post_processing/face_reg/__init__.py +43 -0
  28. matrice_analytics/post_processing/face_reg/compare_similarity.py +556 -0
  29. matrice_analytics/post_processing/face_reg/embedding_manager.py +950 -0
  30. matrice_analytics/post_processing/face_reg/face_recognition.py +2234 -0
  31. matrice_analytics/post_processing/face_reg/face_recognition_client.py +606 -0
  32. matrice_analytics/post_processing/face_reg/people_activity_logging.py +321 -0
  33. matrice_analytics/post_processing/ocr/__init__.py +0 -0
  34. matrice_analytics/post_processing/ocr/easyocr_extractor.py +250 -0
  35. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/__init__.py +9 -0
  36. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/__init__.py +4 -0
  37. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/cli.py +33 -0
  38. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/dataset_stats.py +139 -0
  39. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/export.py +398 -0
  40. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/train.py +447 -0
  41. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/utils.py +129 -0
  42. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/valid.py +93 -0
  43. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/validate_dataset.py +240 -0
  44. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/visualize_augmentation.py +176 -0
  45. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/visualize_predictions.py +96 -0
  46. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/core/__init__.py +3 -0
  47. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/core/process.py +246 -0
  48. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/core/types.py +60 -0
  49. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/core/utils.py +87 -0
  50. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/inference/__init__.py +3 -0
  51. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/inference/config.py +82 -0
  52. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/inference/hub.py +141 -0
  53. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/inference/plate_recognizer.py +323 -0
  54. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/py.typed +0 -0
  55. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/__init__.py +0 -0
  56. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/data/__init__.py +0 -0
  57. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/data/augmentation.py +101 -0
  58. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/data/dataset.py +97 -0
  59. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/__init__.py +0 -0
  60. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/config.py +114 -0
  61. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/layers.py +553 -0
  62. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/loss.py +55 -0
  63. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/metric.py +86 -0
  64. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/model_builders.py +95 -0
  65. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/model_schema.py +395 -0
  66. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/utilities/__init__.py +0 -0
  67. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/utilities/backend_utils.py +38 -0
  68. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/utilities/utils.py +214 -0
  69. matrice_analytics/post_processing/ocr/postprocessing.py +270 -0
  70. matrice_analytics/post_processing/ocr/preprocessing.py +52 -0
  71. matrice_analytics/post_processing/post_processor.py +1175 -0
  72. matrice_analytics/post_processing/test_cases/__init__.py +1 -0
  73. matrice_analytics/post_processing/test_cases/run_tests.py +143 -0
  74. matrice_analytics/post_processing/test_cases/test_advanced_customer_service.py +841 -0
  75. matrice_analytics/post_processing/test_cases/test_basic_counting_tracking.py +523 -0
  76. matrice_analytics/post_processing/test_cases/test_comprehensive.py +531 -0
  77. matrice_analytics/post_processing/test_cases/test_config.py +852 -0
  78. matrice_analytics/post_processing/test_cases/test_customer_service.py +585 -0
  79. matrice_analytics/post_processing/test_cases/test_data_generators.py +583 -0
  80. matrice_analytics/post_processing/test_cases/test_people_counting.py +510 -0
  81. matrice_analytics/post_processing/test_cases/test_processor.py +524 -0
  82. matrice_analytics/post_processing/test_cases/test_usecases.py +165 -0
  83. matrice_analytics/post_processing/test_cases/test_utilities.py +356 -0
  84. matrice_analytics/post_processing/test_cases/test_utils.py +743 -0
  85. matrice_analytics/post_processing/usecases/Histopathological_Cancer_Detection_img.py +604 -0
  86. matrice_analytics/post_processing/usecases/__init__.py +267 -0
  87. matrice_analytics/post_processing/usecases/abandoned_object_detection.py +797 -0
  88. matrice_analytics/post_processing/usecases/advanced_customer_service.py +1601 -0
  89. matrice_analytics/post_processing/usecases/age_detection.py +842 -0
  90. matrice_analytics/post_processing/usecases/age_gender_detection.py +1085 -0
  91. matrice_analytics/post_processing/usecases/anti_spoofing_detection.py +656 -0
  92. matrice_analytics/post_processing/usecases/assembly_line_detection.py +841 -0
  93. matrice_analytics/post_processing/usecases/banana_defect_detection.py +624 -0
  94. matrice_analytics/post_processing/usecases/basic_counting_tracking.py +667 -0
  95. matrice_analytics/post_processing/usecases/blood_cancer_detection_img.py +881 -0
  96. matrice_analytics/post_processing/usecases/car_damage_detection.py +834 -0
  97. matrice_analytics/post_processing/usecases/car_part_segmentation.py +946 -0
  98. matrice_analytics/post_processing/usecases/car_service.py +1601 -0
  99. matrice_analytics/post_processing/usecases/cardiomegaly_classification.py +864 -0
  100. matrice_analytics/post_processing/usecases/cell_microscopy_segmentation.py +897 -0
  101. matrice_analytics/post_processing/usecases/chicken_pose_detection.py +648 -0
  102. matrice_analytics/post_processing/usecases/child_monitoring.py +814 -0
  103. matrice_analytics/post_processing/usecases/color/clip.py +660 -0
  104. matrice_analytics/post_processing/usecases/color/clip_processor/merges.txt +48895 -0
  105. matrice_analytics/post_processing/usecases/color/clip_processor/preprocessor_config.json +28 -0
  106. matrice_analytics/post_processing/usecases/color/clip_processor/special_tokens_map.json +30 -0
  107. matrice_analytics/post_processing/usecases/color/clip_processor/tokenizer.json +245079 -0
  108. matrice_analytics/post_processing/usecases/color/clip_processor/tokenizer_config.json +32 -0
  109. matrice_analytics/post_processing/usecases/color/clip_processor/vocab.json +1 -0
  110. matrice_analytics/post_processing/usecases/color/color_map_utils.py +70 -0
  111. matrice_analytics/post_processing/usecases/color/color_mapper.py +468 -0
  112. matrice_analytics/post_processing/usecases/color_detection.py +1936 -0
  113. matrice_analytics/post_processing/usecases/color_map_utils.py +70 -0
  114. matrice_analytics/post_processing/usecases/concrete_crack_detection.py +827 -0
  115. matrice_analytics/post_processing/usecases/crop_weed_detection.py +781 -0
  116. matrice_analytics/post_processing/usecases/customer_service.py +1008 -0
  117. matrice_analytics/post_processing/usecases/defect_detection_products.py +936 -0
  118. matrice_analytics/post_processing/usecases/distracted_driver_detection.py +822 -0
  119. matrice_analytics/post_processing/usecases/drone_traffic_monitoring.py +585 -0
  120. matrice_analytics/post_processing/usecases/drowsy_driver_detection.py +829 -0
  121. matrice_analytics/post_processing/usecases/dwell_detection.py +829 -0
  122. matrice_analytics/post_processing/usecases/emergency_vehicle_detection.py +827 -0
  123. matrice_analytics/post_processing/usecases/face_emotion.py +813 -0
  124. matrice_analytics/post_processing/usecases/face_recognition.py +827 -0
  125. matrice_analytics/post_processing/usecases/fashion_detection.py +835 -0
  126. matrice_analytics/post_processing/usecases/field_mapping.py +902 -0
  127. matrice_analytics/post_processing/usecases/fire_detection.py +1146 -0
  128. matrice_analytics/post_processing/usecases/flare_analysis.py +836 -0
  129. matrice_analytics/post_processing/usecases/flower_segmentation.py +1006 -0
  130. matrice_analytics/post_processing/usecases/gas_leak_detection.py +837 -0
  131. matrice_analytics/post_processing/usecases/gender_detection.py +832 -0
  132. matrice_analytics/post_processing/usecases/human_activity_recognition.py +871 -0
  133. matrice_analytics/post_processing/usecases/intrusion_detection.py +1672 -0
  134. matrice_analytics/post_processing/usecases/leaf.py +821 -0
  135. matrice_analytics/post_processing/usecases/leaf_disease.py +840 -0
  136. matrice_analytics/post_processing/usecases/leak_detection.py +837 -0
  137. matrice_analytics/post_processing/usecases/license_plate_detection.py +1188 -0
  138. matrice_analytics/post_processing/usecases/license_plate_monitoring.py +1781 -0
  139. matrice_analytics/post_processing/usecases/litter_monitoring.py +717 -0
  140. matrice_analytics/post_processing/usecases/mask_detection.py +869 -0
  141. matrice_analytics/post_processing/usecases/natural_disaster.py +907 -0
  142. matrice_analytics/post_processing/usecases/parking.py +787 -0
  143. matrice_analytics/post_processing/usecases/parking_space_detection.py +822 -0
  144. matrice_analytics/post_processing/usecases/pcb_defect_detection.py +888 -0
  145. matrice_analytics/post_processing/usecases/pedestrian_detection.py +808 -0
  146. matrice_analytics/post_processing/usecases/people_counting.py +706 -0
  147. matrice_analytics/post_processing/usecases/people_counting_bckp.py +1683 -0
  148. matrice_analytics/post_processing/usecases/people_tracking.py +1842 -0
  149. matrice_analytics/post_processing/usecases/pipeline_detection.py +605 -0
  150. matrice_analytics/post_processing/usecases/plaque_segmentation_img.py +874 -0
  151. matrice_analytics/post_processing/usecases/pothole_segmentation.py +915 -0
  152. matrice_analytics/post_processing/usecases/ppe_compliance.py +645 -0
  153. matrice_analytics/post_processing/usecases/price_tag_detection.py +822 -0
  154. matrice_analytics/post_processing/usecases/proximity_detection.py +1901 -0
  155. matrice_analytics/post_processing/usecases/road_lane_detection.py +623 -0
  156. matrice_analytics/post_processing/usecases/road_traffic_density.py +832 -0
  157. matrice_analytics/post_processing/usecases/road_view_segmentation.py +915 -0
  158. matrice_analytics/post_processing/usecases/shelf_inventory_detection.py +583 -0
  159. matrice_analytics/post_processing/usecases/shoplifting_detection.py +822 -0
  160. matrice_analytics/post_processing/usecases/shopping_cart_analysis.py +899 -0
  161. matrice_analytics/post_processing/usecases/skin_cancer_classification_img.py +864 -0
  162. matrice_analytics/post_processing/usecases/smoker_detection.py +833 -0
  163. matrice_analytics/post_processing/usecases/solar_panel.py +810 -0
  164. matrice_analytics/post_processing/usecases/suspicious_activity_detection.py +1030 -0
  165. matrice_analytics/post_processing/usecases/template_usecase.py +380 -0
  166. matrice_analytics/post_processing/usecases/theft_detection.py +648 -0
  167. matrice_analytics/post_processing/usecases/traffic_sign_monitoring.py +724 -0
  168. matrice_analytics/post_processing/usecases/underground_pipeline_defect_detection.py +775 -0
  169. matrice_analytics/post_processing/usecases/underwater_pollution_detection.py +842 -0
  170. matrice_analytics/post_processing/usecases/vehicle_monitoring.py +1029 -0
  171. matrice_analytics/post_processing/usecases/warehouse_object_segmentation.py +899 -0
  172. matrice_analytics/post_processing/usecases/waterbody_segmentation.py +923 -0
  173. matrice_analytics/post_processing/usecases/weapon_detection.py +771 -0
  174. matrice_analytics/post_processing/usecases/weld_defect_detection.py +615 -0
  175. matrice_analytics/post_processing/usecases/wildlife_monitoring.py +898 -0
  176. matrice_analytics/post_processing/usecases/windmill_maintenance.py +834 -0
  177. matrice_analytics/post_processing/usecases/wound_segmentation.py +856 -0
  178. matrice_analytics/post_processing/utils/__init__.py +150 -0
  179. matrice_analytics/post_processing/utils/advanced_counting_utils.py +400 -0
  180. matrice_analytics/post_processing/utils/advanced_helper_utils.py +317 -0
  181. matrice_analytics/post_processing/utils/advanced_tracking_utils.py +461 -0
  182. matrice_analytics/post_processing/utils/alerting_utils.py +213 -0
  183. matrice_analytics/post_processing/utils/category_mapping_utils.py +94 -0
  184. matrice_analytics/post_processing/utils/color_utils.py +592 -0
  185. matrice_analytics/post_processing/utils/counting_utils.py +182 -0
  186. matrice_analytics/post_processing/utils/filter_utils.py +261 -0
  187. matrice_analytics/post_processing/utils/format_utils.py +293 -0
  188. matrice_analytics/post_processing/utils/geometry_utils.py +300 -0
  189. matrice_analytics/post_processing/utils/smoothing_utils.py +358 -0
  190. matrice_analytics/post_processing/utils/tracking_utils.py +234 -0
  191. matrice_analytics/py.typed +0 -0
  192. matrice_analytics-0.1.60.dist-info/METADATA +481 -0
  193. matrice_analytics-0.1.60.dist-info/RECORD +196 -0
  194. matrice_analytics-0.1.60.dist-info/WHEEL +5 -0
  195. matrice_analytics-0.1.60.dist-info/licenses/LICENSE.txt +21 -0
  196. 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)