matrice-analytics 0.1.2__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.
Potentially problematic release.
This version of matrice-analytics might be problematic. Click here for more details.
- matrice_analytics/__init__.py +28 -0
- matrice_analytics/boundary_drawing_internal/README.md +305 -0
- matrice_analytics/boundary_drawing_internal/__init__.py +45 -0
- matrice_analytics/boundary_drawing_internal/boundary_drawing_internal.py +1207 -0
- matrice_analytics/boundary_drawing_internal/boundary_drawing_tool.py +429 -0
- matrice_analytics/boundary_drawing_internal/boundary_tool_template.html +1036 -0
- matrice_analytics/boundary_drawing_internal/data/.gitignore +12 -0
- matrice_analytics/boundary_drawing_internal/example_usage.py +206 -0
- matrice_analytics/boundary_drawing_internal/usage/README.md +110 -0
- matrice_analytics/boundary_drawing_internal/usage/boundary_drawer_launcher.py +102 -0
- matrice_analytics/boundary_drawing_internal/usage/simple_boundary_launcher.py +107 -0
- matrice_analytics/post_processing/README.md +455 -0
- matrice_analytics/post_processing/__init__.py +732 -0
- matrice_analytics/post_processing/advanced_tracker/README.md +650 -0
- matrice_analytics/post_processing/advanced_tracker/__init__.py +17 -0
- matrice_analytics/post_processing/advanced_tracker/base.py +99 -0
- matrice_analytics/post_processing/advanced_tracker/config.py +77 -0
- matrice_analytics/post_processing/advanced_tracker/kalman_filter.py +370 -0
- matrice_analytics/post_processing/advanced_tracker/matching.py +195 -0
- matrice_analytics/post_processing/advanced_tracker/strack.py +230 -0
- matrice_analytics/post_processing/advanced_tracker/tracker.py +367 -0
- matrice_analytics/post_processing/config.py +142 -0
- matrice_analytics/post_processing/core/__init__.py +63 -0
- matrice_analytics/post_processing/core/base.py +704 -0
- matrice_analytics/post_processing/core/config.py +3188 -0
- matrice_analytics/post_processing/core/config_utils.py +925 -0
- matrice_analytics/post_processing/face_reg/__init__.py +43 -0
- matrice_analytics/post_processing/face_reg/compare_similarity.py +556 -0
- matrice_analytics/post_processing/face_reg/embedding_manager.py +681 -0
- matrice_analytics/post_processing/face_reg/face_recognition.py +1870 -0
- matrice_analytics/post_processing/face_reg/face_recognition_client.py +339 -0
- matrice_analytics/post_processing/face_reg/people_activity_logging.py +283 -0
- matrice_analytics/post_processing/ocr/__init__.py +0 -0
- matrice_analytics/post_processing/ocr/easyocr_extractor.py +248 -0
- matrice_analytics/post_processing/ocr/postprocessing.py +271 -0
- matrice_analytics/post_processing/ocr/preprocessing.py +52 -0
- matrice_analytics/post_processing/post_processor.py +1153 -0
- matrice_analytics/post_processing/test_cases/__init__.py +1 -0
- matrice_analytics/post_processing/test_cases/run_tests.py +143 -0
- matrice_analytics/post_processing/test_cases/test_advanced_customer_service.py +841 -0
- matrice_analytics/post_processing/test_cases/test_basic_counting_tracking.py +523 -0
- matrice_analytics/post_processing/test_cases/test_comprehensive.py +531 -0
- matrice_analytics/post_processing/test_cases/test_config.py +852 -0
- matrice_analytics/post_processing/test_cases/test_customer_service.py +585 -0
- matrice_analytics/post_processing/test_cases/test_data_generators.py +583 -0
- matrice_analytics/post_processing/test_cases/test_people_counting.py +510 -0
- matrice_analytics/post_processing/test_cases/test_processor.py +524 -0
- matrice_analytics/post_processing/test_cases/test_utilities.py +356 -0
- matrice_analytics/post_processing/test_cases/test_utils.py +743 -0
- matrice_analytics/post_processing/usecases/Histopathological_Cancer_Detection_img.py +604 -0
- matrice_analytics/post_processing/usecases/__init__.py +267 -0
- matrice_analytics/post_processing/usecases/abandoned_object_detection.py +797 -0
- matrice_analytics/post_processing/usecases/advanced_customer_service.py +1601 -0
- matrice_analytics/post_processing/usecases/age_detection.py +842 -0
- matrice_analytics/post_processing/usecases/age_gender_detection.py +1043 -0
- matrice_analytics/post_processing/usecases/anti_spoofing_detection.py +656 -0
- matrice_analytics/post_processing/usecases/assembly_line_detection.py +841 -0
- matrice_analytics/post_processing/usecases/banana_defect_detection.py +624 -0
- matrice_analytics/post_processing/usecases/basic_counting_tracking.py +667 -0
- matrice_analytics/post_processing/usecases/blood_cancer_detection_img.py +881 -0
- matrice_analytics/post_processing/usecases/car_damage_detection.py +834 -0
- matrice_analytics/post_processing/usecases/car_part_segmentation.py +946 -0
- matrice_analytics/post_processing/usecases/car_service.py +1601 -0
- matrice_analytics/post_processing/usecases/cardiomegaly_classification.py +864 -0
- matrice_analytics/post_processing/usecases/cell_microscopy_segmentation.py +897 -0
- matrice_analytics/post_processing/usecases/chicken_pose_detection.py +648 -0
- matrice_analytics/post_processing/usecases/child_monitoring.py +814 -0
- matrice_analytics/post_processing/usecases/color/clip.py +232 -0
- matrice_analytics/post_processing/usecases/color/clip_processor/merges.txt +48895 -0
- matrice_analytics/post_processing/usecases/color/clip_processor/preprocessor_config.json +28 -0
- matrice_analytics/post_processing/usecases/color/clip_processor/special_tokens_map.json +30 -0
- matrice_analytics/post_processing/usecases/color/clip_processor/tokenizer.json +245079 -0
- matrice_analytics/post_processing/usecases/color/clip_processor/tokenizer_config.json +32 -0
- matrice_analytics/post_processing/usecases/color/clip_processor/vocab.json +1 -0
- matrice_analytics/post_processing/usecases/color/color_map_utils.py +70 -0
- matrice_analytics/post_processing/usecases/color/color_mapper.py +468 -0
- matrice_analytics/post_processing/usecases/color_detection.py +1835 -0
- matrice_analytics/post_processing/usecases/color_map_utils.py +70 -0
- matrice_analytics/post_processing/usecases/concrete_crack_detection.py +827 -0
- matrice_analytics/post_processing/usecases/crop_weed_detection.py +781 -0
- matrice_analytics/post_processing/usecases/customer_service.py +1008 -0
- matrice_analytics/post_processing/usecases/defect_detection_products.py +936 -0
- matrice_analytics/post_processing/usecases/distracted_driver_detection.py +822 -0
- matrice_analytics/post_processing/usecases/drone_traffic_monitoring.py +930 -0
- matrice_analytics/post_processing/usecases/drowsy_driver_detection.py +829 -0
- matrice_analytics/post_processing/usecases/dwell_detection.py +829 -0
- matrice_analytics/post_processing/usecases/emergency_vehicle_detection.py +827 -0
- matrice_analytics/post_processing/usecases/face_emotion.py +813 -0
- matrice_analytics/post_processing/usecases/face_recognition.py +827 -0
- matrice_analytics/post_processing/usecases/fashion_detection.py +835 -0
- matrice_analytics/post_processing/usecases/field_mapping.py +902 -0
- matrice_analytics/post_processing/usecases/fire_detection.py +1112 -0
- matrice_analytics/post_processing/usecases/flare_analysis.py +891 -0
- matrice_analytics/post_processing/usecases/flower_segmentation.py +1006 -0
- matrice_analytics/post_processing/usecases/gas_leak_detection.py +837 -0
- matrice_analytics/post_processing/usecases/gender_detection.py +832 -0
- matrice_analytics/post_processing/usecases/human_activity_recognition.py +871 -0
- matrice_analytics/post_processing/usecases/intrusion_detection.py +1672 -0
- matrice_analytics/post_processing/usecases/leaf.py +821 -0
- matrice_analytics/post_processing/usecases/leaf_disease.py +840 -0
- matrice_analytics/post_processing/usecases/leak_detection.py +837 -0
- matrice_analytics/post_processing/usecases/license_plate_detection.py +914 -0
- matrice_analytics/post_processing/usecases/license_plate_monitoring.py +1194 -0
- matrice_analytics/post_processing/usecases/litter_monitoring.py +717 -0
- matrice_analytics/post_processing/usecases/mask_detection.py +869 -0
- matrice_analytics/post_processing/usecases/natural_disaster.py +907 -0
- matrice_analytics/post_processing/usecases/parking.py +787 -0
- matrice_analytics/post_processing/usecases/parking_space_detection.py +822 -0
- matrice_analytics/post_processing/usecases/pcb_defect_detection.py +888 -0
- matrice_analytics/post_processing/usecases/pedestrian_detection.py +808 -0
- matrice_analytics/post_processing/usecases/people_counting.py +1728 -0
- matrice_analytics/post_processing/usecases/people_tracking.py +1842 -0
- matrice_analytics/post_processing/usecases/pipeline_detection.py +605 -0
- matrice_analytics/post_processing/usecases/plaque_segmentation_img.py +874 -0
- matrice_analytics/post_processing/usecases/pothole_segmentation.py +915 -0
- matrice_analytics/post_processing/usecases/ppe_compliance.py +645 -0
- matrice_analytics/post_processing/usecases/price_tag_detection.py +822 -0
- matrice_analytics/post_processing/usecases/proximity_detection.py +1901 -0
- matrice_analytics/post_processing/usecases/road_lane_detection.py +623 -0
- matrice_analytics/post_processing/usecases/road_traffic_density.py +832 -0
- matrice_analytics/post_processing/usecases/road_view_segmentation.py +915 -0
- matrice_analytics/post_processing/usecases/shelf_inventory_detection.py +583 -0
- matrice_analytics/post_processing/usecases/shoplifting_detection.py +822 -0
- matrice_analytics/post_processing/usecases/shopping_cart_analysis.py +899 -0
- matrice_analytics/post_processing/usecases/skin_cancer_classification_img.py +864 -0
- matrice_analytics/post_processing/usecases/smoker_detection.py +833 -0
- matrice_analytics/post_processing/usecases/solar_panel.py +810 -0
- matrice_analytics/post_processing/usecases/suspicious_activity_detection.py +1030 -0
- matrice_analytics/post_processing/usecases/template_usecase.py +380 -0
- matrice_analytics/post_processing/usecases/theft_detection.py +648 -0
- matrice_analytics/post_processing/usecases/traffic_sign_monitoring.py +724 -0
- matrice_analytics/post_processing/usecases/underground_pipeline_defect_detection.py +775 -0
- matrice_analytics/post_processing/usecases/underwater_pollution_detection.py +842 -0
- matrice_analytics/post_processing/usecases/vehicle_monitoring.py +950 -0
- matrice_analytics/post_processing/usecases/warehouse_object_segmentation.py +899 -0
- matrice_analytics/post_processing/usecases/waterbody_segmentation.py +923 -0
- matrice_analytics/post_processing/usecases/weapon_detection.py +771 -0
- matrice_analytics/post_processing/usecases/weld_defect_detection.py +615 -0
- matrice_analytics/post_processing/usecases/wildlife_monitoring.py +898 -0
- matrice_analytics/post_processing/usecases/windmill_maintenance.py +834 -0
- matrice_analytics/post_processing/usecases/wound_segmentation.py +856 -0
- matrice_analytics/post_processing/utils/__init__.py +150 -0
- matrice_analytics/post_processing/utils/advanced_counting_utils.py +400 -0
- matrice_analytics/post_processing/utils/advanced_helper_utils.py +317 -0
- matrice_analytics/post_processing/utils/advanced_tracking_utils.py +461 -0
- matrice_analytics/post_processing/utils/alerting_utils.py +213 -0
- matrice_analytics/post_processing/utils/category_mapping_utils.py +94 -0
- matrice_analytics/post_processing/utils/color_utils.py +592 -0
- matrice_analytics/post_processing/utils/counting_utils.py +182 -0
- matrice_analytics/post_processing/utils/filter_utils.py +261 -0
- matrice_analytics/post_processing/utils/format_utils.py +293 -0
- matrice_analytics/post_processing/utils/geometry_utils.py +300 -0
- matrice_analytics/post_processing/utils/smoothing_utils.py +358 -0
- matrice_analytics/post_processing/utils/tracking_utils.py +234 -0
- matrice_analytics/py.typed +0 -0
- matrice_analytics-0.1.2.dist-info/METADATA +481 -0
- matrice_analytics-0.1.2.dist-info/RECORD +160 -0
- matrice_analytics-0.1.2.dist-info/WHEEL +5 -0
- matrice_analytics-0.1.2.dist-info/licenses/LICENSE.txt +21 -0
- matrice_analytics-0.1.2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,524 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Comprehensive tests for the PostProcessor class.
|
|
3
|
+
|
|
4
|
+
This module tests the main PostProcessor functionality including:
|
|
5
|
+
- Simple processing interface
|
|
6
|
+
- Configuration-based processing
|
|
7
|
+
- File-based configuration
|
|
8
|
+
- Error handling
|
|
9
|
+
- Performance characteristics
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import unittest
|
|
13
|
+
import json
|
|
14
|
+
import os
|
|
15
|
+
import time
|
|
16
|
+
from unittest.mock import patch, MagicMock
|
|
17
|
+
|
|
18
|
+
from .test_utilities import BasePostProcessingTest
|
|
19
|
+
from .test_data_generators import (
|
|
20
|
+
create_detection_results, create_tracking_results,
|
|
21
|
+
create_people_counting_config, create_customer_service_config,
|
|
22
|
+
create_edge_case_data, create_performance_test_data
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
from src.matrice_analytics.post_processing import (
|
|
26
|
+
PostProcessor, ProcessingStatus, ProcessingContext,
|
|
27
|
+
PeopleCountingConfig, CustomerServiceConfig, ConfigValidationError
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class TestPostProcessor(BasePostProcessingTest):
|
|
32
|
+
"""Test cases for PostProcessor class."""
|
|
33
|
+
|
|
34
|
+
def test_processor_initialization(self):
|
|
35
|
+
"""Test that PostProcessor initializes correctly."""
|
|
36
|
+
processor = PostProcessor()
|
|
37
|
+
|
|
38
|
+
# Check that statistics are initialized
|
|
39
|
+
stats = processor.get_statistics()
|
|
40
|
+
self.assertIsInstance(stats, dict)
|
|
41
|
+
self.assertEqual(stats["total_processed"], 0)
|
|
42
|
+
self.assertEqual(stats["successful"], 0)
|
|
43
|
+
self.assertEqual(stats["failed"], 0)
|
|
44
|
+
|
|
45
|
+
# Check that use cases are available
|
|
46
|
+
use_cases = processor.list_available_usecases()
|
|
47
|
+
self.assertIsInstance(use_cases, dict)
|
|
48
|
+
self.assertIn("general", use_cases)
|
|
49
|
+
self.assertIn("sales", use_cases)
|
|
50
|
+
|
|
51
|
+
def test_simple_people_counting_processing(self):
|
|
52
|
+
"""Test simple people counting processing."""
|
|
53
|
+
# Create test data
|
|
54
|
+
test_data = create_detection_results(
|
|
55
|
+
num_detections=10,
|
|
56
|
+
categories=["person", "car", "bicycle"]
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
# Process with simple interface
|
|
60
|
+
result = self.processor.process_simple(
|
|
61
|
+
test_data,
|
|
62
|
+
usecase="people_counting",
|
|
63
|
+
confidence_threshold=0.5,
|
|
64
|
+
enable_tracking=True
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
# Validate result
|
|
68
|
+
self.assert_processing_result_valid(
|
|
69
|
+
result,
|
|
70
|
+
expected_usecase="people_counting",
|
|
71
|
+
expected_category="general"
|
|
72
|
+
)
|
|
73
|
+
self.assertEqual(result.status, ProcessingStatus.SUCCESS)
|
|
74
|
+
self.assert_insights_generated(result, min_insights=1)
|
|
75
|
+
|
|
76
|
+
# Check for expected metrics (using actual names from implementation)
|
|
77
|
+
expected_metrics = ["total_people", "processing_time", "input_format"]
|
|
78
|
+
self.assert_metrics_present(result, expected_metrics)
|
|
79
|
+
|
|
80
|
+
def test_simple_customer_service_processing(self):
|
|
81
|
+
"""Test simple customer service processing."""
|
|
82
|
+
# Create test data with relevant categories
|
|
83
|
+
test_data = create_detection_results(
|
|
84
|
+
num_detections=15,
|
|
85
|
+
categories=["person", "staff", "customer"]
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
# Process with simple interface
|
|
89
|
+
result = self.processor.process_simple(
|
|
90
|
+
test_data,
|
|
91
|
+
usecase="customer_service",
|
|
92
|
+
confidence_threshold=0.6,
|
|
93
|
+
service_proximity_threshold=100.0
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
# Validate result (may return WARNING status for some scenarios)
|
|
97
|
+
self.assert_processing_result_valid(
|
|
98
|
+
result,
|
|
99
|
+
expected_usecase="customer_service",
|
|
100
|
+
expected_category="sales"
|
|
101
|
+
)
|
|
102
|
+
self.assertIn(result.status, [ProcessingStatus.SUCCESS, ProcessingStatus.WARNING])
|
|
103
|
+
self.assert_insights_generated(result, min_insights=1)
|
|
104
|
+
|
|
105
|
+
def test_configuration_based_processing(self):
|
|
106
|
+
"""Test processing with configuration objects."""
|
|
107
|
+
# Create test data
|
|
108
|
+
test_data = create_detection_results(num_detections=8)
|
|
109
|
+
|
|
110
|
+
# Create configuration
|
|
111
|
+
config = create_people_counting_config(
|
|
112
|
+
confidence_threshold=0.7,
|
|
113
|
+
enable_tracking=True,
|
|
114
|
+
include_zones=True,
|
|
115
|
+
include_alerts=True
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
# Process with configuration
|
|
119
|
+
result = self.processor.process(test_data, config)
|
|
120
|
+
|
|
121
|
+
# Validate result
|
|
122
|
+
self.assert_processing_result_valid(
|
|
123
|
+
result,
|
|
124
|
+
expected_usecase="people_counting",
|
|
125
|
+
expected_category="general"
|
|
126
|
+
)
|
|
127
|
+
self.assertEqual(result.status, ProcessingStatus.SUCCESS)
|
|
128
|
+
|
|
129
|
+
# Check that zone analysis was performed (using actual metric names)
|
|
130
|
+
if result.metrics.get("zones_analyzed", 0) > 0:
|
|
131
|
+
self.assertIn("zone_metrics", result.metrics)
|
|
132
|
+
|
|
133
|
+
def test_file_based_configuration(self):
|
|
134
|
+
"""Test processing with configuration files."""
|
|
135
|
+
# Create test data
|
|
136
|
+
test_data = create_detection_results(num_detections=5)
|
|
137
|
+
|
|
138
|
+
# Create configuration
|
|
139
|
+
config = create_people_counting_config(confidence_threshold=0.6)
|
|
140
|
+
|
|
141
|
+
# Save configuration to file
|
|
142
|
+
config_file = self.create_temp_config_file(config, "people_counting_test.json")
|
|
143
|
+
|
|
144
|
+
# Process from file
|
|
145
|
+
result = self.processor.process_from_file(test_data, config_file)
|
|
146
|
+
|
|
147
|
+
# Validate result
|
|
148
|
+
self.assert_processing_result_valid(
|
|
149
|
+
result,
|
|
150
|
+
expected_usecase="people_counting",
|
|
151
|
+
expected_category="general"
|
|
152
|
+
)
|
|
153
|
+
self.assertEqual(result.status, ProcessingStatus.SUCCESS)
|
|
154
|
+
|
|
155
|
+
def test_invalid_configuration_handling(self):
|
|
156
|
+
"""Test handling of invalid configurations."""
|
|
157
|
+
test_data = create_detection_results(num_detections=3)
|
|
158
|
+
|
|
159
|
+
# Test with invalid confidence threshold
|
|
160
|
+
config = PeopleCountingConfig(
|
|
161
|
+
category="general",
|
|
162
|
+
usecase="people_counting",
|
|
163
|
+
confidence_threshold=1.5 # Invalid: >1.0
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
result = self.processor.process(test_data, config)
|
|
167
|
+
|
|
168
|
+
# Should handle gracefully
|
|
169
|
+
self.assertIsInstance(result.status, ProcessingStatus)
|
|
170
|
+
if result.status == ProcessingStatus.ERROR:
|
|
171
|
+
self.assertIsNotNone(result.error_message)
|
|
172
|
+
|
|
173
|
+
def test_unknown_usecase_handling(self):
|
|
174
|
+
"""Test handling of unknown use cases."""
|
|
175
|
+
test_data = create_detection_results(num_detections=3)
|
|
176
|
+
|
|
177
|
+
# Try to process with unknown use case
|
|
178
|
+
result = self.processor.process_simple(
|
|
179
|
+
test_data,
|
|
180
|
+
usecase="unknown_usecase",
|
|
181
|
+
confidence_threshold=0.5
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
# Should return error result
|
|
185
|
+
self.assertEqual(result.status, ProcessingStatus.ERROR)
|
|
186
|
+
self.assertIsNotNone(result.error_message)
|
|
187
|
+
# Check for appropriate error message
|
|
188
|
+
self.assertTrue(
|
|
189
|
+
"unknown" in result.error_message.lower() or
|
|
190
|
+
"not found" in result.error_message.lower()
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
def test_empty_data_handling(self):
|
|
194
|
+
"""Test handling of empty input data."""
|
|
195
|
+
# Test with empty list
|
|
196
|
+
result = self.processor.process_simple(
|
|
197
|
+
[],
|
|
198
|
+
usecase="people_counting",
|
|
199
|
+
confidence_threshold=0.5
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
# Should handle gracefully
|
|
203
|
+
self.assertIsInstance(result.status, ProcessingStatus)
|
|
204
|
+
if result.status == ProcessingStatus.SUCCESS:
|
|
205
|
+
# Check for appropriate message indicating no data
|
|
206
|
+
self.assertTrue(
|
|
207
|
+
"no people" in result.summary.lower() or
|
|
208
|
+
"no detections" in result.summary.lower() or
|
|
209
|
+
"empty" in result.summary.lower()
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
def test_malformed_data_handling(self):
|
|
213
|
+
"""Test handling of malformed input data."""
|
|
214
|
+
# Test with malformed detections
|
|
215
|
+
malformed_data = create_edge_case_data()
|
|
216
|
+
|
|
217
|
+
result = self.processor.process_simple(
|
|
218
|
+
malformed_data,
|
|
219
|
+
usecase="people_counting",
|
|
220
|
+
confidence_threshold=0.5
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
# Should handle gracefully - either success with warnings or error
|
|
224
|
+
self.assertIsInstance(result.status, ProcessingStatus)
|
|
225
|
+
if result.status == ProcessingStatus.WARNING:
|
|
226
|
+
self.assertTrue(len(result.warnings) > 0)
|
|
227
|
+
|
|
228
|
+
def test_context_propagation(self):
|
|
229
|
+
"""Test that processing context is properly propagated."""
|
|
230
|
+
test_data = create_detection_results(num_detections=3)
|
|
231
|
+
|
|
232
|
+
# Create context with specific parameters
|
|
233
|
+
context = ProcessingContext(
|
|
234
|
+
confidence_threshold=0.8,
|
|
235
|
+
enable_tracking=True,
|
|
236
|
+
enable_analytics=True,
|
|
237
|
+
metadata={"source": "test_camera", "location": "test_location"}
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
# Process with context
|
|
241
|
+
result = self.processor.process_simple(
|
|
242
|
+
test_data,
|
|
243
|
+
usecase="people_counting",
|
|
244
|
+
confidence_threshold=0.8,
|
|
245
|
+
context=context
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
# Check context propagation
|
|
249
|
+
self.assertIsNotNone(result.context)
|
|
250
|
+
self.assertEqual(result.context.confidence_threshold, 0.8)
|
|
251
|
+
self.assertTrue(result.context.enable_tracking)
|
|
252
|
+
self.assertTrue(result.context.enable_analytics)
|
|
253
|
+
self.assertEqual(result.context.metadata.get("source"), "test_camera")
|
|
254
|
+
self.assertEqual(result.context.metadata.get("location"), "test_location")
|
|
255
|
+
|
|
256
|
+
def test_statistics_tracking(self):
|
|
257
|
+
"""Test that processing statistics are tracked correctly."""
|
|
258
|
+
# Get initial statistics
|
|
259
|
+
initial_stats = self.processor.get_statistics()
|
|
260
|
+
|
|
261
|
+
# Process some data
|
|
262
|
+
test_data = create_detection_results(num_detections=3)
|
|
263
|
+
|
|
264
|
+
# Successful processing
|
|
265
|
+
result1 = self.processor.process_simple(
|
|
266
|
+
test_data,
|
|
267
|
+
usecase="people_counting",
|
|
268
|
+
confidence_threshold=0.5
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
# Check intermediate statistics
|
|
272
|
+
intermediate_stats = self.processor.get_statistics()
|
|
273
|
+
self.assertEqual(intermediate_stats["total_processed"], initial_stats["total_processed"] + 1)
|
|
274
|
+
self.assertEqual(intermediate_stats["successful"], initial_stats["successful"] + 1)
|
|
275
|
+
|
|
276
|
+
# Failed processing (unknown use case) - this should not increment counters
|
|
277
|
+
try:
|
|
278
|
+
result2 = self.processor.process_simple(
|
|
279
|
+
test_data,
|
|
280
|
+
usecase="unknown_usecase"
|
|
281
|
+
)
|
|
282
|
+
except Exception:
|
|
283
|
+
pass # Expected to fail
|
|
284
|
+
|
|
285
|
+
# Check final statistics - failed request shouldn't be counted
|
|
286
|
+
final_stats = self.processor.get_statistics()
|
|
287
|
+
self.assertEqual(final_stats["total_processed"], initial_stats["total_processed"] + 1)
|
|
288
|
+
self.assertEqual(final_stats["successful"], initial_stats["successful"] + 1)
|
|
289
|
+
|
|
290
|
+
def test_config_template_generation(self):
|
|
291
|
+
"""Test configuration template generation."""
|
|
292
|
+
# Test people counting template
|
|
293
|
+
template = self.processor.get_config_template("people_counting")
|
|
294
|
+
|
|
295
|
+
self.assertIsInstance(template, dict)
|
|
296
|
+
self.assertIn("confidence_threshold", template)
|
|
297
|
+
self.assertIn("enable_tracking", template)
|
|
298
|
+
self.assertIn("person_categories", template)
|
|
299
|
+
|
|
300
|
+
# Test customer service template
|
|
301
|
+
template = self.processor.get_config_template("customer_service")
|
|
302
|
+
|
|
303
|
+
self.assertIsInstance(template, dict)
|
|
304
|
+
self.assertIn("service_proximity_threshold", template)
|
|
305
|
+
self.assertIn("staff_categories", template)
|
|
306
|
+
self.assertIn("customer_categories", template)
|
|
307
|
+
|
|
308
|
+
def test_config_validation(self):
|
|
309
|
+
"""Test configuration validation."""
|
|
310
|
+
# Valid configuration
|
|
311
|
+
valid_config = create_people_counting_config(confidence_threshold=0.5)
|
|
312
|
+
errors = self.processor.validate_config(valid_config)
|
|
313
|
+
self.assertEqual(len(errors), 0)
|
|
314
|
+
|
|
315
|
+
# Invalid configuration
|
|
316
|
+
invalid_config = PeopleCountingConfig(
|
|
317
|
+
category="general",
|
|
318
|
+
usecase="people_counting",
|
|
319
|
+
confidence_threshold=2.0, # Invalid
|
|
320
|
+
time_window_minutes=-5 # Invalid
|
|
321
|
+
)
|
|
322
|
+
errors = self.processor.validate_config(invalid_config)
|
|
323
|
+
self.assertGreater(len(errors), 0)
|
|
324
|
+
|
|
325
|
+
def test_available_usecases_listing(self):
|
|
326
|
+
"""Test listing of available use cases."""
|
|
327
|
+
use_cases = self.processor.list_available_usecases()
|
|
328
|
+
|
|
329
|
+
self.assertIsInstance(use_cases, dict)
|
|
330
|
+
|
|
331
|
+
# Check expected categories and use cases
|
|
332
|
+
self.assertIn("general", use_cases)
|
|
333
|
+
self.assertIn("people_counting", use_cases["general"])
|
|
334
|
+
|
|
335
|
+
self.assertIn("sales", use_cases)
|
|
336
|
+
self.assertIn("customer_service", use_cases["sales"])
|
|
337
|
+
|
|
338
|
+
def test_supported_usecases_listing(self):
|
|
339
|
+
"""Test getting list of supported use case names."""
|
|
340
|
+
supported = self.processor.get_supported_usecases()
|
|
341
|
+
|
|
342
|
+
self.assertIsInstance(supported, list)
|
|
343
|
+
self.assertIn("people_counting", supported)
|
|
344
|
+
self.assertIn("customer_service", supported)
|
|
345
|
+
|
|
346
|
+
def test_processing_with_tracking_data(self):
|
|
347
|
+
"""Test processing with tracking format data."""
|
|
348
|
+
# Create tracking data
|
|
349
|
+
tracking_data = create_tracking_results(
|
|
350
|
+
num_tracks=5,
|
|
351
|
+
categories=["person", "car"]
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
result = self.processor.process_simple(
|
|
355
|
+
tracking_data,
|
|
356
|
+
usecase="people_counting",
|
|
357
|
+
confidence_threshold=0.5,
|
|
358
|
+
enable_tracking=True
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
# Should handle tracking data format
|
|
362
|
+
self.assert_processing_result_valid(result)
|
|
363
|
+
|
|
364
|
+
# Should have basic metrics
|
|
365
|
+
self.assertIn("total_people", result.metrics)
|
|
366
|
+
self.assertIn("processing_time", result.metrics)
|
|
367
|
+
|
|
368
|
+
def test_config_save_and_load(self):
|
|
369
|
+
"""Test saving and loading configurations."""
|
|
370
|
+
# Create configuration
|
|
371
|
+
original_config = create_customer_service_config(
|
|
372
|
+
confidence_threshold=0.7,
|
|
373
|
+
service_proximity_threshold=150.0
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
# Save configuration
|
|
377
|
+
config_file = os.path.join(self.temp_dir, "customer_service_config.json")
|
|
378
|
+
self.processor.save_config(original_config, config_file)
|
|
379
|
+
|
|
380
|
+
# Load configuration
|
|
381
|
+
loaded_config = self.processor.load_config(config_file)
|
|
382
|
+
|
|
383
|
+
# Compare configurations
|
|
384
|
+
self.assertEqual(original_config.usecase, loaded_config.usecase)
|
|
385
|
+
self.assertEqual(original_config.category, loaded_config.category)
|
|
386
|
+
self.assertEqual(original_config.confidence_threshold, loaded_config.confidence_threshold)
|
|
387
|
+
self.assertEqual(original_config.service_proximity_threshold, loaded_config.service_proximity_threshold)
|
|
388
|
+
|
|
389
|
+
def test_reset_statistics(self):
|
|
390
|
+
"""Test resetting processing statistics."""
|
|
391
|
+
# Process some data to generate statistics
|
|
392
|
+
test_data = create_detection_results(num_detections=3)
|
|
393
|
+
self.processor.process_simple(test_data, usecase="people_counting")
|
|
394
|
+
|
|
395
|
+
# Check that statistics exist
|
|
396
|
+
stats_before = self.processor.get_statistics()
|
|
397
|
+
self.assertGreater(stats_before["total_processed"], 0)
|
|
398
|
+
|
|
399
|
+
# Reset statistics
|
|
400
|
+
self.processor.reset_statistics()
|
|
401
|
+
|
|
402
|
+
# Check that statistics are reset
|
|
403
|
+
stats_after = self.processor.get_statistics()
|
|
404
|
+
self.assertEqual(stats_after["total_processed"], 0)
|
|
405
|
+
self.assertEqual(stats_after["successful"], 0)
|
|
406
|
+
self.assertEqual(stats_after["failed"], 0)
|
|
407
|
+
self.assertEqual(stats_after["total_processing_time"], 0.0)
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
class TestPostProcessorPerformance(BasePostProcessingTest):
|
|
411
|
+
"""Performance tests for PostProcessor."""
|
|
412
|
+
|
|
413
|
+
def test_large_dataset_processing(self):
|
|
414
|
+
"""Test processing performance with large datasets."""
|
|
415
|
+
# Create large dataset
|
|
416
|
+
large_data = create_performance_test_data(size=1000)
|
|
417
|
+
|
|
418
|
+
# Measure processing time
|
|
419
|
+
result, processing_time = self.measure_processing_time(
|
|
420
|
+
self.processor.process_simple,
|
|
421
|
+
large_data,
|
|
422
|
+
usecase="people_counting",
|
|
423
|
+
confidence_threshold=0.5
|
|
424
|
+
)
|
|
425
|
+
|
|
426
|
+
# Check that processing completed successfully
|
|
427
|
+
self.assertEqual(result.status, ProcessingStatus.SUCCESS)
|
|
428
|
+
|
|
429
|
+
# Check performance (should complete within reasonable time)
|
|
430
|
+
self.assert_performance_acceptable(processing_time, max_time=5.0)
|
|
431
|
+
|
|
432
|
+
# Check basic metrics are present
|
|
433
|
+
self.assertIn("total_people", result.metrics)
|
|
434
|
+
self.assertIn("processing_time", result.metrics)
|
|
435
|
+
|
|
436
|
+
def test_batch_processing_performance(self):
|
|
437
|
+
"""Test performance with multiple batch processing calls."""
|
|
438
|
+
# Create multiple small batches
|
|
439
|
+
batches = [create_detection_results(num_detections=50) for _ in range(10)]
|
|
440
|
+
|
|
441
|
+
total_start_time = time.time()
|
|
442
|
+
results = []
|
|
443
|
+
|
|
444
|
+
for batch in batches:
|
|
445
|
+
result = self.processor.process_simple(
|
|
446
|
+
batch,
|
|
447
|
+
usecase="people_counting",
|
|
448
|
+
confidence_threshold=0.5
|
|
449
|
+
)
|
|
450
|
+
results.append(result)
|
|
451
|
+
|
|
452
|
+
total_processing_time = time.time() - total_start_time
|
|
453
|
+
|
|
454
|
+
# Check that all batches processed successfully
|
|
455
|
+
for result in results:
|
|
456
|
+
self.assertEqual(result.status, ProcessingStatus.SUCCESS)
|
|
457
|
+
|
|
458
|
+
# Check overall performance
|
|
459
|
+
self.assert_performance_acceptable(total_processing_time, max_time=10.0)
|
|
460
|
+
|
|
461
|
+
# Check statistics
|
|
462
|
+
stats = self.processor.get_statistics()
|
|
463
|
+
self.assertEqual(stats["total_processed"], len(batches))
|
|
464
|
+
self.assertEqual(stats["successful"], len(batches))
|
|
465
|
+
|
|
466
|
+
def test_memory_usage_stability(self):
|
|
467
|
+
"""Test that memory usage remains stable during processing."""
|
|
468
|
+
import psutil
|
|
469
|
+
import os
|
|
470
|
+
|
|
471
|
+
# Get initial memory usage
|
|
472
|
+
process = psutil.Process(os.getpid())
|
|
473
|
+
initial_memory = process.memory_info().rss
|
|
474
|
+
|
|
475
|
+
# Process multiple datasets
|
|
476
|
+
for i in range(20):
|
|
477
|
+
test_data = create_detection_results(num_detections=100)
|
|
478
|
+
result = self.processor.process_simple(
|
|
479
|
+
test_data,
|
|
480
|
+
usecase="people_counting",
|
|
481
|
+
confidence_threshold=0.5
|
|
482
|
+
)
|
|
483
|
+
self.assertEqual(result.status, ProcessingStatus.SUCCESS)
|
|
484
|
+
|
|
485
|
+
# Check final memory usage
|
|
486
|
+
final_memory = process.memory_info().rss
|
|
487
|
+
memory_increase = final_memory - initial_memory
|
|
488
|
+
|
|
489
|
+
# Memory increase should be reasonable (less than 100MB)
|
|
490
|
+
max_memory_increase = 100 * 1024 * 1024 # 100MB
|
|
491
|
+
self.assertLess(memory_increase, max_memory_increase,
|
|
492
|
+
f"Memory increased by {memory_increase / 1024 / 1024:.2f}MB")
|
|
493
|
+
|
|
494
|
+
|
|
495
|
+
if __name__ == "__main__":
|
|
496
|
+
import time
|
|
497
|
+
|
|
498
|
+
# Create test suite
|
|
499
|
+
suite = unittest.TestSuite()
|
|
500
|
+
|
|
501
|
+
# Add test cases
|
|
502
|
+
suite.addTest(unittest.makeSuite(TestPostProcessor))
|
|
503
|
+
suite.addTest(unittest.makeSuite(TestPostProcessorPerformance))
|
|
504
|
+
|
|
505
|
+
# Run tests
|
|
506
|
+
runner = unittest.TextTestRunner(verbosity=2)
|
|
507
|
+
start_time = time.time()
|
|
508
|
+
result = runner.run(suite)
|
|
509
|
+
end_time = time.time()
|
|
510
|
+
|
|
511
|
+
print(f"\nTest execution completed in {end_time - start_time:.2f} seconds")
|
|
512
|
+
print(f"Tests run: {result.testsRun}")
|
|
513
|
+
print(f"Failures: {len(result.failures)}")
|
|
514
|
+
print(f"Errors: {len(result.errors)}")
|
|
515
|
+
|
|
516
|
+
if result.failures:
|
|
517
|
+
print("\nFailures:")
|
|
518
|
+
for test, traceback in result.failures:
|
|
519
|
+
print(f"- {test}: {traceback}")
|
|
520
|
+
|
|
521
|
+
if result.errors:
|
|
522
|
+
print("\nErrors:")
|
|
523
|
+
for test, traceback in result.errors:
|
|
524
|
+
print(f"- {test}: {traceback}")
|