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,182 @@
1
+ """
2
+ Counting utilities for post-processing operations.
3
+ """
4
+
5
+ import time
6
+ from typing import List, Dict, Any, Tuple, Optional, Set
7
+ from collections import defaultdict
8
+
9
+ from .geometry_utils import point_in_polygon, get_bbox_center, get_bbox_bottom25_center
10
+ from .filter_utils import calculate_bbox_fingerprint
11
+
12
+
13
+ def count_objects_by_category(results: Any) -> Dict[str, int]:
14
+ """
15
+ Count objects by category from detection results.
16
+
17
+ Args:
18
+ results: Detection results (list or dict format)
19
+
20
+ Returns:
21
+ Dict[str, int]: Category counts
22
+ """
23
+ counts = defaultdict(int)
24
+
25
+ if isinstance(results, list):
26
+ # Detection format
27
+ for detection in results:
28
+ category = detection.get("category", "unknown")
29
+ counts[category] += 1
30
+
31
+ elif isinstance(results, dict):
32
+ # Frame-based format (tracking or activity recognition)
33
+ seen_tracks = set() # To avoid double counting same track across frames
34
+
35
+ for frame_id, detections in results.items():
36
+ if isinstance(detections, list):
37
+ for detection in detections:
38
+ category = detection.get("category", "unknown")
39
+ track_id = detection.get("track_id")
40
+
41
+ # If tracking data is available, count unique tracks only
42
+ if track_id is not None:
43
+ track_key = f"{category}_{track_id}"
44
+ if track_key not in seen_tracks:
45
+ seen_tracks.add(track_key)
46
+ counts[category] += 1
47
+ else:
48
+ # No tracking, count all detections
49
+ counts[category] += 1
50
+
51
+ return dict(counts)
52
+
53
+
54
+ def count_objects_in_zones(results: Any, zones: Dict[str, List[List[float]]], stream_info:Optional[Any]=None) -> Dict[str, Dict[str, int]]:
55
+ """
56
+ Count objects in defined zones.
57
+
58
+ Args:
59
+ results: Detection results
60
+ zones: Dictionary of zone_name -> polygon coordinates
61
+
62
+ Returns:
63
+ Dict[str, Dict[str, int]]: Zone counts by category
64
+ """
65
+ zone_counts = {}
66
+
67
+ for zone_name, zone_polygon in zones.items():
68
+ zone_counts[zone_name] = defaultdict(int)
69
+
70
+ if isinstance(results, list):
71
+ # Detection format
72
+ for detection in results:
73
+ if _is_detection_in_zone(detection, zone_polygon, stream_info):
74
+ category = detection.get("category", "unknown")
75
+ zone_counts[zone_name][category] += 1
76
+
77
+ elif isinstance(results, dict):
78
+ # Frame-based format
79
+ seen_tracks = set()
80
+
81
+ for frame_id, detections in results.items():
82
+ if isinstance(detections, list):
83
+ for detection in detections:
84
+ if _is_detection_in_zone(detection, zone_polygon, stream_info):
85
+ category = detection.get("category", "unknown")
86
+ track_id = detection.get("track_id")
87
+
88
+ if track_id is not None:
89
+ track_key = f"{zone_name}_{category}_{track_id}"
90
+ if track_key not in seen_tracks:
91
+ seen_tracks.add(track_key)
92
+ zone_counts[zone_name][category] += 1
93
+ else:
94
+ zone_counts[zone_name][category] += 1
95
+
96
+ # Convert to regular dict
97
+ zone_counts[zone_name] = dict(zone_counts[zone_name])
98
+
99
+ return zone_counts
100
+
101
+
102
+ def count_unique_tracks(results: Dict[str, List[Dict]]) -> Dict[str, int]:
103
+ """
104
+ Count unique tracks by category from tracking results.
105
+
106
+ Args:
107
+ results: Tracking results in frame format
108
+
109
+ Returns:
110
+ Dict[str, int]: Unique track counts by category
111
+ """
112
+ unique_tracks = defaultdict(set)
113
+
114
+ for frame_id, detections in results.items():
115
+ if isinstance(detections, list):
116
+ for detection in detections:
117
+ track_id = detection.get("track_id")
118
+ category = detection.get("category", "unknown")
119
+
120
+ if track_id is not None:
121
+ unique_tracks[category].add(track_id)
122
+
123
+ return {category: len(tracks) for category, tracks in unique_tracks.items()}
124
+
125
+
126
+ def calculate_counting_summary(results: Any, zones: Optional[Dict[str, List[List[float]]]] = None) -> Dict[str, Any]:
127
+ """
128
+ Calculate comprehensive counting summary.
129
+
130
+ Args:
131
+ results: Detection/tracking results
132
+ zones: Optional zone definitions
133
+
134
+ Returns:
135
+ Dict[str, Any]: Comprehensive counting summary
136
+ """
137
+ summary = {
138
+ "total_objects": 0,
139
+ "by_category": {},
140
+ "timestamp": time.time()
141
+ }
142
+
143
+ # Basic category counts
144
+ category_counts = count_objects_by_category(results)
145
+ summary["by_category"] = category_counts
146
+ summary["total_objects"] = sum(category_counts.values())
147
+
148
+ # Zone-based counts if zones provided
149
+ if zones:
150
+ zone_counts = count_objects_in_zones(results, zones)
151
+ summary["zone_analysis"] = zone_counts
152
+
153
+ # Calculate zone totals
154
+ zone_totals = {}
155
+ for zone_name, zone_data in zone_counts.items():
156
+ zone_totals[zone_name] = sum(zone_data.values())
157
+ summary["zone_totals"] = zone_totals
158
+
159
+ # Tracking-specific counts
160
+ if isinstance(results, dict):
161
+ unique_counts = count_unique_tracks(results)
162
+ if unique_counts:
163
+ summary["unique_tracks"] = unique_counts
164
+ summary["total_unique_tracks"] = sum(unique_counts.values())
165
+
166
+ return summary
167
+
168
+
169
+ def _is_detection_in_zone(detection: Dict[str, Any], zone_polygon: List[List[float]], stream_info:Optional[Any]=None) -> bool:
170
+ """Check if a detection is within a zone polygon."""
171
+ bbox = detection.get("bounding_box", detection.get("bbox"))
172
+ if not bbox:
173
+ return False
174
+ if stream_info: #This code ensures that if zone is bigger than the stream resolution, then whole frame is considered as in the zone.
175
+ for p in zone_polygon:
176
+ if p[0] > stream_info.get("stream_resolution",{}).get("width",0) and stream_info.get("stream_resolution",{}).get("width",0) != 0:
177
+ return True
178
+ if p[1] > stream_info.get("stream_resolution",{}).get("height",0) and stream_info.get("stream_resolution",{}).get("height",0) != 0:
179
+ return True
180
+ #center = get_bbox_center(bbox)
181
+ bottom25_center = get_bbox_bottom25_center(bbox)
182
+ return point_in_polygon(bottom25_center, [(p[0], p[1]) for p in zone_polygon])
@@ -0,0 +1,261 @@
1
+ """
2
+ Filter utilities for post-processing operations.
3
+ """
4
+
5
+ import time
6
+ from typing import List, Dict, Any, Set
7
+ from collections import defaultdict
8
+
9
+
10
+ def filter_by_confidence(results: Any, threshold: float = 0.5) -> Any:
11
+ """
12
+ Filter results by confidence threshold.
13
+
14
+ Args:
15
+ results: Detection or tracking results
16
+ threshold: Minimum confidence threshold
17
+
18
+ Returns:
19
+ Filtered results in the same format
20
+ """
21
+ if isinstance(results, list):
22
+ # Detection format
23
+ return [r for r in results if r.get("confidence", 0) >= threshold]
24
+
25
+ elif isinstance(results, dict):
26
+ # Check if it's a simple classification result
27
+ if "confidence" in results and "category" in results:
28
+ return results if results.get("confidence", 0) >= threshold else {}
29
+
30
+ # Frame-based format (tracking or activity recognition)
31
+ filtered_results = {}
32
+ for frame_id, detections in results.items():
33
+ if isinstance(detections, list):
34
+ filtered_detections = [
35
+ d for d in detections if d.get("confidence", 0) >= threshold
36
+ ]
37
+ if filtered_detections:
38
+ filtered_results[frame_id] = filtered_detections
39
+
40
+ return filtered_results
41
+
42
+ return results
43
+
44
+
45
+ def filter_by_categories(results: Any, allowed_categories: List[str]) -> Any:
46
+ """
47
+ Filter results to only include specified categories.
48
+
49
+ Args:
50
+ results: Detection or tracking results
51
+ allowed_categories: List of allowed category names
52
+
53
+ Returns:
54
+ Filtered results in the same format
55
+ """
56
+ if isinstance(results, list):
57
+ # Detection format
58
+ return [r for r in results if r.get("category", "") in allowed_categories]
59
+
60
+ elif isinstance(results, dict):
61
+ # Check if it's a simple classification result
62
+ if "category" in results:
63
+ return results if results.get("category", "") in allowed_categories else {}
64
+
65
+ # Frame-based format
66
+ filtered_results = {}
67
+ for frame_id, detections in results.items():
68
+ if isinstance(detections, list):
69
+ filtered_detections = [
70
+ d for d in detections if d.get("category", "") in allowed_categories
71
+ ]
72
+ if filtered_detections:
73
+ filtered_results[frame_id] = filtered_detections
74
+
75
+ return filtered_results
76
+
77
+ return results
78
+
79
+
80
+ def calculate_bbox_fingerprint(bbox: Dict[str, Any], category: str = "") -> str:
81
+ """
82
+ Calculate a fingerprint for a bounding box to detect duplicates.
83
+
84
+ Args:
85
+ bbox: Bounding box dictionary
86
+ category: Object category
87
+
88
+ Returns:
89
+ str: Unique fingerprint for the bbox
90
+ """
91
+ # Extract coordinates
92
+ if "xmin" in bbox:
93
+ coords = (bbox["xmin"], bbox["ymin"], bbox["xmax"], bbox["ymax"])
94
+ elif "x1" in bbox:
95
+ coords = (bbox["x1"], bbox["y1"], bbox["x2"], bbox["y2"])
96
+ else:
97
+ values = list(bbox.values())
98
+ coords = tuple(values[:4]) if len(values) >= 4 else (0, 0, 0, 0)
99
+
100
+ # Round coordinates to reduce floating point precision issues
101
+ rounded_coords = tuple(round(c, 2) for c in coords)
102
+
103
+ return f"{category}_{rounded_coords}"
104
+
105
+
106
+ def clean_expired_tracks(track_timestamps: Dict[str, float],
107
+ track_last_seen: Dict[str, float],
108
+ current_timestamp: float,
109
+ expiry_time: float) -> None:
110
+ """
111
+ Clean expired tracks from tracking dictionaries.
112
+
113
+ Args:
114
+ track_timestamps: Dictionary of track_id -> first_seen_timestamp
115
+ track_last_seen: Dictionary of track_id -> last_seen_timestamp
116
+ current_timestamp: Current timestamp
117
+ expiry_time: Time after which tracks expire
118
+ """
119
+ expired_tracks = []
120
+
121
+ for track_id, last_seen in track_last_seen.items():
122
+ if current_timestamp - last_seen > expiry_time:
123
+ expired_tracks.append(track_id)
124
+
125
+ for track_id in expired_tracks:
126
+ track_timestamps.pop(track_id, None)
127
+ track_last_seen.pop(track_id, None)
128
+
129
+
130
+ def remove_duplicate_detections(results: List[Dict[str, Any]],
131
+ similarity_threshold: float = 0.8) -> List[Dict[str, Any]]:
132
+ """
133
+ Remove duplicate detections based on bbox similarity.
134
+
135
+ Args:
136
+ results: List of detection dictionaries
137
+ similarity_threshold: IoU threshold for considering detections as duplicates
138
+
139
+ Returns:
140
+ List of unique detections
141
+ """
142
+ from .geometry_utils import calculate_iou
143
+
144
+ if not results:
145
+ return results
146
+
147
+ unique_detections = []
148
+
149
+ for detection in results:
150
+ is_duplicate = False
151
+
152
+ for existing in unique_detections:
153
+ # Check if same category
154
+ if detection.get("category") == existing.get("category"):
155
+ # Calculate IoU between bounding boxes
156
+ bbox1 = detection.get("bounding_box", detection.get("bbox", {}))
157
+ bbox2 = existing.get("bounding_box", existing.get("bbox", {}))
158
+
159
+ if bbox1 and bbox2:
160
+ iou = calculate_iou(bbox1, bbox2)
161
+ if iou >= similarity_threshold:
162
+ is_duplicate = True
163
+ # Keep the one with higher confidence
164
+ if detection.get("confidence", 0) > existing.get("confidence", 0):
165
+ unique_detections.remove(existing)
166
+ unique_detections.append(detection)
167
+ break
168
+
169
+ if not is_duplicate:
170
+ unique_detections.append(detection)
171
+
172
+ return unique_detections
173
+
174
+
175
+ def apply_category_mapping(results: Any, index_to_category: Dict[str, str]) -> Any:
176
+ """
177
+ Apply category index to name mapping.
178
+
179
+ Args:
180
+ results: Detection or tracking results
181
+ index_to_category: Mapping from category index to category name
182
+
183
+ Returns:
184
+ Results with mapped category names
185
+ """
186
+
187
+ def map_detection(
188
+ detection: Dict[str, Any], index_to_category: Dict[str, str]
189
+ ) -> Dict[str, Any]:
190
+ """Map a single detection."""
191
+ detection = detection.copy()
192
+ category_id = str(detection.get("category", detection.get("category_id")))
193
+ index_to_category = {str(k): str(v) for k, v in index_to_category.items()}
194
+ if category_id in index_to_category:
195
+ detection["category"] = index_to_category[category_id]
196
+ detection["category_id"] = category_id
197
+ return detection
198
+
199
+ if isinstance(results, list):
200
+ # Detection format
201
+ return [map_detection(r, index_to_category) for r in results]
202
+
203
+ elif isinstance(results, dict):
204
+ # Check if it's a simple classification result
205
+ if "category" in results or "category_id" in results:
206
+ return map_detection(results, index_to_category)
207
+
208
+ # Frame-based format
209
+ mapped_results = {}
210
+ for frame_id, detections in results.items():
211
+ if isinstance(detections, list):
212
+ mapped_results[frame_id] = [
213
+ map_detection(d, index_to_category) for d in detections
214
+ ]
215
+ else:
216
+ mapped_results[frame_id] = detections
217
+
218
+ return mapped_results
219
+
220
+ return results
221
+
222
+
223
+ def filter_by_area(results: Any, min_area: float = 0, max_area: float = float('inf')) -> Any:
224
+ """
225
+ Filter detections by bounding box area.
226
+
227
+ Args:
228
+ results: Detection or tracking results
229
+ min_area: Minimum bounding box area
230
+ max_area: Maximum bounding box area
231
+
232
+ Returns:
233
+ Filtered results
234
+ """
235
+ from .geometry_utils import get_bbox_area
236
+
237
+ def is_valid_area(detection: Dict[str, Any]) -> bool:
238
+ """Check if detection has valid area."""
239
+ bbox = detection.get("bounding_box", detection.get("bbox"))
240
+ if not bbox:
241
+ return True # Keep detections without bbox
242
+
243
+ area = get_bbox_area(bbox)
244
+ return min_area <= area <= max_area
245
+
246
+ if isinstance(results, list):
247
+ # Detection format
248
+ return [r for r in results if is_valid_area(r)]
249
+
250
+ elif isinstance(results, dict):
251
+ # Frame-based format
252
+ filtered_results = {}
253
+ for frame_id, detections in results.items():
254
+ if isinstance(detections, list):
255
+ filtered_detections = [d for d in detections if is_valid_area(d)]
256
+ if filtered_detections:
257
+ filtered_results[frame_id] = filtered_detections
258
+
259
+ return filtered_results
260
+
261
+ return results