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,531 @@
1
+ """
2
+ Comprehensive test suite for post processing module.
3
+
4
+ This module provides comprehensive test coverage for all post processing functionality
5
+ with correct API usage and data formats.
6
+ """
7
+
8
+ import unittest
9
+ import time
10
+ import tempfile
11
+ import json
12
+ import os
13
+ from typing import Dict, List, Any
14
+
15
+ # Fix imports for proper module resolution
16
+ import sys
17
+ sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../../src'))
18
+
19
+ from src.matrice_analytics.post_processing import (
20
+ PostProcessor, ProcessingContext, ProcessingStatus, ProcessingResult,
21
+ PeopleCountingConfig, CustomerServiceConfig,
22
+ point_in_polygon, get_bbox_center, calculate_distance, calculate_iou,
23
+ get_bbox_area, normalize_bbox, denormalize_bbox, line_segments_intersect,
24
+ convert_to_coco_format, convert_to_yolo_format, match_results_structure,
25
+ filter_by_confidence, filter_by_categories, count_objects_by_category,
26
+ count_objects_in_zones, calculate_counting_summary
27
+ )
28
+ from src.matrice_analytics.post_processing.usecases.basic_counting_tracking import (
29
+ BasicCountingTrackingUseCase, BasicCountingTrackingConfig
30
+ )
31
+
32
+
33
+ class TestPostProcessorCore(unittest.TestCase):
34
+ """Test core PostProcessor functionality."""
35
+
36
+ def setUp(self):
37
+ """Set up test environment."""
38
+ self.processor = PostProcessor()
39
+ self.temp_dir = tempfile.mkdtemp()
40
+
41
+ def tearDown(self):
42
+ """Clean up test environment."""
43
+ import shutil
44
+ if os.path.exists(self.temp_dir):
45
+ shutil.rmtree(self.temp_dir)
46
+
47
+ def test_processor_initialization(self):
48
+ """Test PostProcessor initialization."""
49
+ processor = PostProcessor()
50
+ self.assertIsNotNone(processor)
51
+
52
+ # Check statistics initialization
53
+ stats = processor.get_statistics()
54
+ self.assertEqual(stats["total_processed"], 0)
55
+ self.assertEqual(stats["successful"], 0)
56
+ self.assertEqual(stats["failed"], 0)
57
+
58
+ def test_simple_people_counting(self):
59
+ """Test simple people counting processing."""
60
+ # Create test detection data
61
+ detections = [
62
+ {"bbox": [10, 20, 50, 60], "confidence": 0.8, "category": "person"},
63
+ {"bbox": [100, 100, 150, 200], "confidence": 0.9, "category": "person"},
64
+ {"bbox": [200, 200, 250, 300], "confidence": 0.7, "category": "car"}
65
+ ]
66
+
67
+ # Process with simple interface
68
+ result = self.processor.process_simple(
69
+ detections,
70
+ "people_counting",
71
+ confidence_threshold=0.6
72
+ )
73
+
74
+ # Verify result
75
+ self.assertIsInstance(result, ProcessingResult)
76
+ self.assertIn(result.status, [ProcessingStatus.SUCCESS, ProcessingStatus.WARNING])
77
+ self.assertIsNotNone(result.data)
78
+ self.assertGreater(len(result.insights), 0)
79
+
80
+ def test_people_counting_with_zones(self):
81
+ """Test people counting with zone configuration."""
82
+ detections = [
83
+ {"bbox": [25, 25, 75, 75], "confidence": 0.8, "category": "person"},
84
+ {"bbox": [125, 125, 175, 175], "confidence": 0.9, "category": "person"}
85
+ ]
86
+
87
+ zones = {
88
+ "entrance": [[0, 0], [100, 0], [100, 100], [0, 100]],
89
+ "lobby": [[100, 100], [200, 100], [200, 200], [100, 200]]
90
+ }
91
+
92
+ # Use zone_config parameter structure
93
+ result = self.processor.process_simple(
94
+ detections,
95
+ "people_counting",
96
+ confidence_threshold=0.5,
97
+ zone_config={"zones": zones}
98
+ )
99
+
100
+ self.assertIn(result.status, [ProcessingStatus.SUCCESS, ProcessingStatus.WARNING])
101
+ self.assertIsNotNone(result.data)
102
+
103
+ def test_configuration_creation(self):
104
+ """Test configuration creation."""
105
+ # Test people counting config
106
+ config = self.processor.create_config(
107
+ "people_counting",
108
+ confidence_threshold=0.7,
109
+ enable_tracking=True
110
+ )
111
+
112
+ self.assertIsInstance(config, PeopleCountingConfig)
113
+ self.assertEqual(config.confidence_threshold, 0.7)
114
+ self.assertTrue(config.enable_tracking)
115
+
116
+ def test_statistics_tracking(self):
117
+ """Test processing statistics tracking."""
118
+ detections = [{"bbox": [10, 20, 50, 60], "confidence": 0.8, "category": "person"}]
119
+
120
+ # Process multiple times
121
+ for _ in range(3):
122
+ self.processor.process_simple(detections, "people_counting")
123
+
124
+ stats = self.processor.get_statistics()
125
+ self.assertEqual(stats["total_processed"], 3)
126
+ self.assertGreaterEqual(stats["successful"], 0)
127
+
128
+
129
+ class TestGeometryUtils(unittest.TestCase):
130
+ """Test geometry utility functions."""
131
+
132
+ def test_point_in_polygon(self):
133
+ """Test point in polygon detection."""
134
+ # Square polygon
135
+ square = [(0, 0), (100, 0), (100, 100), (0, 100)]
136
+
137
+ # Test points inside
138
+ self.assertTrue(point_in_polygon((50, 50), square))
139
+ self.assertTrue(point_in_polygon((10, 10), square))
140
+ self.assertTrue(point_in_polygon((90, 90), square))
141
+
142
+ # Test points outside
143
+ self.assertFalse(point_in_polygon((150, 50), square))
144
+ self.assertFalse(point_in_polygon((50, 150), square))
145
+ self.assertFalse(point_in_polygon((-10, 50), square))
146
+
147
+ def test_get_bbox_center(self):
148
+ """Test bounding box center calculation."""
149
+ # Test list format
150
+ bbox_list = [10, 20, 50, 60]
151
+ center = get_bbox_center(bbox_list)
152
+ self.assertEqual(center, (30.0, 40.0))
153
+
154
+ # Test dict format
155
+ bbox_dict = {"xmin": 10, "ymin": 20, "xmax": 50, "ymax": 60}
156
+ center = get_bbox_center(bbox_dict)
157
+ self.assertEqual(center, (30.0, 40.0))
158
+
159
+ def test_calculate_distance(self):
160
+ """Test distance calculation."""
161
+ point1 = (0, 0)
162
+ point2 = (3, 4)
163
+ distance = calculate_distance(point1, point2)
164
+ self.assertEqual(distance, 5.0) # 3-4-5 triangle
165
+
166
+ def test_calculate_iou(self):
167
+ """Test IoU calculation with dict format."""
168
+ # Identical boxes
169
+ bbox1 = {"xmin": 0, "ymin": 0, "xmax": 100, "ymax": 100}
170
+ bbox2 = {"xmin": 0, "ymin": 0, "xmax": 100, "ymax": 100}
171
+ iou = calculate_iou(bbox1, bbox2)
172
+ self.assertEqual(iou, 1.0)
173
+
174
+ # Non-overlapping boxes
175
+ bbox1 = {"xmin": 0, "ymin": 0, "xmax": 50, "ymax": 50}
176
+ bbox2 = {"xmin": 100, "ymin": 100, "xmax": 150, "ymax": 150}
177
+ iou = calculate_iou(bbox1, bbox2)
178
+ self.assertEqual(iou, 0.0)
179
+
180
+ # Partially overlapping boxes
181
+ bbox1 = {"xmin": 0, "ymin": 0, "xmax": 100, "ymax": 100}
182
+ bbox2 = {"xmin": 50, "ymin": 50, "xmax": 150, "ymax": 150}
183
+ iou = calculate_iou(bbox1, bbox2)
184
+ self.assertGreater(iou, 0.0)
185
+ self.assertLess(iou, 1.0)
186
+
187
+ def test_get_bbox_area(self):
188
+ """Test bounding box area calculation."""
189
+ bbox = {"xmin": 0, "ymin": 0, "xmax": 100, "ymax": 50}
190
+ area = get_bbox_area(bbox)
191
+ self.assertEqual(area, 5000.0)
192
+
193
+ def test_normalize_denormalize_bbox(self):
194
+ """Test bbox normalization and denormalization."""
195
+ bbox = {"xmin": 100, "ymin": 200, "xmax": 300, "ymax": 400}
196
+ image_width, image_height = 640, 480
197
+
198
+ # Normalize
199
+ normalized = normalize_bbox(bbox, image_width, image_height)
200
+ self.assertAlmostEqual(normalized["xmin"], 100/640)
201
+ self.assertAlmostEqual(normalized["ymin"], 200/480)
202
+
203
+ # Denormalize back
204
+ denormalized = denormalize_bbox(normalized, image_width, image_height)
205
+ self.assertAlmostEqual(denormalized["xmin"], 100)
206
+ self.assertAlmostEqual(denormalized["ymin"], 200)
207
+
208
+ def test_line_segments_intersect(self):
209
+ """Test line segment intersection detection."""
210
+ # Intersecting lines
211
+ p1, p2 = (0, 0), (100, 100)
212
+ p3, p4 = (0, 100), (100, 0)
213
+ self.assertTrue(line_segments_intersect(p1, p2, p3, p4))
214
+
215
+ # Non-intersecting lines
216
+ p1, p2 = (0, 0), (50, 50)
217
+ p3, p4 = (100, 100), (150, 150)
218
+ self.assertFalse(line_segments_intersect(p1, p2, p3, p4))
219
+
220
+
221
+ class TestFormatUtils(unittest.TestCase):
222
+ """Test format utility functions."""
223
+
224
+ def test_match_results_structure(self):
225
+ """Test result structure matching."""
226
+ # Detection format
227
+ detections = [{"bbox": [0, 0, 100, 100], "confidence": 0.8, "category": "person"}]
228
+ format_type = match_results_structure(detections)
229
+ self.assertEqual(format_type.value, "detection")
230
+
231
+ # Classification format
232
+ classification = {"category": "person", "confidence": 0.8}
233
+ format_type = match_results_structure(classification)
234
+ self.assertEqual(format_type.value, "classification")
235
+
236
+ def test_convert_to_coco_format(self):
237
+ """Test conversion to COCO format."""
238
+ detections = [
239
+ {"bounding_box": {"xmin": 10, "ymin": 20, "xmax": 50, "ymax": 60}, "confidence": 0.8, "category": "person"},
240
+ {"bounding_box": {"xmin": 100, "ymin": 100, "xmax": 150, "ymax": 200}, "confidence": 0.9, "category": "car"}
241
+ ]
242
+
243
+ coco_format = convert_to_coco_format(detections)
244
+
245
+ self.assertEqual(len(coco_format), 2)
246
+
247
+ # Check first detection
248
+ first = coco_format[0]
249
+ self.assertIn("bbox", first)
250
+ self.assertIn("score", first)
251
+ self.assertIn("category", first)
252
+ self.assertEqual(first["score"], 0.8)
253
+ self.assertEqual(first["category"], "person")
254
+
255
+ def test_convert_to_yolo_format(self):
256
+ """Test conversion to YOLO format."""
257
+ detections = [
258
+ {"bounding_box": {"xmin": 10, "ymin": 20, "xmax": 50, "ymax": 60}, "confidence": 0.8, "category": "person"}
259
+ ]
260
+
261
+ yolo_format = convert_to_yolo_format(detections)
262
+
263
+ self.assertEqual(len(yolo_format), 1)
264
+ self.assertEqual(len(yolo_format[0]), 6) # [class_id, x_center, y_center, width, height, confidence]
265
+
266
+
267
+ class TestFilterUtils(unittest.TestCase):
268
+ """Test filter utility functions."""
269
+
270
+ def test_filter_by_confidence(self):
271
+ """Test confidence-based filtering."""
272
+ detections = [
273
+ {"bbox": [0, 0, 100, 100], "confidence": 0.9, "category": "person"},
274
+ {"bbox": [100, 100, 200, 200], "confidence": 0.3, "category": "person"},
275
+ {"bbox": [200, 200, 300, 300], "confidence": 0.7, "category": "car"}
276
+ ]
277
+
278
+ filtered = filter_by_confidence(detections, 0.5)
279
+ self.assertEqual(len(filtered), 2) # Only 0.9 and 0.7 confidence detections
280
+
281
+ # Check that low confidence detection was filtered out
282
+ confidences = [d["confidence"] for d in filtered]
283
+ self.assertNotIn(0.3, confidences)
284
+
285
+ def test_filter_by_categories(self):
286
+ """Test category-based filtering."""
287
+ detections = [
288
+ {"bbox": [0, 0, 100, 100], "confidence": 0.8, "category": "person"},
289
+ {"bbox": [100, 100, 200, 200], "confidence": 0.8, "category": "car"},
290
+ {"bbox": [200, 200, 300, 300], "confidence": 0.8, "category": "bike"}
291
+ ]
292
+
293
+ filtered = filter_by_categories(detections, ["person", "car"])
294
+ self.assertEqual(len(filtered), 2)
295
+
296
+ categories = [d["category"] for d in filtered]
297
+ self.assertIn("person", categories)
298
+ self.assertIn("car", categories)
299
+ self.assertNotIn("bike", categories)
300
+
301
+
302
+ class TestCountingUtils(unittest.TestCase):
303
+ """Test counting utility functions."""
304
+
305
+ def test_count_objects_by_category(self):
306
+ """Test object counting by category."""
307
+ detections = [
308
+ {"bbox": [0, 0, 100, 100], "confidence": 0.8, "category": "person"},
309
+ {"bbox": [100, 100, 200, 200], "confidence": 0.8, "category": "person"},
310
+ {"bbox": [200, 200, 300, 300], "confidence": 0.8, "category": "car"}
311
+ ]
312
+
313
+ counts = count_objects_by_category(detections)
314
+ self.assertEqual(counts["person"], 2)
315
+ self.assertEqual(counts["car"], 1)
316
+
317
+ def test_count_objects_in_zones(self):
318
+ """Test zone-based object counting."""
319
+ detections = [
320
+ {"bbox": [25, 25, 75, 75], "confidence": 0.8, "category": "person"}, # In zone1
321
+ {"bbox": [125, 25, 175, 75], "confidence": 0.7, "category": "person"}, # In zone2
322
+ {"bbox": [225, 225, 275, 275], "confidence": 0.9, "category": "car"} # Outside zones
323
+ ]
324
+
325
+ zones = {
326
+ "zone1": [[0, 0], [100, 0], [100, 100], [0, 100]],
327
+ "zone2": [[100, 0], [200, 0], [200, 100], [100, 100]]
328
+ }
329
+
330
+ zone_counts = count_objects_in_zones(detections, zones)
331
+
332
+ # Check that we have zone analysis
333
+ self.assertIn("zone1", zone_counts)
334
+ self.assertIn("zone2", zone_counts)
335
+
336
+ # Check that person in zone1 is counted
337
+ self.assertIn("person", zone_counts["zone1"])
338
+ self.assertEqual(zone_counts["zone1"]["person"], 1)
339
+
340
+ def test_calculate_counting_summary(self):
341
+ """Test comprehensive counting summary."""
342
+ detections = [
343
+ {"bbox": [25, 25, 75, 75], "confidence": 0.8, "category": "person"},
344
+ {"bbox": [125, 25, 175, 75], "confidence": 0.7, "category": "person"},
345
+ {"bbox": [225, 225, 275, 275], "confidence": 0.9, "category": "car"}
346
+ ]
347
+
348
+ zones = {
349
+ "zone1": [[0, 0], [100, 0], [100, 100], [0, 100]]
350
+ }
351
+
352
+ summary = calculate_counting_summary(detections, zones=zones)
353
+
354
+ # Check summary structure
355
+ self.assertIn("total_objects", summary)
356
+ self.assertIn("by_category", summary)
357
+ self.assertEqual(summary["total_objects"], 3)
358
+ self.assertEqual(summary["by_category"]["person"], 2)
359
+ self.assertEqual(summary["by_category"]["car"], 1)
360
+
361
+
362
+ class TestBasicCountingTracking(unittest.TestCase):
363
+ """Test basic counting tracking use case."""
364
+
365
+ def setUp(self):
366
+ """Set up test environment."""
367
+ self.use_case = BasicCountingTrackingUseCase()
368
+ self.processor = PostProcessor()
369
+
370
+ def test_config_creation(self):
371
+ """Test BasicCountingTrackingConfig creation."""
372
+ config = BasicCountingTrackingConfig(
373
+ confidence_threshold=0.7,
374
+ target_categories=["person", "car"],
375
+ zones={"entrance": [[0, 0], [100, 0], [100, 100], [0, 100]]}
376
+ )
377
+
378
+ self.assertEqual(config.confidence_threshold, 0.7)
379
+ self.assertEqual(config.target_categories, ["person", "car"])
380
+ self.assertIn("entrance", config.zones)
381
+
382
+ # Validate config
383
+ errors = config.validate()
384
+ self.assertEqual(len(errors), 0, f"Config validation failed: {errors}")
385
+
386
+ def test_basic_processing(self):
387
+ """Test basic counting tracking processing."""
388
+ detections = [
389
+ {"bbox": [25, 25, 75, 75], "confidence": 0.8, "category": "person"},
390
+ {"bbox": [125, 125, 175, 175], "confidence": 0.9, "category": "person"}
391
+ ]
392
+
393
+ config = BasicCountingTrackingConfig(
394
+ confidence_threshold=0.5,
395
+ target_categories=["person"]
396
+ )
397
+
398
+ context = ProcessingContext()
399
+ result = self.use_case.process(detections, config, context)
400
+
401
+ self.assertIsInstance(result, ProcessingResult)
402
+ self.assertIn(result.status, [ProcessingStatus.SUCCESS, ProcessingStatus.WARNING])
403
+ self.assertIsNotNone(result.data)
404
+
405
+ def test_zone_based_processing(self):
406
+ """Test zone-based processing."""
407
+ detections = [
408
+ {"bbox": [25, 25, 75, 75], "confidence": 0.8, "category": "person"},
409
+ {"bbox": [125, 125, 175, 175], "confidence": 0.9, "category": "person"}
410
+ ]
411
+
412
+ config = BasicCountingTrackingConfig(
413
+ confidence_threshold=0.5,
414
+ target_categories=["person"],
415
+ zones={
416
+ "zone1": [[0, 0], [100, 0], [100, 100], [0, 100]],
417
+ "zone2": [[100, 100], [200, 100], [200, 200], [100, 200]]
418
+ }
419
+ )
420
+
421
+ result = self.use_case.process(detections, config)
422
+
423
+ self.assertIn(result.status, [ProcessingStatus.SUCCESS, ProcessingStatus.WARNING])
424
+ self.assertIsNotNone(result.data)
425
+
426
+ # Check that zone analysis is included
427
+ if "zone_analysis" in result.data:
428
+ zone_analysis = result.data["zone_analysis"]
429
+ self.assertIsInstance(zone_analysis, dict)
430
+
431
+
432
+ class TestIntegration(unittest.TestCase):
433
+ """Test integration scenarios."""
434
+
435
+ def setUp(self):
436
+ """Set up test environment."""
437
+ self.processor = PostProcessor()
438
+ self.temp_dir = tempfile.mkdtemp()
439
+
440
+ def tearDown(self):
441
+ """Clean up test environment."""
442
+ import shutil
443
+ if os.path.exists(self.temp_dir):
444
+ shutil.rmtree(self.temp_dir)
445
+
446
+ def test_end_to_end_people_counting(self):
447
+ """Test end-to-end people counting scenario."""
448
+ # Create realistic detection data
449
+ detections = [
450
+ {"bbox": [50, 50, 100, 150], "confidence": 0.85, "category": "person"},
451
+ {"bbox": [200, 100, 250, 200], "confidence": 0.92, "category": "person"},
452
+ {"bbox": [300, 50, 400, 100], "confidence": 0.78, "category": "car"},
453
+ {"bbox": [450, 150, 500, 250], "confidence": 0.65, "category": "person"}
454
+ ]
455
+
456
+ # Define zones for entrance monitoring
457
+ zones = {
458
+ "entrance": [[0, 0], [150, 0], [150, 300], [0, 300]],
459
+ "lobby": [[150, 0], [400, 0], [400, 300], [150, 300]],
460
+ "exit": [[400, 0], [550, 0], [550, 300], [400, 300]]
461
+ }
462
+
463
+ # Process with people counting using correct zone_config structure
464
+ result = self.processor.process_simple(
465
+ detections,
466
+ "people_counting",
467
+ confidence_threshold=0.7,
468
+ zone_config={"zones": zones},
469
+ person_categories=["person"]
470
+ )
471
+
472
+ # Verify results
473
+ self.assertIn(result.status, [ProcessingStatus.SUCCESS, ProcessingStatus.WARNING])
474
+ self.assertIsNotNone(result.data)
475
+ self.assertGreater(len(result.insights), 0)
476
+
477
+ # Check that processing time is recorded
478
+ self.assertGreaterEqual(result.processing_time, 0)
479
+
480
+ def test_configuration_file_workflow(self):
481
+ """Test configuration file save/load workflow."""
482
+ # Create configuration
483
+ config = self.processor.create_config(
484
+ "people_counting",
485
+ confidence_threshold=0.8,
486
+ enable_tracking=True
487
+ )
488
+
489
+ # Save to file
490
+ config_file = os.path.join(self.temp_dir, "test_config.json")
491
+ self.processor.save_config(config, config_file)
492
+
493
+ # Verify file exists
494
+ self.assertTrue(os.path.exists(config_file))
495
+
496
+ # Load configuration
497
+ loaded_config = self.processor.load_config(config_file)
498
+
499
+ # Verify loaded config
500
+ self.assertEqual(loaded_config.confidence_threshold, 0.8)
501
+ self.assertEqual(loaded_config.enable_tracking, True)
502
+
503
+ def test_multiple_use_cases(self):
504
+ """Test processing with multiple use cases."""
505
+ detections = [
506
+ {"bbox": [25, 25, 75, 75], "confidence": 0.8, "category": "person"},
507
+ {"bbox": [125, 125, 175, 175], "confidence": 0.9, "category": "person"}
508
+ ]
509
+
510
+ # Test people counting
511
+ result1 = self.processor.process_simple(
512
+ detections, "people_counting", confidence_threshold=0.5
513
+ )
514
+ self.assertIn(result1.status, [ProcessingStatus.SUCCESS, ProcessingStatus.WARNING])
515
+
516
+ # Test basic counting tracking
517
+ result2 = self.processor.process_simple(
518
+ detections, "basic_counting_tracking",
519
+ confidence_threshold=0.5,
520
+ target_categories=["person"]
521
+ )
522
+ self.assertIn(result2.status, [ProcessingStatus.SUCCESS, ProcessingStatus.WARNING])
523
+
524
+ # Verify both processed successfully
525
+ self.assertIsNotNone(result1.data)
526
+ self.assertIsNotNone(result2.data)
527
+
528
+
529
+ if __name__ == '__main__':
530
+ # Run tests with verbose output
531
+ unittest.main(verbosity=2)