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,667 @@
1
+ """
2
+ Basic counting with tracking use case implementation.
3
+
4
+ This module provides a simplified counting use case that combines basic object counting
5
+ with essential tracking and alerting features. It's designed for scenarios where you need
6
+ simple counting with track continuity and basic alert notifications.
7
+ """
8
+
9
+ from typing import Any, Dict, List, Optional
10
+ import time
11
+
12
+ from ..core.base import BaseProcessor, ProcessingContext, ProcessingResult, ConfigProtocol
13
+ from ..core.config import BaseConfig, TrackingConfig, AlertConfig
14
+ from ..utils import (
15
+ filter_by_confidence,
16
+ apply_category_mapping,
17
+ count_objects_by_category,
18
+ count_objects_in_zones,
19
+ count_unique_tracks,
20
+ calculate_counting_summary,
21
+ match_results_structure
22
+ )
23
+
24
+
25
+ class BasicCountingTrackingConfig(BaseConfig):
26
+ """Configuration for basic counting with tracking."""
27
+
28
+ def __init__(
29
+ self,
30
+ category: str = "general",
31
+ usecase: str = "basic_counting_tracking",
32
+ confidence_threshold: float = 0.5,
33
+ target_categories: List[str] = None,
34
+ zones: Optional[Dict[str, List[List[float]]]] = None,
35
+ enable_tracking: bool = True,
36
+ tracking_method: str = "kalman",
37
+ max_age: int = 30,
38
+ min_hits: int = 3,
39
+ count_thresholds: Optional[Dict[str, int]] = None,
40
+ zone_thresholds: Optional[Dict[str, int]] = None,
41
+ alert_cooldown: float = 60.0,
42
+ enable_unique_counting: bool = True,
43
+ index_to_category: Optional[Dict[int, str]] = None,
44
+ **kwargs
45
+ ):
46
+ """
47
+ Initialize basic counting tracking configuration.
48
+
49
+ Args:
50
+ category: Use case category
51
+ usecase: Use case name
52
+ confidence_threshold: Minimum confidence for detections
53
+ target_categories: List of categories to count
54
+ zones: Zone definitions for spatial analysis
55
+ enable_tracking: Whether to enable tracking
56
+ tracking_method: Tracking algorithm to use
57
+ max_age: Maximum age for tracks in frames
58
+ min_hits: Minimum hits before confirming track
59
+ count_thresholds: Count thresholds for alerts
60
+ zone_thresholds: Zone occupancy thresholds for alerts
61
+ alert_cooldown: Alert cooldown time in seconds
62
+ enable_unique_counting: Enable unique object counting
63
+ index_to_category: Optional mapping from class indices to category names
64
+ **kwargs: Additional parameters
65
+ """
66
+ super().__init__(
67
+ category=category,
68
+ usecase=usecase,
69
+ confidence_threshold=confidence_threshold,
70
+ enable_tracking=enable_tracking,
71
+ enable_analytics=True,
72
+ **kwargs
73
+ )
74
+
75
+ # Target categories
76
+ self.target_categories = target_categories or ["person", "people", "object"]
77
+
78
+ # Zone configuration
79
+ self.zones = zones or {}
80
+
81
+ # Category mapping
82
+ self.index_to_category = index_to_category
83
+
84
+ # Tracking configuration
85
+ self.tracking_config = None
86
+ if enable_tracking:
87
+ self.tracking_config = TrackingConfig(
88
+ tracking_method=tracking_method,
89
+ max_age=max_age,
90
+ min_hits=min_hits,
91
+ iou_threshold=0.3,
92
+ target_classes=self.target_categories
93
+ )
94
+
95
+ # Alert configuration
96
+ self.alert_config = None
97
+ if count_thresholds or zone_thresholds:
98
+ self.alert_config = AlertConfig(
99
+ count_thresholds=count_thresholds or {},
100
+ occupancy_thresholds=zone_thresholds or {},
101
+ alert_cooldown=alert_cooldown
102
+ )
103
+
104
+ # Counting settings
105
+ self.enable_unique_counting = enable_unique_counting
106
+
107
+ def validate(self) -> List[str]:
108
+ """Validate configuration."""
109
+ errors = super().validate()
110
+
111
+ if not self.target_categories:
112
+ errors.append("target_categories cannot be empty")
113
+
114
+ # Validate zones
115
+ for zone_name, polygon in self.zones.items():
116
+ if len(polygon) < 3:
117
+ errors.append(f"Zone '{zone_name}' must have at least 3 points")
118
+
119
+ for i, point in enumerate(polygon):
120
+ if len(point) != 2:
121
+ errors.append(f"Zone '{zone_name}' point {i} must have exactly 2 coordinates")
122
+
123
+ # Validate nested configurations
124
+ if self.tracking_config:
125
+ errors.extend(self.tracking_config.validate())
126
+
127
+ if self.alert_config:
128
+ errors.extend(self.alert_config.validate())
129
+
130
+ return errors
131
+
132
+
133
+ class BasicCountingTrackingUseCase(BaseProcessor):
134
+ """Basic counting with tracking use case."""
135
+
136
+ def __init__(self):
137
+ """Initialize basic counting tracking use case."""
138
+ super().__init__("basic_counting_tracking")
139
+ self.category = "general"
140
+
141
+ def get_config_schema(self) -> Dict[str, Any]:
142
+ """Get configuration schema for basic counting tracking."""
143
+ return {
144
+ "type": "object",
145
+ "properties": {
146
+ "confidence_threshold": {
147
+ "type": "number",
148
+ "minimum": 0.0,
149
+ "maximum": 1.0,
150
+ "default": 0.5,
151
+ "description": "Minimum confidence threshold for detections"
152
+ },
153
+ "target_categories": {
154
+ "type": "array",
155
+ "items": {"type": "string"},
156
+ "default": ["person", "people", "object"],
157
+ "description": "Categories to count and track"
158
+ },
159
+ "zones": {
160
+ "type": "object",
161
+ "additionalProperties": {
162
+ "type": "array",
163
+ "items": {
164
+ "type": "array",
165
+ "items": {"type": "number"},
166
+ "minItems": 2,
167
+ "maxItems": 2
168
+ },
169
+ "minItems": 3
170
+ },
171
+ "description": "Zone definitions as polygons"
172
+ },
173
+ "enable_tracking": {
174
+ "type": "boolean",
175
+ "default": True,
176
+ "description": "Enable tracking for unique counting"
177
+ },
178
+ "tracking_method": {
179
+ "type": "string",
180
+ "enum": ["kalman", "sort", "deepsort", "bytetrack"],
181
+ "default": "kalman",
182
+ "description": "Tracking algorithm to use"
183
+ },
184
+ "max_age": {
185
+ "type": "integer",
186
+ "minimum": 1,
187
+ "default": 30,
188
+ "description": "Maximum age for tracks in frames"
189
+ },
190
+ "min_hits": {
191
+ "type": "integer",
192
+ "minimum": 1,
193
+ "default": 3,
194
+ "description": "Minimum hits before confirming track"
195
+ },
196
+ "count_thresholds": {
197
+ "type": "object",
198
+ "additionalProperties": {"type": "integer", "minimum": 1},
199
+ "description": "Count thresholds for alerts"
200
+ },
201
+ "zone_thresholds": {
202
+ "type": "object",
203
+ "additionalProperties": {"type": "integer", "minimum": 1},
204
+ "description": "Zone occupancy thresholds for alerts"
205
+ },
206
+ "alert_cooldown": {
207
+ "type": "number",
208
+ "minimum": 0.0,
209
+ "default": 60.0,
210
+ "description": "Alert cooldown time in seconds"
211
+ },
212
+ "enable_unique_counting": {
213
+ "type": "boolean",
214
+ "default": True,
215
+ "description": "Enable unique object counting using tracking"
216
+ }
217
+ },
218
+ "required": ["confidence_threshold"],
219
+ "additionalProperties": False
220
+ }
221
+
222
+ def create_default_config(self, **overrides) -> BasicCountingTrackingConfig:
223
+ """Create default configuration with optional overrides."""
224
+ defaults = {
225
+ "category": self.category,
226
+ "usecase": self.name,
227
+ "confidence_threshold": 0.5,
228
+ "enable_tracking": True,
229
+ "enable_unique_counting": True,
230
+ "tracking_method": "kalman",
231
+ "max_age": 30,
232
+ "min_hits": 3,
233
+ "alert_cooldown": 60.0
234
+ }
235
+ defaults.update(overrides)
236
+ return BasicCountingTrackingConfig(**defaults)
237
+
238
+ def validate_config(self, config: ConfigProtocol) -> bool:
239
+ """Validate configuration for this use case."""
240
+ if not isinstance(config, BasicCountingTrackingConfig):
241
+ return False
242
+
243
+ errors = config.validate()
244
+ if errors:
245
+ self.logger.warning(f"Configuration validation errors: {errors}")
246
+ return False
247
+
248
+ return True
249
+
250
+ def process(self, data: Any, config: ConfigProtocol,
251
+ context: Optional[ProcessingContext] = None) -> ProcessingResult:
252
+ """
253
+ Process basic counting with tracking.
254
+
255
+ Args:
256
+ data: Raw model output (detection or tracking format)
257
+ config: Basic counting tracking configuration
258
+ context: Processing context
259
+
260
+ Returns:
261
+ ProcessingResult: Processing result with counting and tracking analytics
262
+ """
263
+ start_time = time.time()
264
+
265
+ try:
266
+ # Ensure we have the right config type
267
+ if not isinstance(config, BasicCountingTrackingConfig):
268
+ return self.create_error_result(
269
+ "Invalid configuration type for basic counting tracking",
270
+ usecase=self.name,
271
+ category=self.category,
272
+ context=context
273
+ )
274
+
275
+ # Initialize processing context if not provided
276
+ if context is None:
277
+ context = ProcessingContext()
278
+
279
+ # Detect input format
280
+ input_format = match_results_structure(data)
281
+ context.input_format = input_format
282
+ context.confidence_threshold = config.confidence_threshold
283
+
284
+ self.logger.info(f"Processing basic counting tracking with format: {input_format.value}")
285
+
286
+ # Step 1: Apply confidence filtering
287
+ processed_data = data
288
+ if config.confidence_threshold is not None:
289
+ processed_data = filter_by_confidence(processed_data, config.confidence_threshold)
290
+ self.logger.debug(f"Applied confidence filtering with threshold {config.confidence_threshold}")
291
+
292
+ # Step 2: Apply category mapping if provided
293
+ if config.index_to_category:
294
+ processed_data = apply_category_mapping(processed_data, config.index_to_category)
295
+ self.logger.debug("Applied category mapping")
296
+
297
+ # Step 3: Calculate basic counting summary
298
+ counting_summary = calculate_counting_summary(
299
+ processed_data,
300
+ zones=config.zones
301
+ )
302
+
303
+ # Step 4: Zone-based analysis if zones are configured
304
+ zone_analysis = {}
305
+ if config.zones:
306
+ zone_analysis = count_objects_in_zones(processed_data, config.zones)
307
+ self.logger.debug(f"Analyzed {len(config.zones)} zones")
308
+
309
+ # Step 5: Unique tracking analysis if enabled
310
+ tracking_analysis = {}
311
+ if config.enable_tracking and config.enable_unique_counting:
312
+ tracking_analysis = self._analyze_tracking(processed_data, config)
313
+ self.logger.debug("Performed tracking analysis")
314
+
315
+ # Step 6: Generate insights and alerts
316
+ insights = self._generate_insights(counting_summary, zone_analysis, tracking_analysis, config)
317
+ alerts = self._check_alerts(counting_summary, zone_analysis, config)
318
+
319
+ # Step 7: Calculate metrics
320
+ metrics = self._calculate_metrics(counting_summary, zone_analysis, tracking_analysis, config, context)
321
+
322
+ # Step 8: Extract predictions
323
+ predictions = self._extract_predictions(processed_data)
324
+
325
+ # Step 9: Generate summary
326
+ summary = self._generate_summary(counting_summary, zone_analysis, tracking_analysis, alerts)
327
+
328
+ # Mark processing as completed
329
+ context.mark_completed()
330
+
331
+ # Create successful result
332
+ result = self.create_result(
333
+ data={
334
+ "counting_summary": counting_summary,
335
+ "zone_analysis": zone_analysis,
336
+ "tracking_analysis": tracking_analysis,
337
+ "alerts": alerts,
338
+ "total_objects": counting_summary.get("total_objects", 0),
339
+ "zones_count": len(config.zones) if config.zones else 0,
340
+ "tracking_enabled": config.enable_tracking
341
+ },
342
+ usecase=self.name,
343
+ category=self.category,
344
+ context=context
345
+ )
346
+
347
+ # Add human-readable information
348
+ result.summary = summary
349
+ result.insights = insights
350
+ result.predictions = predictions
351
+ result.metrics = metrics
352
+
353
+ # Add warnings
354
+ if config.confidence_threshold and config.confidence_threshold < 0.3:
355
+ result.add_warning(f"Low confidence threshold ({config.confidence_threshold}) may result in false positives")
356
+
357
+ if config.enable_tracking and not any(item.get("track_id") for item in self._flatten_data(processed_data)):
358
+ result.add_warning("Tracking enabled but no track IDs found in input data")
359
+
360
+ self.logger.info(f"Basic counting tracking completed successfully in {result.processing_time:.2f}s")
361
+ return result
362
+
363
+ except Exception as e:
364
+ self.logger.error(f"Basic counting tracking failed: {str(e)}", exc_info=True)
365
+
366
+ if context:
367
+ context.mark_completed()
368
+
369
+ return self.create_error_result(
370
+ str(e),
371
+ type(e).__name__,
372
+ usecase=self.name,
373
+ category=self.category,
374
+ context=context
375
+ )
376
+
377
+ def _analyze_tracking(self, data: Any, config: BasicCountingTrackingConfig) -> Dict[str, Any]:
378
+ """Analyze tracking data for unique counting."""
379
+ tracking_analysis = {
380
+ "unique_tracks": 0,
381
+ "active_tracks": 0,
382
+ "track_categories": {},
383
+ "track_zones": {}
384
+ }
385
+
386
+ try:
387
+ if isinstance(data, dict):
388
+ # Frame-based tracking data
389
+ unique_tracks = count_unique_tracks(data)
390
+ tracking_analysis["unique_tracks"] = sum(unique_tracks.values())
391
+ tracking_analysis["track_categories"] = unique_tracks
392
+
393
+ # Count active tracks in current frame
394
+ latest_frame = max(data.keys()) if data else None
395
+ if latest_frame and isinstance(data[latest_frame], list):
396
+ active_track_ids = set()
397
+ for item in data[latest_frame]:
398
+ track_id = item.get("track_id")
399
+ if track_id is not None:
400
+ active_track_ids.add(track_id)
401
+ tracking_analysis["active_tracks"] = len(active_track_ids)
402
+
403
+ elif isinstance(data, list):
404
+ # Detection format with track IDs
405
+ unique_track_ids = set()
406
+ track_categories = {}
407
+
408
+ for item in data:
409
+ track_id = item.get("track_id")
410
+ category = item.get("category", "unknown")
411
+
412
+ if track_id is not None:
413
+ unique_track_ids.add(track_id)
414
+ if category not in track_categories:
415
+ track_categories[category] = set()
416
+ track_categories[category].add(track_id)
417
+
418
+ tracking_analysis["unique_tracks"] = len(unique_track_ids)
419
+ tracking_analysis["active_tracks"] = len(unique_track_ids)
420
+ tracking_analysis["track_categories"] = {
421
+ cat: len(tracks) for cat, tracks in track_categories.items()
422
+ }
423
+
424
+ # Zone-based tracking analysis
425
+ if config.zones:
426
+ zone_tracks = {}
427
+ for zone_name in config.zones:
428
+ zone_tracks[zone_name] = 0
429
+
430
+ # This would require more complex zone intersection logic
431
+ # For now, we'll keep it simple
432
+ tracking_analysis["track_zones"] = zone_tracks
433
+
434
+ except Exception as e:
435
+ self.logger.warning(f"Tracking analysis failed: {str(e)}")
436
+
437
+ return tracking_analysis
438
+
439
+ def _generate_insights(self, counting_summary: Dict, zone_analysis: Dict,
440
+ tracking_analysis: Dict, config: BasicCountingTrackingConfig) -> List[str]:
441
+ """Generate human-readable insights."""
442
+ insights = []
443
+
444
+ total_objects = counting_summary.get("total_objects", 0)
445
+
446
+ if total_objects == 0:
447
+ insights.append("No objects detected in the scene")
448
+ return insights
449
+
450
+ # Basic counting insights
451
+ insights.append(f"Detected {total_objects} objects in total")
452
+
453
+ # Category breakdown
454
+ category_counts = counting_summary.get("by_category", {})
455
+ for category, count in category_counts.items():
456
+ if count > 0 and category in config.target_categories:
457
+ percentage = (count / total_objects) * 100
458
+ insights.append(f"Category '{category}': {count} objects ({percentage:.1f}% of total)")
459
+
460
+ # Zone insights
461
+ if zone_analysis:
462
+ zones_with_objects = sum(1 for zone_counts in zone_analysis.values()
463
+ if (sum(zone_counts.values()) if isinstance(zone_counts, dict) else zone_counts) > 0)
464
+ insights.append(f"Objects detected in {zones_with_objects}/{len(zone_analysis)} zones")
465
+
466
+ for zone_name, zone_counts in zone_analysis.items():
467
+ zone_total = sum(zone_counts.values()) if isinstance(zone_counts, dict) else zone_counts
468
+ if zone_total > 0:
469
+ percentage = (zone_total / total_objects) * 100
470
+ insights.append(f"Zone '{zone_name}': {zone_total} objects ({percentage:.1f}% of total)")
471
+
472
+ # Tracking insights
473
+ if config.enable_tracking and tracking_analysis:
474
+ unique_tracks = tracking_analysis.get("unique_tracks", 0)
475
+ active_tracks = tracking_analysis.get("active_tracks", 0)
476
+
477
+ if unique_tracks > 0:
478
+ insights.append(f"Tracking: {unique_tracks} unique objects, {active_tracks} currently active")
479
+
480
+ if unique_tracks != total_objects:
481
+ efficiency = (unique_tracks / total_objects) * 100 if total_objects > 0 else 0
482
+ insights.append(f"Tracking efficiency: {efficiency:.1f}% ({unique_tracks}/{total_objects} tracked)")
483
+
484
+ # Track category breakdown
485
+ track_categories = tracking_analysis.get("track_categories", {})
486
+ for category, count in track_categories.items():
487
+ if count > 0:
488
+ insights.append(f"Tracked '{category}': {count} unique objects")
489
+
490
+ return insights
491
+
492
+ def _check_alerts(self, counting_summary: Dict, zone_analysis: Dict,
493
+ config: BasicCountingTrackingConfig) -> List[Dict]:
494
+ """Check for alert conditions."""
495
+ alerts = []
496
+
497
+ if not config.alert_config:
498
+ return alerts
499
+
500
+ total_objects = counting_summary.get("total_objects", 0)
501
+
502
+ # Count threshold alerts
503
+ for category, threshold in config.alert_config.count_thresholds.items():
504
+ if category == "all" and total_objects >= threshold:
505
+ alerts.append({
506
+ "type": "count_threshold",
507
+ "severity": "warning",
508
+ "message": f"Total object count ({total_objects}) exceeds threshold ({threshold})",
509
+ "category": category,
510
+ "current_count": total_objects,
511
+ "threshold": threshold,
512
+ "timestamp": time.time()
513
+ })
514
+ elif category in counting_summary.get("by_category", {}):
515
+ count = counting_summary["by_category"][category]
516
+ if count >= threshold:
517
+ alerts.append({
518
+ "type": "count_threshold",
519
+ "severity": "warning",
520
+ "message": f"{category} count ({count}) exceeds threshold ({threshold})",
521
+ "category": category,
522
+ "current_count": count,
523
+ "threshold": threshold,
524
+ "timestamp": time.time()
525
+ })
526
+
527
+ # Zone occupancy alerts
528
+ for zone_name, threshold in config.alert_config.occupancy_thresholds.items():
529
+ if zone_name in zone_analysis:
530
+ zone_count = sum(zone_analysis[zone_name].values()) if isinstance(zone_analysis[zone_name], dict) else zone_analysis[zone_name]
531
+ if zone_count >= threshold:
532
+ alerts.append({
533
+ "type": "zone_occupancy",
534
+ "severity": "warning",
535
+ "message": f"Zone '{zone_name}' occupancy ({zone_count}) exceeds threshold ({threshold})",
536
+ "zone": zone_name,
537
+ "current_occupancy": zone_count,
538
+ "threshold": threshold,
539
+ "timestamp": time.time()
540
+ })
541
+
542
+ return alerts
543
+
544
+ def _calculate_metrics(self, counting_summary: Dict, zone_analysis: Dict,
545
+ tracking_analysis: Dict, config: BasicCountingTrackingConfig,
546
+ context: ProcessingContext) -> Dict[str, Any]:
547
+ """Calculate detailed metrics."""
548
+ total_objects = counting_summary.get("total_objects", 0)
549
+
550
+ metrics = {
551
+ "total_objects": total_objects,
552
+ "processing_time": context.processing_time or 0.0,
553
+ "input_format": context.input_format.value,
554
+ "confidence_threshold": config.confidence_threshold,
555
+ "zones_analyzed": len(zone_analysis),
556
+ "tracking_enabled": config.enable_tracking,
557
+ "unique_counting_enabled": config.enable_unique_counting
558
+ }
559
+
560
+ # Zone metrics
561
+ if zone_analysis:
562
+ zone_metrics = {}
563
+ for zone_name, zone_counts in zone_analysis.items():
564
+ zone_total = sum(zone_counts.values()) if isinstance(zone_counts, dict) else zone_counts
565
+ zone_metrics[zone_name] = {
566
+ "count": zone_total,
567
+ "percentage": (zone_total / total_objects) * 100 if total_objects > 0 else 0
568
+ }
569
+ metrics["zone_metrics"] = zone_metrics
570
+
571
+ # Tracking metrics
572
+ if config.enable_tracking and tracking_analysis:
573
+ unique_tracks = tracking_analysis.get("unique_tracks", 0)
574
+ active_tracks = tracking_analysis.get("active_tracks", 0)
575
+
576
+ metrics.update({
577
+ "unique_tracks": unique_tracks,
578
+ "active_tracks": active_tracks,
579
+ "tracking_efficiency": (unique_tracks / total_objects) * 100 if total_objects > 0 else 0,
580
+ "track_categories": tracking_analysis.get("track_categories", {})
581
+ })
582
+
583
+ # Category metrics
584
+ category_counts = counting_summary.get("by_category", {})
585
+ category_metrics = {}
586
+ for category, count in category_counts.items():
587
+ if category in config.target_categories:
588
+ category_metrics[category] = {
589
+ "count": count,
590
+ "percentage": (count / total_objects) * 100 if total_objects > 0 else 0
591
+ }
592
+ metrics["category_metrics"] = category_metrics
593
+
594
+ return metrics
595
+
596
+ def _extract_predictions(self, data: Any) -> List[Dict[str, Any]]:
597
+ """Extract predictions from processed data."""
598
+ predictions = []
599
+
600
+ try:
601
+ flattened_data = self._flatten_data(data)
602
+ for item in flattened_data:
603
+ prediction = self._normalize_prediction(item)
604
+ if prediction:
605
+ predictions.append(prediction)
606
+ except Exception as e:
607
+ self.logger.warning(f"Failed to extract predictions: {str(e)}")
608
+
609
+ return predictions
610
+
611
+ def _flatten_data(self, data: Any) -> List[Dict[str, Any]]:
612
+ """Flatten data structure to list of items."""
613
+ items = []
614
+
615
+ if isinstance(data, list):
616
+ items.extend(data)
617
+ elif isinstance(data, dict):
618
+ for frame_id, frame_data in data.items():
619
+ if isinstance(frame_data, list):
620
+ for item in frame_data:
621
+ if isinstance(item, dict):
622
+ item["frame_id"] = frame_id
623
+ items.append(item)
624
+
625
+ return items
626
+
627
+ def _normalize_prediction(self, item: Dict[str, Any]) -> Dict[str, Any]:
628
+ """Normalize a single prediction item."""
629
+ if not isinstance(item, dict):
630
+ return {}
631
+
632
+ return {
633
+ "category": item.get("category", item.get("class", "unknown")),
634
+ "confidence": item.get("confidence", item.get("score", 0.0)),
635
+ "bounding_box": item.get("bounding_box", item.get("bbox", {})),
636
+ "track_id": item.get("track_id"),
637
+ "frame_id": item.get("frame_id")
638
+ }
639
+
640
+ def _generate_summary(self, counting_summary: Dict, zone_analysis: Dict,
641
+ tracking_analysis: Dict, alerts: List) -> str:
642
+ """Generate human-readable summary."""
643
+ total_objects = counting_summary.get("total_objects", 0)
644
+
645
+ if total_objects == 0:
646
+ return "No objects detected"
647
+
648
+ summary_parts = [f"{total_objects} objects detected"]
649
+
650
+ # Add tracking info
651
+ if tracking_analysis:
652
+ unique_tracks = tracking_analysis.get("unique_tracks", 0)
653
+ if unique_tracks > 0:
654
+ summary_parts.append(f"{unique_tracks} unique tracks")
655
+
656
+ # Add zone info
657
+ if zone_analysis:
658
+ zones_with_objects = sum(1 for zone_counts in zone_analysis.values()
659
+ if (sum(zone_counts.values()) if isinstance(zone_counts, dict) else zone_counts) > 0)
660
+ summary_parts.append(f"in {zones_with_objects}/{len(zone_analysis)} zones")
661
+
662
+ # Add alert info
663
+ if alerts:
664
+ alert_count = len(alerts)
665
+ summary_parts.append(f"with {alert_count} alert{'s' if alert_count != 1 else ''}")
666
+
667
+ return ", ".join(summary_parts)