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,510 @@
1
+ """
2
+ Comprehensive tests for the People Counting use case.
3
+
4
+ This module tests detailed functionality for people counting including:
5
+ - Zone-based analysis
6
+ - Tracking integration
7
+ - Alert generation
8
+ - Configuration validation
9
+ - Performance characteristics
10
+ """
11
+
12
+ import unittest
13
+ import json
14
+ import time
15
+ from unittest.mock import patch, MagicMock
16
+
17
+ from .test_utilities import BasePostProcessingTest
18
+ from .test_data_generators import (
19
+ create_detection_results, create_tracking_results,
20
+ create_people_counting_config, create_zone_polygons,
21
+ create_performance_test_data, create_edge_case_data
22
+ )
23
+
24
+ from src.matrice_analytics.post_processing import (
25
+ PostProcessor, ProcessingStatus, PeopleCountingConfig,
26
+ ZoneConfig, AlertConfig
27
+ )
28
+
29
+
30
+ class TestPeopleCountingUseCase(BasePostProcessingTest):
31
+ """Test cases for people counting use case."""
32
+
33
+ def test_basic_people_counting(self):
34
+ """Test basic people counting without zones."""
35
+ # Create test data with people and other objects
36
+ test_data = create_detection_results(
37
+ num_detections=20,
38
+ categories=["person", "car", "bicycle", "dog"]
39
+ )
40
+
41
+ result = self.processor.process_simple(
42
+ test_data,
43
+ usecase="people_counting",
44
+ confidence_threshold=0.5,
45
+ person_categories=["person"]
46
+ )
47
+
48
+ # Validate basic result structure
49
+ self.assert_processing_result_valid(result, expected_usecase="people_counting")
50
+ self.assertEqual(result.status, ProcessingStatus.SUCCESS)
51
+
52
+ # Check for expected metrics (using actual metric names from implementation)
53
+ self.assertIn("total_people", result.metrics)
54
+ self.assertIn("processing_time", result.metrics)
55
+ self.assertIn("input_format", result.metrics)
56
+ self.assertIn("confidence_threshold", result.metrics)
57
+
58
+ # Check zone-based metrics (using actual zone metric structure)
59
+ if result.metrics.get("zones_analyzed", 0) > 0:
60
+ self.assertIn("zone_metrics", result.metrics)
61
+
62
+ # Check people count using correct metric name
63
+ self.assertGreaterEqual(result.metrics["total_people"], 0)
64
+
65
+ # Check that insights were generated
66
+ self.assert_insights_generated(result, min_insights=1)
67
+
68
+ # Verify people count is reasonable
69
+ people_count = result.metrics["total_people"]
70
+ self.assertIsInstance(people_count, int)
71
+ self.assertGreaterEqual(people_count, 0)
72
+
73
+ def test_people_counting_with_zones(self):
74
+ """Test people counting with zone-based analysis."""
75
+ # Create test data
76
+ test_data = create_detection_results(
77
+ num_detections=15,
78
+ categories=["person", "people"]
79
+ )
80
+
81
+ # Create configuration with zones
82
+ config = create_people_counting_config(
83
+ confidence_threshold=0.6,
84
+ include_zones=True,
85
+ include_alerts=False
86
+ )
87
+
88
+ result = self.processor.process(test_data, config)
89
+
90
+ # Validate result
91
+ self.assert_processing_result_valid(result)
92
+ self.assertEqual(result.status, ProcessingStatus.SUCCESS)
93
+
94
+ # Check that zone analysis was performed
95
+ self.assertIn("zone_metrics", result.metrics)
96
+ self.assertIn("zones_analyzed", result.metrics)
97
+ self.assertGreater(result.metrics["zones_analyzed"], 0)
98
+
99
+ def test_people_counting_with_tracking(self):
100
+ """Test people counting with tracking enabled."""
101
+ # Create tracking data
102
+ tracking_data = create_tracking_results(
103
+ num_tracks=8,
104
+ categories=["person", "car"]
105
+ )
106
+
107
+ result = self.processor.process_simple(
108
+ tracking_data,
109
+ usecase="people_counting",
110
+ confidence_threshold=0.5,
111
+ enable_tracking=True,
112
+ enable_unique_counting=True
113
+ )
114
+
115
+ # Validate result
116
+ self.assert_processing_result_valid(result)
117
+ self.assertEqual(result.status, ProcessingStatus.SUCCESS)
118
+
119
+ # Check basic tracking metrics
120
+ self.assertIn("total_people", result.metrics)
121
+ self.assertIn("processing_time", result.metrics)
122
+
123
+ def test_people_counting_with_alerts(self):
124
+ """Test people counting with alert generation."""
125
+ # Create test data with many people
126
+ test_data = create_detection_results(
127
+ num_detections=25,
128
+ categories=["person"] * 20 + ["car"] * 5 # 20 people, 5 cars
129
+ )
130
+
131
+ # Create configuration with low alert thresholds
132
+ config = create_people_counting_config(
133
+ confidence_threshold=0.5,
134
+ include_alerts=True
135
+ )
136
+ # Set low thresholds to trigger alerts
137
+ config.alert_config.count_thresholds = {"person": 5, "all": 10}
138
+ config.alert_config.occupancy_thresholds = {"entrance": 3}
139
+
140
+ result = self.processor.process(test_data, config)
141
+
142
+ # Validate result
143
+ self.assert_processing_result_valid(result)
144
+ self.assertEqual(result.status, ProcessingStatus.SUCCESS)
145
+
146
+ # Check basic people counting metrics
147
+ self.assertIn("total_people", result.metrics)
148
+ self.assertGreater(result.metrics["total_people"], 0)
149
+
150
+ def test_people_counting_confidence_filtering(self):
151
+ """Test that confidence filtering works correctly."""
152
+ # Create test data with mixed confidence levels
153
+ test_data = []
154
+
155
+ # High confidence people (should be included)
156
+ for i in range(5):
157
+ test_data.append({
158
+ "bbox": [100 + i*50, 100, 140 + i*50, 200],
159
+ "confidence": 0.9,
160
+ "category": "person",
161
+ "category_id": 0
162
+ })
163
+
164
+ # Low confidence people (should be filtered out)
165
+ for i in range(5):
166
+ test_data.append({
167
+ "bbox": [100 + i*50, 300, 140 + i*50, 400],
168
+ "confidence": 0.2,
169
+ "category": "person",
170
+ "category_id": 0
171
+ })
172
+
173
+ result = self.processor.process_simple(
174
+ test_data,
175
+ usecase="people_counting",
176
+ confidence_threshold=0.5
177
+ )
178
+
179
+ # Should only count high confidence detections
180
+ self.assertEqual(result.status, ProcessingStatus.SUCCESS)
181
+ self.assertEqual(result.metrics["total_people"], 5)
182
+
183
+ def test_people_counting_category_mapping(self):
184
+ """Test people counting with category mapping."""
185
+ # Create test data with different person categories
186
+ test_data = []
187
+ categories = ["person", "people", "human", "pedestrian"]
188
+
189
+ for i, category in enumerate(categories):
190
+ for j in range(3): # 3 detections per category
191
+ test_data.append({
192
+ "bbox": [100 + j*50, 100 + i*100, 140 + j*50, 180 + i*100],
193
+ "confidence": 0.8,
194
+ "category": category,
195
+ "category_id": i
196
+ })
197
+
198
+ result = self.processor.process_simple(
199
+ test_data,
200
+ usecase="people_counting",
201
+ confidence_threshold=0.5,
202
+ person_categories=["person", "people", "human", "pedestrian"]
203
+ )
204
+
205
+ # Should count all categories as people
206
+ self.assertEqual(result.status, ProcessingStatus.SUCCESS)
207
+ self.assertEqual(result.metrics["total_people"], 12) # 4 categories * 3 detections
208
+
209
+ def test_people_counting_empty_data(self):
210
+ """Test people counting with empty input data."""
211
+ result = self.processor.process_simple(
212
+ [],
213
+ usecase="people_counting",
214
+ confidence_threshold=0.5
215
+ )
216
+
217
+ # Should handle empty data gracefully
218
+ self.assertEqual(result.status, ProcessingStatus.SUCCESS)
219
+ self.assertEqual(result.metrics["total_people"], 0)
220
+
221
+ def test_people_counting_config_validation(self):
222
+ """Test people counting configuration validation."""
223
+ # Valid configuration
224
+ valid_config = PeopleCountingConfig(
225
+ category="general",
226
+ usecase="people_counting",
227
+ confidence_threshold=0.5,
228
+ time_window_minutes=60,
229
+ person_categories=["person"]
230
+ )
231
+
232
+ errors = valid_config.validate()
233
+ self.assertEqual(len(errors), 0)
234
+
235
+ # Invalid configurations
236
+ invalid_configs = [
237
+ # Invalid confidence threshold
238
+ PeopleCountingConfig(
239
+ category="general",
240
+ usecase="people_counting",
241
+ confidence_threshold=1.5
242
+ ),
243
+ # Invalid time window
244
+ PeopleCountingConfig(
245
+ category="general",
246
+ usecase="people_counting",
247
+ time_window_minutes=-10
248
+ ),
249
+ # Empty person categories
250
+ PeopleCountingConfig(
251
+ category="general",
252
+ usecase="people_counting",
253
+ person_categories=[]
254
+ )
255
+ ]
256
+
257
+ for config in invalid_configs:
258
+ errors = config.validate()
259
+ self.assertGreater(len(errors), 0)
260
+
261
+ def test_people_counting_zone_validation(self):
262
+ """Test zone configuration validation."""
263
+ # Valid zone configuration
264
+ valid_zones = create_zone_polygons(2, zone_names=["area1", "area2"])
265
+ zone_config = ZoneConfig(zones=valid_zones)
266
+
267
+ errors = zone_config.validate()
268
+ self.assertEqual(len(errors), 0)
269
+
270
+ # Invalid zone configurations
271
+ invalid_zone_configs = [
272
+ # Zone with too few points
273
+ ZoneConfig(zones={"invalid": [[0, 0], [100, 100]]}),
274
+ # Zone with invalid point format
275
+ ZoneConfig(zones={"invalid": [[0, 0, 0], [100, 100, 100], [50, 50, 50]]}),
276
+ # Invalid confidence threshold for zone
277
+ ZoneConfig(
278
+ zones={"valid": [[0, 0], [100, 0], [100, 100], [0, 100]]},
279
+ zone_confidence_thresholds={"valid": 1.5}
280
+ )
281
+ ]
282
+
283
+ for zone_config in invalid_zone_configs:
284
+ errors = zone_config.validate()
285
+ self.assertGreater(len(errors), 0)
286
+
287
+ def test_people_counting_alert_validation(self):
288
+ """Test alert configuration validation."""
289
+ # Valid alert configuration
290
+ valid_alert_config = AlertConfig(
291
+ count_thresholds={"person": 10},
292
+ occupancy_thresholds={"entrance": 5},
293
+ alert_cooldown=30.0
294
+ )
295
+
296
+ errors = valid_alert_config.validate()
297
+ self.assertEqual(len(errors), 0)
298
+
299
+ # Invalid alert configurations
300
+ invalid_alert_configs = [
301
+ # Invalid count threshold
302
+ AlertConfig(count_thresholds={"person": -5}),
303
+ # Invalid occupancy threshold
304
+ AlertConfig(occupancy_thresholds={"entrance": 0}),
305
+ # Invalid alert cooldown
306
+ AlertConfig(alert_cooldown=-10.0),
307
+ # Webhook enabled without URL
308
+ AlertConfig(enable_webhook_alerts=True, webhook_url=None),
309
+ # Email enabled without recipients
310
+ AlertConfig(enable_email_alerts=True, email_recipients=[])
311
+ ]
312
+
313
+ for alert_config in invalid_alert_configs:
314
+ errors = alert_config.validate()
315
+ self.assertGreater(len(errors), 0)
316
+
317
+ def test_people_counting_performance(self):
318
+ """Test people counting performance with various data sizes."""
319
+ # Test different data sizes
320
+ data_sizes = [10, 50, 100]
321
+
322
+ for size in data_sizes:
323
+ with self.subTest(size=size):
324
+ # Create test data
325
+ test_data = create_detection_results(
326
+ num_detections=size,
327
+ categories=["person", "car", "bicycle"]
328
+ )
329
+
330
+ # Measure processing time
331
+ result, processing_time = self.measure_processing_time(
332
+ self.processor.process_simple,
333
+ test_data,
334
+ usecase="people_counting",
335
+ confidence_threshold=0.5
336
+ )
337
+
338
+ # Should complete successfully
339
+ self.assertEqual(result.status, ProcessingStatus.SUCCESS)
340
+
341
+ # Performance should be reasonable
342
+ self.assert_performance_acceptable(processing_time, max_time=2.0)
343
+
344
+ # Should have basic metrics
345
+ self.assertIn("total_people", result.metrics)
346
+ self.assertIn("processing_time", result.metrics)
347
+
348
+ def test_people_counting_insights_generation(self):
349
+ """Test that meaningful insights are generated."""
350
+ # Create test data
351
+ test_data = create_detection_results(
352
+ num_detections=20,
353
+ categories=["person"] * 15 + ["car"] * 5
354
+ )
355
+
356
+ # Create configuration with zones and alerts
357
+ config = create_people_counting_config(
358
+ confidence_threshold=0.5,
359
+ include_zones=True,
360
+ include_alerts=True
361
+ )
362
+
363
+ result = self.processor.process(test_data, config)
364
+
365
+ # Should generate multiple insights
366
+ self.assert_insights_generated(result, min_insights=1)
367
+
368
+ # Check insight quality
369
+ insights = result.insights
370
+
371
+ # Should mention people count
372
+ count_mentioned = any("people" in insight.lower() or "person" in insight.lower()
373
+ for insight in insights)
374
+ self.assertTrue(count_mentioned)
375
+
376
+ # Should have zone metrics if zones are configured
377
+ if config.zone_config and config.zone_config.zones:
378
+ self.assertIn("zone_metrics", result.metrics)
379
+
380
+ def test_people_counting_metrics_completeness(self):
381
+ """Test that all expected metrics are generated."""
382
+ # Create test data
383
+ test_data = create_detection_results(
384
+ num_detections=10,
385
+ categories=["person", "car"]
386
+ )
387
+
388
+ # Create comprehensive configuration
389
+ config = create_people_counting_config(
390
+ confidence_threshold=0.5,
391
+ enable_tracking=True,
392
+ include_zones=True,
393
+ include_alerts=True
394
+ )
395
+
396
+ result = self.processor.process(test_data, config)
397
+
398
+ # Check for core metrics
399
+ expected_core_metrics = [
400
+ "total_people",
401
+ "processing_time",
402
+ "input_format",
403
+ "confidence_threshold"
404
+ ]
405
+
406
+ for metric in expected_core_metrics:
407
+ self.assertIn(metric, result.metrics)
408
+
409
+ # Check for zone metrics if zones are configured
410
+ if config.zone_config and config.zone_config.zones:
411
+ self.assertIn("zone_metrics", result.metrics)
412
+ self.assertIn("zones_analyzed", result.metrics)
413
+
414
+ # Check for alert configuration existence (alerts may be generated but not always included in metrics)
415
+ if config.alert_config:
416
+ # Just validate that alert configuration exists, don't require specific metrics
417
+ self.assertIsNotNone(config.alert_config.count_thresholds)
418
+
419
+ def test_people_counting_time_window_analysis(self):
420
+ """Test time window analysis functionality."""
421
+ # Create tracking data spanning multiple time windows
422
+ tracking_data = create_tracking_results(
423
+ num_tracks=10,
424
+ track_length_range=(20, 50)
425
+ )
426
+
427
+ result = self.processor.process_simple(
428
+ tracking_data,
429
+ usecase="people_counting",
430
+ confidence_threshold=0.5,
431
+ enable_tracking=True,
432
+ time_window_minutes=30
433
+ )
434
+
435
+ # Should handle time window analysis
436
+ self.assertEqual(result.status, ProcessingStatus.SUCCESS)
437
+
438
+ # Should have basic metrics
439
+ self.assertIn("total_people", result.metrics)
440
+ self.assertIn("processing_time", result.metrics)
441
+
442
+
443
+ class TestPeopleCountingIntegration(BasePostProcessingTest):
444
+ """Integration tests for people counting with other components."""
445
+
446
+ def test_people_counting_with_mixed_data_formats(self):
447
+ """Test people counting with mixed detection and tracking data."""
448
+ # Create mixed format data
449
+ mixed_data = {
450
+ "detections": create_detection_results(5, categories=["person"]),
451
+ "tracks": create_tracking_results(3, categories=["person"]),
452
+ "metadata": {"source": "mixed_test"}
453
+ }
454
+
455
+ result = self.processor.process_simple(
456
+ mixed_data,
457
+ usecase="people_counting",
458
+ confidence_threshold=0.5
459
+ )
460
+
461
+ # Should handle mixed format gracefully
462
+ self.assertIsInstance(result.status, ProcessingStatus)
463
+
464
+ def test_people_counting_config_serialization(self):
465
+ """Test configuration serialization and deserialization."""
466
+ # Create configuration
467
+ original_config = create_people_counting_config(
468
+ confidence_threshold=0.7,
469
+ include_zones=True,
470
+ include_alerts=True
471
+ )
472
+
473
+ # Save and load configuration
474
+ config_file = self.create_temp_config_file(original_config)
475
+ loaded_config = self.processor.load_config(config_file)
476
+
477
+ # Compare key attributes
478
+ self.assertEqual(original_config.usecase, loaded_config.usecase)
479
+ self.assertEqual(original_config.category, loaded_config.category)
480
+ self.assertEqual(original_config.confidence_threshold, loaded_config.confidence_threshold)
481
+ self.assertEqual(original_config.person_categories, loaded_config.person_categories)
482
+
483
+ def test_people_counting_error_recovery(self):
484
+ """Test error recovery in people counting."""
485
+ # Create partially invalid data
486
+ mixed_quality_data = [
487
+ # Valid detection
488
+ {"bbox": [10, 10, 50, 50], "confidence": 0.8, "category": "person"},
489
+ # Invalid detection (will be filtered or cause warning)
490
+ {"bbox": [100, 100, 90, 90], "confidence": 0.9, "category": "person"}, # Invalid bbox
491
+ # Another valid detection
492
+ {"bbox": [200, 200, 240, 250], "confidence": 0.9, "category": "person"}
493
+ ]
494
+
495
+ result = self.processor.process_simple(
496
+ mixed_quality_data,
497
+ usecase="people_counting",
498
+ confidence_threshold=0.5
499
+ )
500
+
501
+ # Should handle partial failures gracefully
502
+ self.assertIn(result.status, [ProcessingStatus.SUCCESS, ProcessingStatus.WARNING])
503
+
504
+ # Should still provide some results
505
+ self.assertIsNotNone(result.data)
506
+ self.assertIsInstance(result.metrics, dict)
507
+
508
+
509
+ if __name__ == "__main__":
510
+ unittest.main(verbosity=2)