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,358 @@
1
+ """
2
+ BBox smoothing utilities for post-processing operations.
3
+
4
+ This module provides generalized smoothing algorithms for bounding box detections
5
+ to reduce noise and false positives in detection results.
6
+ """
7
+
8
+ from typing import List, Dict, Any, Optional, Union
9
+ from collections import deque
10
+ from dataclasses import dataclass
11
+ import logging
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ @dataclass
17
+ class BBoxSmoothingConfig:
18
+ """Configuration for bbox smoothing algorithms."""
19
+
20
+ smoothing_algorithm: str = "observability" # "window" or "observability"
21
+ window_size: int = 20
22
+ cooldown_frames: int = 5
23
+ confidence_threshold: float = 0.5
24
+ confidence_range_factor: float = 0.5 # For observability algorithm
25
+ track_by_centroid: bool = True
26
+ centroid_quantization: int = 10
27
+ enable_smoothing: bool = True # Master flag to enable/disable smoothing
28
+
29
+ def __post_init__(self):
30
+ """Validate configuration parameters."""
31
+ if self.smoothing_algorithm not in ["window", "observability"]:
32
+ logging.error(f"Invalid smoothing_algorithm: {self.smoothing_algorithm}. Must be 'window' or 'observability'")
33
+ self.smoothing_algorithm = "observability"
34
+
35
+ # Convert window_size to int if it's a string
36
+ if isinstance(self.window_size, str):
37
+ try:
38
+ self.window_size = int(self.window_size)
39
+ except ValueError:
40
+ logging.error(f"window_size must be a valid integer, got {self.window_size}")
41
+ self.window_size = 20
42
+
43
+ if self.window_size <= 0:
44
+ logging.error(f"window_size must be positive, got {self.window_size}")
45
+ self.window_size = 20
46
+ # Convert cooldown_frames to int if it's a string
47
+ if isinstance(self.cooldown_frames, str):
48
+ try:
49
+ self.cooldown_frames = int(self.cooldown_frames)
50
+ except ValueError:
51
+ logging.error(f"cooldown_frames must be a valid integer, got {self.cooldown_frames}")
52
+ self.cooldown_frames = 5
53
+
54
+ if self.cooldown_frames < 0:
55
+ logging.error(f"cooldown_frames must be non-negative, got {self.cooldown_frames}")
56
+ self.cooldown_frames = 5
57
+ # Convert confidence_threshold to float if it's a string
58
+ if isinstance(self.confidence_threshold, str):
59
+ try:
60
+ self.confidence_threshold = float(self.confidence_threshold)
61
+ except ValueError:
62
+ logging.error(f"confidence_threshold must be a valid number, got {self.confidence_threshold}")
63
+ self.confidence_threshold = 0.5
64
+ if not 0.0 <= self.confidence_threshold <= 1.0:
65
+ logging.error(f"confidence_threshold must be between 0.0 and 1.0, got {self.confidence_threshold}")
66
+ self.confidence_threshold = 0.5
67
+
68
+ # Convert confidence_range_factor to float if it's a string
69
+ if isinstance(self.confidence_range_factor, str):
70
+ try:
71
+ self.confidence_range_factor = float(self.confidence_range_factor)
72
+ except ValueError:
73
+ logging.error(f"confidence_range_factor must be a valid number, got {self.confidence_range_factor}")
74
+ self.confidence_range_factor = 0.5
75
+
76
+ if not 0.0 <= self.confidence_range_factor <= 1.0:
77
+ logging.error(f"confidence_range_factor must be between 0.0 and 1.0, got {self.confidence_range_factor}")
78
+ self.confidence_range_factor = 0.5
79
+
80
+ # Convert centroid_quantization to int if it's a string
81
+ if isinstance(self.centroid_quantization, str):
82
+ try:
83
+ self.centroid_quantization = int(self.centroid_quantization)
84
+ except ValueError:
85
+ logging.error(f"centroid_quantization must be a valid integer, got {self.centroid_quantization}")
86
+ self.centroid_quantization = 10
87
+
88
+
89
+ class BBoxSmoothingTracker:
90
+ """Tracks individual objects for smoothing across frames."""
91
+
92
+ def __init__(self, config: BBoxSmoothingConfig):
93
+ self.config = config
94
+ self.object_windows = {} # {object_id: deque}
95
+ self.object_cooldowns = {} # {object_id: cooldown_counter}
96
+ self.logger = logging.getLogger(f"{__name__}.BBoxSmoothingTracker")
97
+
98
+ def reset(self):
99
+ """Reset tracker state."""
100
+ self.object_windows.clear()
101
+ self.object_cooldowns.clear()
102
+ self.logger.debug("BBox smoothing tracker reset")
103
+
104
+ def get_stats(self) -> Dict[str, Any]:
105
+ """Get tracker statistics."""
106
+ return {
107
+ "active_objects": len(self.object_windows),
108
+ "total_cooldowns": len(self.object_cooldowns),
109
+ "window_size": self.config.window_size,
110
+ "cooldown_frames": self.config.cooldown_frames
111
+ }
112
+
113
+
114
+ def _get_object_id(detection: Dict, config: BBoxSmoothingConfig) -> str:
115
+ """
116
+ Generate unique object ID from detection using robust hashing.
117
+
118
+ Args:
119
+ detection: Detection dictionary
120
+ config: Smoothing configuration
121
+
122
+ Returns:
123
+ str: Unique object identifier
124
+ """
125
+ # Extract bbox coordinates (handle different formats)
126
+ bbox = detection.get('bounding_box', detection.get('bbox', {}))
127
+
128
+ # Normalize bbox to consistent format
129
+ if isinstance(bbox, dict):
130
+ if 'x' in bbox and 'y' in bbox and 'width' in bbox and 'height' in bbox:
131
+ x, y, w, h = bbox['x'], bbox['y'], bbox['width'], bbox['height']
132
+ elif 'xmin' in bbox and 'ymin' in bbox and 'xmax' in bbox and 'ymax' in bbox:
133
+ x, y, w, h = bbox['xmin'], bbox['ymin'], bbox['xmax'] - bbox['xmin'], bbox['ymax'] - bbox['ymin']
134
+ elif 'x1' in bbox and 'y1' in bbox and 'x2' in bbox and 'y2' in bbox:
135
+ x, y, w, h = bbox['x1'], bbox['y1'], bbox['x2'] - bbox['x1'], bbox['y2'] - bbox['y1']
136
+ else:
137
+ # Fallback: try to extract any numeric values
138
+ values = [v for v in bbox.values() if isinstance(v, (int, float))]
139
+ if len(values) >= 4:
140
+ x, y, w, h = values[0], values[1], values[2], values[3]
141
+ else:
142
+ x, y, w, h = 0, 0, 0, 0
143
+ else:
144
+ # Handle list/tuple format
145
+ if isinstance(bbox, (list, tuple)) and len(bbox) >= 4:
146
+ x, y, w, h = bbox[0], bbox[1], bbox[2], bbox[3]
147
+ else:
148
+ x, y, w, h = 0, 0, 0, 0
149
+
150
+ # Quantize coordinates to reduce jitter (similar to centroid approach but more robust)
151
+ quantized_x = int(x // config.centroid_quantization) if hasattr(config, 'centroid_quantization') else int(x)
152
+ quantized_y = int(y // config.centroid_quantization) if hasattr(config, 'centroid_quantization') else int(y)
153
+ quantized_w = int(w // config.centroid_quantization) if hasattr(config, 'centroid_quantization') else int(w)
154
+ quantized_h = int(h // config.centroid_quantization) if hasattr(config, 'centroid_quantization') else int(h)
155
+
156
+ # Get other attributes (handle missing values)
157
+ confidence = detection.get('confidence', 0.0)
158
+ category = detection.get('category', 'unknown')
159
+
160
+ # Create hash string from quantized bbox and attributes (no track_id dependency)
161
+ hash_string = f"{quantized_x}_{quantized_y}_{quantized_w}_{quantized_h}_{confidence}_{category}"
162
+
163
+ # Generate hash and ensure it's positive
164
+ detection_hash = abs(hash(hash_string))
165
+ return f"detection_{detection_hash}"
166
+
167
+
168
+ def _apply_window_smoothing(detections: List[Dict],
169
+ config: BBoxSmoothingConfig,
170
+ tracker: BBoxSmoothingTracker) -> List[Dict]:
171
+ """
172
+ Apply window smoothing without cooldown (frame-accurate).
173
+
174
+ Args:
175
+ detections: List of detection dictionaries
176
+ config: Smoothing configuration
177
+ tracker: Tracker instance for state management
178
+
179
+ Returns:
180
+ List[Dict]: Smoothed detections (only from current frame)
181
+ """
182
+ output = []
183
+ current_object_ids = set()
184
+
185
+ # Process current detections
186
+ for det in detections:
187
+ object_id = _get_object_id(det, config)
188
+ current_object_ids.add(object_id)
189
+
190
+ # Initialize window if new object
191
+ if object_id not in tracker.object_windows:
192
+ tracker.object_windows[object_id] = deque(maxlen=config.window_size)
193
+
194
+ # Add to window
195
+ tracker.object_windows[object_id].append(det)
196
+
197
+ # Only output detections from current frame
198
+ for object_id in current_object_ids:
199
+ if object_id in tracker.object_windows:
200
+ window = tracker.object_windows[object_id]
201
+ if window:
202
+ # Calculate average confidence for smoothing
203
+ confidences = [d.get('confidence', 0.0) for d in window]
204
+ avg_confidence = sum(confidences) / len(confidences) if confidences else 0.0
205
+
206
+ # Output if above threshold
207
+ if avg_confidence >= config.confidence_threshold:
208
+ output.append(window[-1]) # Most recent detection
209
+
210
+ # Clean up unused windows (optional memory management)
211
+ for object_id in list(tracker.object_windows.keys()):
212
+ if object_id not in current_object_ids:
213
+ del tracker.object_windows[object_id]
214
+
215
+ return output
216
+
217
+
218
+ def _apply_observability_smoothing(detections: List[Dict],
219
+ config: BBoxSmoothingConfig,
220
+ tracker: BBoxSmoothingTracker) -> List[Dict]:
221
+ """
222
+ Apply observability/confidence tradeoff smoothing without cooldown (frame-accurate).
223
+
224
+ Args:
225
+ detections: List of detection dictionaries
226
+ config: Smoothing configuration
227
+ tracker: Tracker instance for state management
228
+
229
+ Returns:
230
+ List[Dict]: Smoothed detections (only from current frame)
231
+ """
232
+ output = []
233
+ current_object_ids = set()
234
+
235
+ # Process current detections
236
+ for det in detections:
237
+ object_id = _get_object_id(det, config)
238
+ current_object_ids.add(object_id)
239
+
240
+ # Initialize window if new object
241
+ if object_id not in tracker.object_windows:
242
+ tracker.object_windows[object_id] = deque(maxlen=config.window_size)
243
+
244
+ tracker.object_windows[object_id].append(det)
245
+
246
+ # Only process detections from current frame
247
+ for object_id in current_object_ids:
248
+ if object_id in tracker.object_windows:
249
+ window = tracker.object_windows[object_id]
250
+ if window:
251
+ # Calculate observability score
252
+ observability_score = len(window) / config.window_size
253
+
254
+ # Get current confidence
255
+ current_confidence = window[-1].get('confidence', 0.0)
256
+
257
+ # Define confidence range
258
+ conf_range = config.confidence_threshold * config.confidence_range_factor
259
+
260
+ # Decision logic
261
+ if current_confidence >= config.confidence_threshold:
262
+ # High confidence: always keep
263
+ output.append(window[-1])
264
+ elif current_confidence >= (config.confidence_threshold - conf_range):
265
+ # Borderline: apply tradeoff
266
+ confidence_factor = (config.confidence_threshold - current_confidence) / conf_range
267
+ if confidence_factor <= observability_score:
268
+ output.append(window[-1])
269
+ # else: too low confidence, discard
270
+
271
+ # Clean up unused windows (optional memory management)
272
+ for object_id in list(tracker.object_windows.keys()):
273
+ if object_id not in current_object_ids:
274
+ del tracker.object_windows[object_id]
275
+
276
+ return output
277
+
278
+
279
+ def bbox_smoothing(detections: Union[List[Dict], Dict[str, List[Dict]]],
280
+ config: BBoxSmoothingConfig,
281
+ tracker: Optional[BBoxSmoothingTracker] = None) -> Union[List[Dict], Dict[str, List[Dict]]]:
282
+ """
283
+ Apply smoothing algorithm to bbox detections.
284
+
285
+ Args:
286
+ detections: Either:
287
+ - List of detection dictionaries (detection format)
288
+ - Dict with frame keys containing lists of detections (tracking format)
289
+ config: Smoothing configuration
290
+ tracker: Optional tracker instance for persistent state across frames
291
+
292
+ Returns:
293
+ Same format as input: List[Dict] or Dict[str, List[Dict]]
294
+ """
295
+ # Early return if smoothing is disabled
296
+ if not config.enable_smoothing:
297
+ return detections
298
+
299
+ # Early return if no detections
300
+ if not detections:
301
+ return detections
302
+
303
+ # Create tracker if not provided
304
+ if tracker is None:
305
+ tracker = BBoxSmoothingTracker(config)
306
+
307
+ # Handle tracking format (dict with frame keys)
308
+ if isinstance(detections, dict):
309
+ smoothed_tracking_results = {}
310
+
311
+ for frame_id, frame_detections in detections.items():
312
+ if isinstance(frame_detections, list):
313
+ # Apply smoothing to this frame's detections
314
+ if config.smoothing_algorithm == "observability":
315
+ smoothed_frame = _apply_observability_smoothing(frame_detections, config, tracker)
316
+ else: # "window"
317
+ smoothed_frame = _apply_window_smoothing(frame_detections, config, tracker)
318
+
319
+ smoothed_tracking_results[frame_id] = smoothed_frame
320
+
321
+ return smoothed_tracking_results
322
+
323
+ # Handle detection format (list of detections)
324
+ elif isinstance(detections, list):
325
+ # Apply selected smoothing algorithm
326
+ if config.smoothing_algorithm == "observability":
327
+ return _apply_observability_smoothing(detections, config, tracker)
328
+ else: # "window"
329
+ return _apply_window_smoothing(detections, config, tracker)
330
+
331
+ # Fallback for unknown format
332
+ return detections
333
+
334
+
335
+ def create_bbox_smoothing_tracker(config: BBoxSmoothingConfig) -> BBoxSmoothingTracker:
336
+ """
337
+ Create a new bbox smoothing tracker instance.
338
+
339
+ Args:
340
+ config: Smoothing configuration
341
+
342
+ Returns:
343
+ BBoxSmoothingTracker: New tracker instance
344
+ """
345
+ return BBoxSmoothingTracker(config)
346
+
347
+
348
+ def create_default_smoothing_config(**overrides) -> BBoxSmoothingConfig:
349
+ """
350
+ Create default smoothing configuration with optional overrides.
351
+
352
+ Args:
353
+ **overrides: Configuration overrides
354
+
355
+ Returns:
356
+ BBoxSmoothingConfig: Configuration instance
357
+ """
358
+ return BBoxSmoothingConfig(**overrides)
@@ -0,0 +1,234 @@
1
+ """
2
+ Tracking utilities for post-processing operations.
3
+ """
4
+
5
+ from typing import List, Dict, Any, Tuple, Optional
6
+ from collections import defaultdict
7
+
8
+ from .geometry_utils import point_in_polygon, get_bbox_center, line_segments_intersect
9
+
10
+
11
+ def track_objects_in_zone(results: Any, zone_polygon: List[List[float]]) -> Dict[str, Any]:
12
+ """
13
+ Track objects within a defined zone.
14
+
15
+ Args:
16
+ results: Detection or tracking results
17
+ zone_polygon: Zone polygon coordinates [[x1,y1], [x2,y2], ...]
18
+
19
+ Returns:
20
+ Dict with zone tracking information
21
+ """
22
+ zone_tracks = []
23
+ zone_polygon_tuples = [(p[0], p[1]) for p in zone_polygon]
24
+
25
+ if isinstance(results, list):
26
+ # Detection format
27
+ for detection in results:
28
+ if _is_detection_in_zone(detection, zone_polygon_tuples):
29
+ bbox = detection.get("bounding_box", detection.get("bbox", {}))
30
+ center = get_bbox_center(bbox)
31
+
32
+ zone_tracks.append({
33
+ **detection,
34
+ "in_zone": True,
35
+ "zone_center": center
36
+ })
37
+
38
+ elif isinstance(results, dict):
39
+ # Frame-based format
40
+ for frame_id, detections in results.items():
41
+ if isinstance(detections, list):
42
+ for detection in detections:
43
+ if _is_detection_in_zone(detection, zone_polygon_tuples):
44
+ bbox = detection.get("bounding_box", detection.get("bbox", {}))
45
+ center = get_bbox_center(bbox)
46
+
47
+ zone_tracks.append({
48
+ **detection,
49
+ "frame_id": frame_id,
50
+ "in_zone": True,
51
+ "zone_center": center
52
+ })
53
+
54
+ return {
55
+ "zone_tracks": zone_tracks,
56
+ "count_in_zone": len(zone_tracks)
57
+ }
58
+
59
+
60
+ def detect_line_crossings(results: Dict[str, List[Dict]], line_points: List[List[float]],
61
+ track_history: Optional[Dict] = None) -> Dict[str, Any]:
62
+ """
63
+ Detect when tracked objects cross a virtual line.
64
+
65
+ Args:
66
+ results: Tracking results in frame format
67
+ line_points: Line coordinates [[x1,y1], [x2,y2]]
68
+ track_history: Optional track position history
69
+
70
+ Returns:
71
+ Dict with crossing information
72
+ """
73
+ if len(line_points) != 2:
74
+ return {"crossings": [], "total_crossings": 0}
75
+
76
+ line_start = tuple(line_points[0])
77
+ line_end = tuple(line_points[1])
78
+ crossings = []
79
+
80
+ if track_history is None:
81
+ track_history = {}
82
+
83
+ for frame_id, detections in results.items():
84
+ if isinstance(detections, list):
85
+ for detection in detections:
86
+ track_id = detection.get("track_id")
87
+ if track_id is None:
88
+ continue
89
+
90
+ bbox = detection.get("bounding_box", detection.get("bbox"))
91
+ if not bbox:
92
+ continue
93
+
94
+ center = get_bbox_center(bbox)
95
+
96
+ # Check for line crossing
97
+ if track_id in track_history:
98
+ prev_pos = track_history[track_id][-1] if track_history[track_id] else None
99
+
100
+ if prev_pos and line_segments_intersect(prev_pos, center, line_start, line_end):
101
+ crossings.append({
102
+ "track_id": track_id,
103
+ "frame_id": frame_id,
104
+ "position": center,
105
+ "category": detection.get("category", "unknown"),
106
+ "previous_position": prev_pos
107
+ })
108
+
109
+ # Update track history
110
+ if track_id not in track_history:
111
+ track_history[track_id] = []
112
+
113
+ track_history[track_id].append(center)
114
+
115
+ # Keep only recent positions (last 10)
116
+ if len(track_history[track_id]) > 10:
117
+ track_history[track_id] = track_history[track_id][-10:]
118
+
119
+ return {
120
+ "crossings": crossings,
121
+ "total_crossings": len(crossings),
122
+ "track_history": track_history
123
+ }
124
+
125
+
126
+ def analyze_track_movements(results: Dict[str, List[Dict]]) -> Dict[str, Any]:
127
+ """
128
+ Analyze movement patterns of tracked objects.
129
+
130
+ Args:
131
+ results: Tracking results in frame format
132
+
133
+ Returns:
134
+ Dict with movement analysis
135
+ """
136
+ track_paths = defaultdict(list)
137
+ track_stats = {}
138
+
139
+ # Collect track positions
140
+ for frame_id, detections in results.items():
141
+ if isinstance(detections, list):
142
+ for detection in detections:
143
+ track_id = detection.get("track_id")
144
+ if track_id is None:
145
+ continue
146
+
147
+ bbox = detection.get("bounding_box", detection.get("bbox"))
148
+ if bbox:
149
+ center = get_bbox_center(bbox)
150
+ track_paths[track_id].append({
151
+ "frame_id": frame_id,
152
+ "position": center,
153
+ "category": detection.get("category", "unknown")
154
+ })
155
+
156
+ # Analyze each track
157
+ for track_id, positions in track_paths.items():
158
+ if len(positions) < 2:
159
+ continue
160
+
161
+ # Calculate total distance traveled
162
+ total_distance = 0
163
+ for i in range(1, len(positions)):
164
+ prev_pos = positions[i-1]["position"]
165
+ curr_pos = positions[i]["position"]
166
+ distance = ((curr_pos[0] - prev_pos[0])**2 + (curr_pos[1] - prev_pos[1])**2)**0.5
167
+ total_distance += distance
168
+
169
+ # Calculate average speed (distance per frame)
170
+ avg_speed = total_distance / (len(positions) - 1) if len(positions) > 1 else 0
171
+
172
+ track_stats[track_id] = {
173
+ "total_frames": len(positions),
174
+ "total_distance": total_distance,
175
+ "average_speed": avg_speed,
176
+ "start_position": positions[0]["position"],
177
+ "end_position": positions[-1]["position"],
178
+ "category": positions[0]["category"]
179
+ }
180
+
181
+ return {
182
+ "track_paths": dict(track_paths),
183
+ "track_statistics": track_stats,
184
+ "total_tracks": len(track_paths)
185
+ }
186
+
187
+
188
+ def filter_tracks_by_duration(results: Dict[str, List[Dict]], min_duration: int = 5) -> Dict[str, List[Dict]]:
189
+ """
190
+ Filter tracking results to only include tracks that appear for minimum duration.
191
+
192
+ Args:
193
+ results: Tracking results in frame format
194
+ min_duration: Minimum number of frames a track must appear
195
+
196
+ Returns:
197
+ Filtered tracking results
198
+ """
199
+ track_counts = defaultdict(int)
200
+
201
+ # Count appearances per track
202
+ for frame_id, detections in results.items():
203
+ if isinstance(detections, list):
204
+ for detection in detections:
205
+ track_id = detection.get("track_id")
206
+ if track_id is not None:
207
+ track_counts[track_id] += 1
208
+
209
+ # Filter tracks that meet minimum duration
210
+ valid_tracks = {track_id for track_id, count in track_counts.items() if count >= min_duration}
211
+
212
+ # Filter results
213
+ filtered_results = {}
214
+ for frame_id, detections in results.items():
215
+ filtered_detections = []
216
+ for detection in detections:
217
+ track_id = detection.get("track_id")
218
+ if track_id is None or track_id in valid_tracks:
219
+ filtered_detections.append(detection)
220
+
221
+ if filtered_detections:
222
+ filtered_results[frame_id] = filtered_detections
223
+
224
+ return filtered_results
225
+
226
+
227
+ def _is_detection_in_zone(detection: Dict[str, Any], zone_polygon: List[Tuple[float, float]]) -> bool:
228
+ """Check if a detection is within a zone polygon."""
229
+ bbox = detection.get("bounding_box", detection.get("bbox"))
230
+ if not bbox:
231
+ return False
232
+
233
+ center = get_bbox_center(bbox)
234
+ return point_in_polygon(center, zone_polygon)
File without changes