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,150 @@
1
+ """
2
+ Utility functions for post-processing operations.
3
+
4
+ This module provides organized utility functions for common post-processing tasks
5
+ like geometry calculations, format conversions, counting, tracking, and filtering.
6
+ """
7
+
8
+ from .geometry_utils import (
9
+ point_in_polygon,
10
+ get_bbox_center,
11
+ calculate_distance,
12
+ calculate_bbox_overlap,
13
+ calculate_iou,
14
+ get_bbox_area,
15
+ normalize_bbox,
16
+ denormalize_bbox,
17
+ line_segments_intersect
18
+ )
19
+
20
+ from .format_utils import (
21
+ convert_to_coco_format,
22
+ convert_to_yolo_format,
23
+ convert_to_tracking_format,
24
+ convert_detection_to_tracking_format,
25
+ convert_tracking_to_detection_format,
26
+ match_results_structure
27
+ )
28
+
29
+ from .filter_utils import (
30
+ filter_by_confidence,
31
+ filter_by_categories,
32
+ calculate_bbox_fingerprint,
33
+ clean_expired_tracks,
34
+ remove_duplicate_detections,
35
+ apply_category_mapping,
36
+ filter_by_area
37
+ )
38
+
39
+ from .counting_utils import (
40
+ count_objects_by_category,
41
+ count_objects_in_zones,
42
+ count_unique_tracks,
43
+ calculate_counting_summary
44
+ )
45
+
46
+ from .tracking_utils import (
47
+ track_objects_in_zone,
48
+ detect_line_crossings,
49
+ analyze_track_movements,
50
+ filter_tracks_by_duration
51
+ )
52
+
53
+ from .smoothing_utils import (
54
+ bbox_smoothing,
55
+ BBoxSmoothingConfig,
56
+ BBoxSmoothingTracker,
57
+ create_bbox_smoothing_tracker,
58
+ create_default_smoothing_config
59
+ )
60
+
61
+ # from .color_utils import (
62
+ # extract_major_colors
63
+ # )
64
+
65
+ # Configuration utilities for easy setup
66
+ from ..core.config_utils import (
67
+ create_people_counting_config,
68
+ create_intrusion_detection_config,
69
+ create_proximity_detection_config,
70
+ create_customer_service_config,
71
+ create_advanced_customer_service_config,
72
+ create_basic_counting_tracking_config,
73
+ create_zone_from_bbox,
74
+ create_polygon_zone,
75
+ create_config_from_template,
76
+ validate_zone_polygon,
77
+ get_use_case_examples,
78
+ create_retail_store_zones,
79
+ create_office_zones
80
+ )
81
+
82
+ __all__ = [
83
+ # Geometry utilities
84
+ 'point_in_polygon',
85
+ 'get_bbox_center',
86
+ 'calculate_distance',
87
+ 'calculate_bbox_overlap',
88
+ 'calculate_iou',
89
+ 'get_bbox_area',
90
+ 'normalize_bbox',
91
+ 'denormalize_bbox',
92
+ 'line_segments_intersect',
93
+
94
+ # Format utilities
95
+ 'convert_to_coco_format',
96
+ 'convert_to_yolo_format',
97
+ 'convert_to_tracking_format',
98
+ 'convert_detection_to_tracking_format',
99
+ 'convert_tracking_to_detection_format',
100
+ 'match_results_structure',
101
+
102
+ # Filter utilities
103
+ 'filter_by_confidence',
104
+ 'filter_by_categories',
105
+ 'calculate_bbox_fingerprint',
106
+ 'clean_expired_tracks',
107
+ 'remove_duplicate_detections',
108
+ 'apply_category_mapping',
109
+ 'filter_by_area',
110
+
111
+ # Counting utilities
112
+ 'count_objects_by_category',
113
+ 'count_objects_in_zones',
114
+ 'count_unique_tracks',
115
+ 'calculate_counting_summary',
116
+
117
+ # Tracking utilities
118
+ 'track_objects_in_zone',
119
+ 'detect_line_crossings',
120
+ 'analyze_track_movements',
121
+ 'filter_tracks_by_duration',
122
+
123
+ # Smoothing utilities
124
+ 'bbox_smoothing',
125
+ 'BBoxSmoothingConfig',
126
+ 'BBoxSmoothingTracker',
127
+ 'create_bbox_smoothing_tracker',
128
+ 'create_default_smoothing_config',
129
+
130
+ # # Color utilities
131
+ # 'extract_major_colors',
132
+ # 'rgb_to_lab',
133
+ # 'lab_distance',
134
+ # 'find_nearest_color',
135
+
136
+ # Configuration utilities
137
+ 'create_people_counting_config',
138
+ 'create_customer_service_config',
139
+ 'create_intrusion_detection_config',
140
+ 'create_proximity_detection_config',
141
+ 'create_advanced_customer_service_config',
142
+ 'create_basic_counting_tracking_config',
143
+ 'create_zone_from_bbox',
144
+ 'create_polygon_zone',
145
+ 'create_config_from_template',
146
+ 'validate_zone_polygon',
147
+ 'get_use_case_examples',
148
+ 'create_retail_store_zones',
149
+ 'create_office_zones'
150
+ ]
@@ -0,0 +1,400 @@
1
+ """
2
+ Advanced counting utilities with time-based tracking and deduplication for post-processing.
3
+ """
4
+
5
+ import time
6
+ from typing import List, Dict, Any, Tuple, Optional, Set
7
+ from collections import defaultdict
8
+ from .format_utils import match_results_structure
9
+ from .geometry_utils import point_in_polygon, get_bbox_center
10
+
11
+
12
+ def calculate_bbox_overlap(bbox1: Dict, bbox2: Dict) -> float:
13
+ """Calculate overlap between two bounding boxes."""
14
+ # Convert to consistent format
15
+ if 'xmin' in bbox1:
16
+ x1_min, y1_min, x1_max, y1_max = bbox1['xmin'], bbox1['ymin'], bbox1['xmax'], bbox1['ymax']
17
+ else:
18
+ x1_min, y1_min, x1_max, y1_max = bbox1.get('x1', 0), bbox1.get('y1', 0), bbox1.get('x2', 0), bbox1.get('y2', 0)
19
+
20
+ if 'xmin' in bbox2:
21
+ x2_min, y2_min, x2_max, y2_max = bbox2['xmin'], bbox2['ymin'], bbox2['xmax'], bbox2['ymax']
22
+ else:
23
+ x2_min, y2_min, x2_max, y2_max = bbox2.get('x1', 0), bbox2.get('y1', 0), bbox2.get('x2', 0), bbox2.get('y2', 0)
24
+
25
+ # Calculate intersection
26
+ x_left = max(x1_min, x2_min)
27
+ y_top = max(y1_min, y2_min)
28
+ x_right = min(x1_max, x2_max)
29
+ y_bottom = min(y1_max, y2_max)
30
+
31
+ if x_right < x_left or y_bottom < y_top:
32
+ return 0.0
33
+
34
+ intersection_area = (x_right - x_left) * (y_bottom - y_top)
35
+
36
+ # Calculate union
37
+ bbox1_area = (x1_max - x1_min) * (y1_max - y1_min)
38
+ bbox2_area = (x2_max - x2_min) * (y2_max - y2_min)
39
+ union_area = bbox1_area + bbox2_area - intersection_area
40
+
41
+ if union_area == 0:
42
+ return 0.0
43
+
44
+ return intersection_area / union_area
45
+
46
+
47
+ def calculate_bbox_fingerprint(bbox: Dict, category: str = "") -> str:
48
+ """Calculate a fingerprint for bbox deduplication."""
49
+ if not bbox:
50
+ return f"{category}_empty"
51
+
52
+ # Normalize bbox coordinates
53
+ if 'xmin' in bbox:
54
+ x1, y1, x2, y2 = bbox['xmin'], bbox['ymin'], bbox['xmax'], bbox['ymax']
55
+ else:
56
+ x1, y1, x2, y2 = bbox.get('x1', 0), bbox.get('y1', 0), bbox.get('x2', 0), bbox.get('y2', 0)
57
+
58
+ # Round to reduce minor variations
59
+ x1, y1, x2, y2 = round(x1, 1), round(y1, 1), round(x2, 1), round(y2, 1)
60
+
61
+ return f"{category}_{x1}_{y1}_{x2}_{y2}"
62
+
63
+
64
+ def clean_expired_tracks(track_timestamps: Dict, track_last_seen: Dict,
65
+ current_timestamp: float, expiry_seconds: int):
66
+ """Clean expired tracks from tracking dictionaries."""
67
+ expired_tracks = []
68
+
69
+ for track_id, last_seen in track_last_seen.items():
70
+ if current_timestamp - last_seen > expiry_seconds:
71
+ expired_tracks.append(track_id)
72
+
73
+ for track_id in expired_tracks:
74
+ track_timestamps.pop(track_id, None)
75
+ track_last_seen.pop(track_id, None)
76
+
77
+
78
+ class CountingLibrary:
79
+ """Library class for handling object counting operations with time-based tracking."""
80
+
81
+ def __init__(self,
82
+ time_window_seconds: int = 3600,
83
+ track_expiry_seconds: int = 300,
84
+ enable_time_based_counting: bool = True,
85
+ enable_bbox_deduplication: bool = True,
86
+ bbox_similarity_threshold: float = 0.8):
87
+ """Initialize counting library with configuration."""
88
+ self.zone_counters = defaultdict(int)
89
+ self.dwell_times = defaultdict(dict)
90
+
91
+ # Time-based tracking for incremental counting
92
+ self.unique_tracks_seen = set() # All unique track IDs ever seen
93
+ self.track_timestamps = {} # track_id -> first_seen_timestamp
94
+ self.track_last_seen = {} # track_id -> last_seen_timestamp
95
+ self.zone_unique_tracks = defaultdict(set) # zone_name -> set of unique track IDs
96
+ self.zone_track_timestamps = defaultdict(dict) # zone_name -> {track_id: first_seen}
97
+
98
+ # Bounding box-based deduplication
99
+ self.bbox_fingerprints = {} # track_id -> bbox_fingerprint for deduplication
100
+ self.seen_bbox_fingerprints = set() # Set of all bbox fingerprints seen
101
+ self.category_bbox_fingerprints = defaultdict(set) # category -> set of bbox fingerprints
102
+
103
+ # Configuration
104
+ self.time_window = time_window_seconds
105
+ self.track_expiry_time = track_expiry_seconds
106
+ self.enable_time_based_counting = enable_time_based_counting
107
+ self.bbox_similarity_threshold = bbox_similarity_threshold
108
+ self.enable_bbox_deduplication = enable_bbox_deduplication
109
+
110
+ def set_time_window(self, time_window_seconds: int):
111
+ """Set the time window for statistics collection."""
112
+ self.time_window = time_window_seconds
113
+
114
+ def count_objects(self, results: Any, identification_keys: List[str] = None,
115
+ current_timestamp: Optional[float] = None) -> Tuple[Any, Dict]:
116
+ """Count objects with metadata, supporting incremental time-based counting."""
117
+ # Use default identification keys if not provided
118
+ if identification_keys is None:
119
+ identification_keys = ["track_id"]
120
+
121
+ if current_timestamp is None:
122
+ current_timestamp = time.time()
123
+
124
+ # Clean expired tracks if time-based counting is enabled
125
+ if self.enable_time_based_counting:
126
+ clean_expired_tracks(self.track_timestamps, self.track_last_seen,
127
+ current_timestamp, self.track_expiry_time)
128
+
129
+ metadata = {"total": 0, "by_category": defaultdict(int)}
130
+ results_type = match_results_structure(results)
131
+
132
+ if results_type == "detection":
133
+ metadata["total"] = len(results)
134
+ for result in results:
135
+ category = result.get("category", "unknown")
136
+ metadata["by_category"][category] += 1
137
+
138
+ elif results_type == "classification":
139
+ metadata["total"] = len(results)
140
+
141
+ elif results_type == "object_tracking":
142
+ current_unique_tracks = set()
143
+ new_tracks_this_frame = set()
144
+ unique_detections_per_category = defaultdict(set) # For proper category counting
145
+
146
+ # Keep track of processed detections to avoid duplicates
147
+ processed_detections = []
148
+
149
+ for frame_id, detections in results.items():
150
+ if isinstance(detections, list):
151
+ for detection in detections:
152
+ # Skip if this detection is a duplicate of an already processed detection
153
+ if self.enable_bbox_deduplication and self._is_duplicate_detection(detection, processed_detections):
154
+ continue
155
+
156
+ # Add to processed detections to check future duplicates
157
+ processed_detections.append(detection)
158
+
159
+ for key in identification_keys:
160
+ if key in detection:
161
+ track_id = detection[key]
162
+ current_unique_tracks.add(track_id)
163
+
164
+ # Track time-based information
165
+ if self.enable_time_based_counting:
166
+ if track_id not in self.unique_tracks_seen:
167
+ self.unique_tracks_seen.add(track_id)
168
+ self.track_timestamps[track_id] = current_timestamp
169
+ new_tracks_this_frame.add(track_id)
170
+
171
+ # Update last seen time
172
+ self.track_last_seen[track_id] = current_timestamp
173
+
174
+ category = detection.get("category", "unknown")
175
+
176
+ # Use bounding box fingerprint for unique category counting
177
+ if ("bounding_box" in detection or "bbox" in detection) and self.enable_bbox_deduplication:
178
+ bbox = detection.get("bounding_box", detection.get("bbox", {}))
179
+ bbox_fingerprint = calculate_bbox_fingerprint(bbox, category)
180
+ unique_detections_per_category[category].add(bbox_fingerprint)
181
+ else:
182
+ # Fallback to track_id based counting
183
+ unique_detections_per_category[category].add(track_id)
184
+
185
+ break # Only use first matching identification key
186
+
187
+ # Update category counts based on unique detections
188
+ for category, unique_fingerprints in unique_detections_per_category.items():
189
+ metadata["by_category"][category] = len(unique_fingerprints)
190
+
191
+ # Set counts based on counting mode
192
+ if self.enable_time_based_counting:
193
+ metadata["total"] = len(self.unique_tracks_seen)
194
+ metadata["current_frame_unique"] = len(current_unique_tracks)
195
+ metadata["new_tracks_this_frame"] = len(new_tracks_this_frame)
196
+ metadata["total_tracks_in_time_window"] = len(self._get_tracks_in_time_window(current_timestamp))
197
+ else:
198
+ metadata["total"] = len(current_unique_tracks)
199
+
200
+ # Convert defaultdict to regular dict for JSON serialization
201
+ metadata["by_category"] = dict(metadata["by_category"])
202
+
203
+ # Add time-based metadata
204
+ if self.enable_time_based_counting:
205
+ metadata["time_based_counting"] = {
206
+ "enabled": True,
207
+ "time_window_seconds": self.time_window,
208
+ "track_expiry_seconds": self.track_expiry_time,
209
+ "current_timestamp": current_timestamp,
210
+ "active_tracks": len([t for t in self.track_last_seen.values()
211
+ if current_timestamp - t <= self.track_expiry_time])
212
+ }
213
+
214
+ return results, metadata
215
+
216
+ def count_in_zones(self, results: Dict, zones: Dict[str, List[Tuple[float, float]]] = None,
217
+ current_timestamp: Optional[float] = None) -> Dict:
218
+ """Count objects in defined zones with configurable rules and time-based tracking."""
219
+ if zones is None:
220
+ zones = {}
221
+
222
+ if current_timestamp is None:
223
+ current_timestamp = time.time()
224
+
225
+ # Clean expired tracks for each zone
226
+ if self.enable_time_based_counting:
227
+ for zone_name in zones.keys():
228
+ self._clean_expired_zone_tracks(zone_name, current_timestamp)
229
+
230
+ zone_counts = {}
231
+
232
+ for zone_name, zone_polygon in zones.items():
233
+ if zone_name not in self.zone_counters:
234
+ self.zone_counters[zone_name] = 0
235
+
236
+ current_count = 0
237
+ current_frame_tracks = set()
238
+ new_zone_tracks = set()
239
+
240
+ if isinstance(results, dict):
241
+ for frame_id, detections in results.items():
242
+ if isinstance(detections, list):
243
+ for detection in detections:
244
+ if "bounding_box" in detection or "bbox" in detection:
245
+ bbox = detection.get("bounding_box", detection.get("bbox", {}))
246
+ center = get_bbox_center(bbox)
247
+
248
+ if point_in_polygon(center, zone_polygon):
249
+ # Get track ID for uniqueness
250
+ track_id = None
251
+ for key in ["track_id"]: # Default identification key
252
+ if key in detection:
253
+ track_id = detection[key]
254
+ break
255
+
256
+ if track_id is not None:
257
+ current_frame_tracks.add(track_id)
258
+
259
+ # Time-based zone tracking
260
+ if self.enable_time_based_counting:
261
+ if track_id not in self.zone_unique_tracks[zone_name]:
262
+ self.zone_unique_tracks[zone_name].add(track_id)
263
+ self.zone_track_timestamps[zone_name][track_id] = current_timestamp
264
+ new_zone_tracks.add(track_id)
265
+
266
+ current_count += 1
267
+
268
+ # Set zone counts based on counting mode
269
+ if self.enable_time_based_counting:
270
+ zone_tracks_in_window = self._get_zone_tracks_in_time_window(zone_name, current_timestamp)
271
+ zone_counts[zone_name] = {
272
+ "current_frame": len(current_frame_tracks),
273
+ "new_this_frame": len(new_zone_tracks),
274
+ "total_unique": len(self.zone_unique_tracks[zone_name]),
275
+ "in_time_window": len(zone_tracks_in_window),
276
+ "time_window_seconds": self.time_window
277
+ }
278
+ else:
279
+ zone_counts[zone_name] = {
280
+ "current_frame": len(current_frame_tracks),
281
+ "total": len(current_frame_tracks)
282
+ }
283
+
284
+ return zone_counts
285
+
286
+ def get_unique_count_by_keys(self, results: Any, keys: List[str] = None) -> Dict[str, int]:
287
+ """Get unique count based on specified keys."""
288
+ if keys is None:
289
+ keys = ["track_id"]
290
+
291
+ unique_values = set()
292
+ results_type = match_results_structure(results)
293
+
294
+ if results_type == "object_tracking":
295
+ for frame_id, detections in results.items():
296
+ if isinstance(detections, list):
297
+ for detection in detections:
298
+ for key in keys:
299
+ if key in detection:
300
+ unique_values.add(detection[key])
301
+ break
302
+
303
+ return {"unique_count": len(unique_values), "keys_used": keys}
304
+
305
+ def get_counting_statistics(self, current_timestamp: Optional[float] = None) -> Dict[str, Any]:
306
+ """Get comprehensive counting statistics."""
307
+ if current_timestamp is None:
308
+ current_timestamp = time.time()
309
+
310
+ stats = {
311
+ "total_unique_tracks": len(self.unique_tracks_seen),
312
+ "active_tracks": len([t for t in self.track_last_seen.values()
313
+ if current_timestamp - t <= self.track_expiry_time]),
314
+ "tracks_in_time_window": len(self._get_tracks_in_time_window(current_timestamp)),
315
+ "zone_statistics": {},
316
+ "configuration": {
317
+ "time_window_seconds": self.time_window,
318
+ "track_expiry_seconds": self.track_expiry_time,
319
+ "time_based_counting_enabled": self.enable_time_based_counting,
320
+ "bbox_deduplication_enabled": self.enable_bbox_deduplication,
321
+ "bbox_similarity_threshold": self.bbox_similarity_threshold
322
+ }
323
+ }
324
+
325
+ # Add zone statistics
326
+ for zone_name in self.zone_unique_tracks.keys():
327
+ zone_tracks_in_window = self._get_zone_tracks_in_time_window(zone_name, current_timestamp)
328
+ stats["zone_statistics"][zone_name] = {
329
+ "total_unique_tracks": len(self.zone_unique_tracks[zone_name]),
330
+ "tracks_in_time_window": len(zone_tracks_in_window)
331
+ }
332
+
333
+ return stats
334
+
335
+ def reset_counters(self, reset_zones: bool = True, reset_time_tracking: bool = True):
336
+ """Reset counting state."""
337
+ if reset_zones:
338
+ self.zone_counters.clear()
339
+ self.dwell_times.clear()
340
+ self.zone_unique_tracks.clear()
341
+ self.zone_track_timestamps.clear()
342
+
343
+ if reset_time_tracking:
344
+ self.unique_tracks_seen.clear()
345
+ self.track_timestamps.clear()
346
+ self.track_last_seen.clear()
347
+ self.bbox_fingerprints.clear()
348
+ self.seen_bbox_fingerprints.clear()
349
+ self.category_bbox_fingerprints.clear()
350
+
351
+ def _get_tracks_in_time_window(self, current_timestamp: float) -> Set[str]:
352
+ """Get tracks that were first seen within the time window."""
353
+ cutoff_time = current_timestamp - self.time_window
354
+ return {track_id for track_id, first_seen in self.track_timestamps.items()
355
+ if first_seen >= cutoff_time}
356
+
357
+ def _get_zone_tracks_in_time_window(self, zone_name: str, current_timestamp: float) -> Set[str]:
358
+ """Get zone tracks that were first seen within the time window."""
359
+ cutoff_time = current_timestamp - self.time_window
360
+ zone_timestamps = self.zone_track_timestamps.get(zone_name, {})
361
+ return {track_id for track_id, first_seen in zone_timestamps.items()
362
+ if first_seen >= cutoff_time}
363
+
364
+ def _clean_expired_zone_tracks(self, zone_name: str, current_timestamp: float):
365
+ """Clean expired tracks from zone tracking."""
366
+ if zone_name not in self.zone_track_timestamps:
367
+ return
368
+
369
+ expired_tracks = []
370
+ zone_timestamps = self.zone_track_timestamps[zone_name]
371
+
372
+ for track_id, first_seen in zone_timestamps.items():
373
+ if current_timestamp - first_seen > self.track_expiry_time:
374
+ expired_tracks.append(track_id)
375
+
376
+ for track_id in expired_tracks:
377
+ self.zone_unique_tracks[zone_name].discard(track_id)
378
+ self.zone_track_timestamps[zone_name].pop(track_id, None)
379
+
380
+ def _is_duplicate_detection(self, detection: Dict[str, Any], current_detections: List[Dict[str, Any]]) -> bool:
381
+ """Check if detection is a duplicate based on bbox similarity."""
382
+ if not self.enable_bbox_deduplication:
383
+ return False
384
+
385
+ detection_bbox = detection.get("bounding_box", detection.get("bbox"))
386
+ if not detection_bbox:
387
+ return False
388
+
389
+ detection_category = detection.get("category", "unknown")
390
+
391
+ for existing_detection in current_detections:
392
+ existing_bbox = existing_detection.get("bounding_box", existing_detection.get("bbox"))
393
+ existing_category = existing_detection.get("category", "unknown")
394
+
395
+ if existing_bbox and detection_category == existing_category:
396
+ overlap = calculate_bbox_overlap(detection_bbox, existing_bbox)
397
+ if overlap >= self.bbox_similarity_threshold:
398
+ return True
399
+
400
+ return False