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.
- 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 +146 -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 +3291 -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 +950 -0
- matrice_analytics/post_processing/face_reg/face_recognition.py +2234 -0
- matrice_analytics/post_processing/face_reg/face_recognition_client.py +606 -0
- matrice_analytics/post_processing/face_reg/people_activity_logging.py +321 -0
- matrice_analytics/post_processing/ocr/__init__.py +0 -0
- matrice_analytics/post_processing/ocr/easyocr_extractor.py +250 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/__init__.py +9 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/__init__.py +4 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/cli.py +33 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/dataset_stats.py +139 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/export.py +398 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/train.py +447 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/utils.py +129 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/valid.py +93 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/validate_dataset.py +240 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/visualize_augmentation.py +176 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/visualize_predictions.py +96 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/core/__init__.py +3 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/core/process.py +246 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/core/types.py +60 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/core/utils.py +87 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/inference/__init__.py +3 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/inference/config.py +82 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/inference/hub.py +141 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/inference/plate_recognizer.py +323 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/py.typed +0 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/__init__.py +0 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/data/__init__.py +0 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/data/augmentation.py +101 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/data/dataset.py +97 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/__init__.py +0 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/config.py +114 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/layers.py +553 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/loss.py +55 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/metric.py +86 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/model_builders.py +95 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/model_schema.py +395 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/utilities/__init__.py +0 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/utilities/backend_utils.py +38 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/utilities/utils.py +214 -0
- matrice_analytics/post_processing/ocr/postprocessing.py +270 -0
- matrice_analytics/post_processing/ocr/preprocessing.py +52 -0
- matrice_analytics/post_processing/post_processor.py +1175 -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_usecases.py +165 -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 +1085 -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 +660 -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 +1936 -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 +585 -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 +1146 -0
- matrice_analytics/post_processing/usecases/flare_analysis.py +836 -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 +1188 -0
- matrice_analytics/post_processing/usecases/license_plate_monitoring.py +1781 -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 +706 -0
- matrice_analytics/post_processing/usecases/people_counting_bckp.py +1683 -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 +1029 -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.60.dist-info/METADATA +481 -0
- matrice_analytics-0.1.60.dist-info/RECORD +196 -0
- matrice_analytics-0.1.60.dist-info/WHEEL +5 -0
- matrice_analytics-0.1.60.dist-info/licenses/LICENSE.txt +21 -0
- matrice_analytics-0.1.60.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,852 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tests for post processing configuration system.
|
|
3
|
+
|
|
4
|
+
This module tests all configuration classes, validation, serialization,
|
|
5
|
+
ConfigManager functionality, and file-based configuration management.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import unittest
|
|
9
|
+
import json
|
|
10
|
+
import yaml
|
|
11
|
+
import tempfile
|
|
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.core.config import (
|
|
20
|
+
BaseConfig, PeopleCountingConfig, CustomerServiceConfig,
|
|
21
|
+
ZoneConfig, TrackingConfig, AlertConfig, ConfigManager, config_manager,
|
|
22
|
+
ConfigValidationError
|
|
23
|
+
)
|
|
24
|
+
from src.matrice_analytics.post_processing.usecases.basic_counting_tracking import BasicCountingTrackingConfig
|
|
25
|
+
|
|
26
|
+
from .test_utilities import BasePostProcessingTest
|
|
27
|
+
from .test_data_generators import create_zone_polygons, create_customer_service_areas
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class TestBaseConfig(BasePostProcessingTest):
|
|
31
|
+
"""Test BaseConfig functionality."""
|
|
32
|
+
|
|
33
|
+
def test_base_config_creation(self):
|
|
34
|
+
"""Test basic configuration creation."""
|
|
35
|
+
config = BaseConfig(
|
|
36
|
+
category="test",
|
|
37
|
+
usecase="test_usecase",
|
|
38
|
+
confidence_threshold=0.7
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
self.assertEqual(config.category, "test")
|
|
42
|
+
self.assertEqual(config.usecase, "test_usecase")
|
|
43
|
+
self.assertEqual(config.confidence_threshold, 0.7)
|
|
44
|
+
self.assertEqual(config.version, "1.0")
|
|
45
|
+
|
|
46
|
+
def test_base_config_validation(self):
|
|
47
|
+
"""Test base configuration validation."""
|
|
48
|
+
# Valid config
|
|
49
|
+
valid_config = BaseConfig(
|
|
50
|
+
category="test",
|
|
51
|
+
usecase="test_usecase",
|
|
52
|
+
confidence_threshold=0.7
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
errors = valid_config.validate()
|
|
56
|
+
self.assertEqual(len(errors), 0)
|
|
57
|
+
|
|
58
|
+
# Invalid confidence threshold
|
|
59
|
+
invalid_config = BaseConfig(
|
|
60
|
+
category="test",
|
|
61
|
+
usecase="test_usecase",
|
|
62
|
+
confidence_threshold=1.5 # > 1.0
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
errors = invalid_config.validate()
|
|
66
|
+
self.assertGreater(len(errors), 0)
|
|
67
|
+
self.assertTrue(any("confidence_threshold" in error for error in errors))
|
|
68
|
+
|
|
69
|
+
# Negative confidence threshold
|
|
70
|
+
invalid_config2 = BaseConfig(
|
|
71
|
+
category="test",
|
|
72
|
+
usecase="test_usecase",
|
|
73
|
+
confidence_threshold=-0.1
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
errors = invalid_config2.validate()
|
|
77
|
+
self.assertGreater(len(errors), 0)
|
|
78
|
+
|
|
79
|
+
def test_base_config_serialization(self):
|
|
80
|
+
"""Test configuration serialization."""
|
|
81
|
+
config = BaseConfig(
|
|
82
|
+
category="test",
|
|
83
|
+
usecase="test_usecase",
|
|
84
|
+
confidence_threshold=0.7,
|
|
85
|
+
enable_tracking=True
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
# Test to_dict
|
|
89
|
+
config_dict = config.to_dict()
|
|
90
|
+
self.assertIsInstance(config_dict, dict)
|
|
91
|
+
self.assertEqual(config_dict["category"], "test")
|
|
92
|
+
self.assertEqual(config_dict["confidence_threshold"], 0.7)
|
|
93
|
+
self.assertEqual(config_dict["enable_tracking"], True)
|
|
94
|
+
|
|
95
|
+
# Test from_dict
|
|
96
|
+
restored_config = BaseConfig.from_dict(config_dict)
|
|
97
|
+
self.assertEqual(restored_config.category, config.category)
|
|
98
|
+
self.assertEqual(restored_config.confidence_threshold, config.confidence_threshold)
|
|
99
|
+
self.assertEqual(restored_config.enable_tracking, config.enable_tracking)
|
|
100
|
+
|
|
101
|
+
def test_base_config_json_serialization(self):
|
|
102
|
+
"""Test JSON serialization."""
|
|
103
|
+
config = BaseConfig(
|
|
104
|
+
category="test",
|
|
105
|
+
usecase="test_usecase",
|
|
106
|
+
confidence_threshold=0.7
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
# Test to_json
|
|
110
|
+
json_str = config.to_json()
|
|
111
|
+
self.assertIsInstance(json_str, str)
|
|
112
|
+
|
|
113
|
+
# Should be valid JSON
|
|
114
|
+
parsed = json.loads(json_str)
|
|
115
|
+
self.assertEqual(parsed["category"], "test")
|
|
116
|
+
|
|
117
|
+
# Test from_json
|
|
118
|
+
restored_config = BaseConfig.from_json(json_str)
|
|
119
|
+
self.assertEqual(restored_config.category, config.category)
|
|
120
|
+
self.assertEqual(restored_config.confidence_threshold, config.confidence_threshold)
|
|
121
|
+
|
|
122
|
+
def test_config_inheritance(self):
|
|
123
|
+
"""Test configuration parameter inheritance."""
|
|
124
|
+
base_config = BaseConfig(
|
|
125
|
+
category="test",
|
|
126
|
+
usecase="test_usecase",
|
|
127
|
+
confidence_threshold=0.6,
|
|
128
|
+
enable_tracking=True
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
# Override some parameters
|
|
132
|
+
child_config = BaseConfig(
|
|
133
|
+
category="test",
|
|
134
|
+
usecase="test_usecase",
|
|
135
|
+
confidence_threshold=0.8, # Override
|
|
136
|
+
enable_tracking=True, # Keep same
|
|
137
|
+
enable_counting=True # New parameter
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
self.assertEqual(child_config.confidence_threshold, 0.8)
|
|
141
|
+
self.assertEqual(child_config.enable_tracking, True)
|
|
142
|
+
self.assertEqual(child_config.enable_counting, True)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class TestPeopleCountingConfig(BasePostProcessingTest):
|
|
146
|
+
"""Test PeopleCountingConfig functionality."""
|
|
147
|
+
|
|
148
|
+
def test_people_counting_config_creation(self):
|
|
149
|
+
"""Test people counting configuration creation."""
|
|
150
|
+
zones = create_zone_polygons(["entrance", "lobby", "exit"])
|
|
151
|
+
|
|
152
|
+
config = PeopleCountingConfig(
|
|
153
|
+
confidence_threshold=0.6,
|
|
154
|
+
enable_tracking=True,
|
|
155
|
+
enable_unique_counting=True,
|
|
156
|
+
time_window_minutes=30,
|
|
157
|
+
person_categories=["person", "people"],
|
|
158
|
+
zones=zones
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
self.assertEqual(config.category, "people_counting")
|
|
162
|
+
self.assertEqual(config.usecase, "people_counting")
|
|
163
|
+
self.assertEqual(config.confidence_threshold, 0.6)
|
|
164
|
+
self.assertTrue(config.enable_tracking)
|
|
165
|
+
self.assertTrue(config.enable_unique_counting)
|
|
166
|
+
self.assertEqual(config.time_window_minutes, 30)
|
|
167
|
+
self.assertEqual(config.person_categories, ["person", "people"])
|
|
168
|
+
self.assertEqual(config.zones, zones)
|
|
169
|
+
|
|
170
|
+
def test_people_counting_config_validation(self):
|
|
171
|
+
"""Test people counting configuration validation."""
|
|
172
|
+
# Valid config
|
|
173
|
+
valid_config = PeopleCountingConfig(
|
|
174
|
+
confidence_threshold=0.6,
|
|
175
|
+
time_window_minutes=30,
|
|
176
|
+
person_categories=["person"]
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
errors = valid_config.validate()
|
|
180
|
+
self.assertEqual(len(errors), 0)
|
|
181
|
+
|
|
182
|
+
# Invalid time window
|
|
183
|
+
invalid_config = PeopleCountingConfig(
|
|
184
|
+
confidence_threshold=0.6,
|
|
185
|
+
time_window_minutes=-10, # Negative
|
|
186
|
+
person_categories=["person"]
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
errors = invalid_config.validate()
|
|
190
|
+
self.assertGreater(len(errors), 0)
|
|
191
|
+
|
|
192
|
+
# Empty person categories
|
|
193
|
+
invalid_config2 = PeopleCountingConfig(
|
|
194
|
+
confidence_threshold=0.6,
|
|
195
|
+
person_categories=[] # Empty
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
errors = invalid_config2.validate()
|
|
199
|
+
self.assertGreater(len(errors), 0)
|
|
200
|
+
|
|
201
|
+
def test_people_counting_config_with_zones(self):
|
|
202
|
+
"""Test people counting configuration with zone validation."""
|
|
203
|
+
# Valid zones
|
|
204
|
+
valid_zones = create_zone_polygons(["entrance", "lobby"])
|
|
205
|
+
|
|
206
|
+
config = PeopleCountingConfig(
|
|
207
|
+
confidence_threshold=0.6,
|
|
208
|
+
zones=valid_zones
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
errors = config.validate()
|
|
212
|
+
self.assertEqual(len(errors), 0)
|
|
213
|
+
|
|
214
|
+
# Invalid zones (too few points)
|
|
215
|
+
invalid_zones = {
|
|
216
|
+
"entrance": [[0, 0], [100, 0]] # Only 2 points
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
config_invalid = PeopleCountingConfig(
|
|
220
|
+
confidence_threshold=0.6,
|
|
221
|
+
zones=invalid_zones
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
errors = config_invalid.validate()
|
|
225
|
+
self.assertGreater(len(errors), 0)
|
|
226
|
+
|
|
227
|
+
def test_people_counting_config_serialization(self):
|
|
228
|
+
"""Test people counting configuration serialization."""
|
|
229
|
+
zones = create_zone_polygons(["entrance", "lobby"])
|
|
230
|
+
|
|
231
|
+
config = PeopleCountingConfig(
|
|
232
|
+
confidence_threshold=0.7,
|
|
233
|
+
enable_unique_counting=True,
|
|
234
|
+
zones=zones,
|
|
235
|
+
person_categories=["person", "people"]
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
# Serialize and deserialize
|
|
239
|
+
config_dict = config.to_dict()
|
|
240
|
+
restored_config = PeopleCountingConfig.from_dict(config_dict)
|
|
241
|
+
|
|
242
|
+
self.assertEqual(restored_config.confidence_threshold, config.confidence_threshold)
|
|
243
|
+
self.assertEqual(restored_config.enable_unique_counting, config.enable_unique_counting)
|
|
244
|
+
self.assertEqual(restored_config.zones, config.zones)
|
|
245
|
+
self.assertEqual(restored_config.person_categories, config.person_categories)
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
class TestCustomerServiceConfig(BasePostProcessingTest):
|
|
249
|
+
"""Test CustomerServiceConfig functionality."""
|
|
250
|
+
|
|
251
|
+
def test_customer_service_config_creation(self):
|
|
252
|
+
"""Test customer service configuration creation."""
|
|
253
|
+
areas = create_customer_service_areas()
|
|
254
|
+
|
|
255
|
+
config = CustomerServiceConfig(
|
|
256
|
+
confidence_threshold=0.7,
|
|
257
|
+
enable_tracking=True,
|
|
258
|
+
customer_categories=["customer", "person"],
|
|
259
|
+
staff_categories=["staff", "employee"],
|
|
260
|
+
service_proximity_threshold=150.0,
|
|
261
|
+
max_service_time=600.0,
|
|
262
|
+
**areas
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
self.assertEqual(config.category, "customer_service")
|
|
266
|
+
self.assertEqual(config.usecase, "customer_service")
|
|
267
|
+
self.assertEqual(config.confidence_threshold, 0.7)
|
|
268
|
+
self.assertEqual(config.customer_categories, ["customer", "person"])
|
|
269
|
+
self.assertEqual(config.staff_categories, ["staff", "employee"])
|
|
270
|
+
self.assertEqual(config.service_proximity_threshold, 150.0)
|
|
271
|
+
self.assertEqual(config.max_service_time, 600.0)
|
|
272
|
+
self.assertIn("customer_areas", config.to_dict())
|
|
273
|
+
self.assertIn("staff_areas", config.to_dict())
|
|
274
|
+
|
|
275
|
+
def test_customer_service_config_validation(self):
|
|
276
|
+
"""Test customer service configuration validation."""
|
|
277
|
+
areas = create_customer_service_areas()
|
|
278
|
+
|
|
279
|
+
# Valid config
|
|
280
|
+
valid_config = CustomerServiceConfig(
|
|
281
|
+
confidence_threshold=0.7,
|
|
282
|
+
customer_categories=["customer"],
|
|
283
|
+
staff_categories=["staff"],
|
|
284
|
+
service_proximity_threshold=150.0,
|
|
285
|
+
**areas
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
errors = valid_config.validate()
|
|
289
|
+
self.assertEqual(len(errors), 0)
|
|
290
|
+
|
|
291
|
+
# Invalid proximity threshold
|
|
292
|
+
invalid_config = CustomerServiceConfig(
|
|
293
|
+
confidence_threshold=0.7,
|
|
294
|
+
customer_categories=["customer"],
|
|
295
|
+
staff_categories=["staff"],
|
|
296
|
+
service_proximity_threshold=-50.0, # Negative
|
|
297
|
+
**areas
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
errors = invalid_config.validate()
|
|
301
|
+
self.assertGreater(len(errors), 0)
|
|
302
|
+
|
|
303
|
+
# Empty categories
|
|
304
|
+
invalid_config2 = CustomerServiceConfig(
|
|
305
|
+
confidence_threshold=0.7,
|
|
306
|
+
customer_categories=[], # Empty
|
|
307
|
+
staff_categories=["staff"],
|
|
308
|
+
**areas
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
errors = invalid_config2.validate()
|
|
312
|
+
self.assertGreater(len(errors), 0)
|
|
313
|
+
|
|
314
|
+
def test_customer_service_config_serialization(self):
|
|
315
|
+
"""Test customer service configuration serialization."""
|
|
316
|
+
areas = create_customer_service_areas()
|
|
317
|
+
|
|
318
|
+
config = CustomerServiceConfig(
|
|
319
|
+
confidence_threshold=0.8,
|
|
320
|
+
customer_categories=["person"],
|
|
321
|
+
staff_categories=["staff"],
|
|
322
|
+
service_proximity_threshold=100.0,
|
|
323
|
+
**areas
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
# Test JSON serialization
|
|
327
|
+
json_str = config.to_json()
|
|
328
|
+
restored_config = CustomerServiceConfig.from_json(json_str)
|
|
329
|
+
|
|
330
|
+
self.assertEqual(restored_config.confidence_threshold, config.confidence_threshold)
|
|
331
|
+
self.assertEqual(restored_config.customer_categories, config.customer_categories)
|
|
332
|
+
self.assertEqual(restored_config.service_proximity_threshold, config.service_proximity_threshold)
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
class TestBasicCountingTrackingConfig(BasePostProcessingTest):
|
|
336
|
+
"""Test BasicCountingTrackingConfig functionality."""
|
|
337
|
+
|
|
338
|
+
def test_basic_counting_tracking_config_creation(self):
|
|
339
|
+
"""Test basic counting tracking configuration creation."""
|
|
340
|
+
zones = create_zone_polygons(["zone_a", "zone_b"])
|
|
341
|
+
lines = {
|
|
342
|
+
"entrance_line": [[100, 200], [200, 200]],
|
|
343
|
+
"exit_line": [[400, 200], [500, 200]]
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
config = BasicCountingTrackingConfig(
|
|
347
|
+
confidence_threshold=0.5,
|
|
348
|
+
enable_tracking=True,
|
|
349
|
+
zones=zones,
|
|
350
|
+
lines=lines
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
self.assertEqual(config.category, "general")
|
|
354
|
+
self.assertEqual(config.usecase, "basic_counting_tracking")
|
|
355
|
+
self.assertEqual(config.confidence_threshold, 0.5)
|
|
356
|
+
self.assertTrue(config.enable_tracking)
|
|
357
|
+
self.assertEqual(config.zones, zones)
|
|
358
|
+
self.assertEqual(config.lines, lines)
|
|
359
|
+
|
|
360
|
+
def test_basic_counting_tracking_config_validation(self):
|
|
361
|
+
"""Test basic counting tracking configuration validation."""
|
|
362
|
+
# Valid config
|
|
363
|
+
valid_config = BasicCountingTrackingConfig(
|
|
364
|
+
confidence_threshold=0.6,
|
|
365
|
+
enable_tracking=True
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
errors = valid_config.validate()
|
|
369
|
+
self.assertEqual(len(errors), 0)
|
|
370
|
+
|
|
371
|
+
# Valid config with zones and lines
|
|
372
|
+
zones = create_zone_polygons(["zone_a"])
|
|
373
|
+
lines = {"line1": [[0, 100], [200, 100]]}
|
|
374
|
+
|
|
375
|
+
valid_config_full = BasicCountingTrackingConfig(
|
|
376
|
+
confidence_threshold=0.6,
|
|
377
|
+
zones=zones,
|
|
378
|
+
lines=lines
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
errors = valid_config_full.validate()
|
|
382
|
+
self.assertEqual(len(errors), 0)
|
|
383
|
+
|
|
384
|
+
# Invalid line (not enough points)
|
|
385
|
+
invalid_lines = {"line1": [[0, 100]]} # Only 1 point
|
|
386
|
+
|
|
387
|
+
invalid_config = BasicCountingTrackingConfig(
|
|
388
|
+
confidence_threshold=0.6,
|
|
389
|
+
lines=invalid_lines
|
|
390
|
+
)
|
|
391
|
+
|
|
392
|
+
errors = invalid_config.validate()
|
|
393
|
+
self.assertGreater(len(errors), 0)
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
class TestZoneConfig(BasePostProcessingTest):
|
|
397
|
+
"""Test ZoneConfig functionality."""
|
|
398
|
+
|
|
399
|
+
def test_zone_config_creation(self):
|
|
400
|
+
"""Test zone configuration creation."""
|
|
401
|
+
polygon = [[0, 0], [100, 0], [100, 100], [0, 100]]
|
|
402
|
+
|
|
403
|
+
zone_config = ZoneConfig(
|
|
404
|
+
name="test_zone",
|
|
405
|
+
polygon=polygon,
|
|
406
|
+
zone_type="entrance",
|
|
407
|
+
enabled=True
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
self.assertEqual(zone_config.name, "test_zone")
|
|
411
|
+
self.assertEqual(zone_config.polygon, polygon)
|
|
412
|
+
self.assertEqual(zone_config.zone_type, "entrance")
|
|
413
|
+
self.assertTrue(zone_config.enabled)
|
|
414
|
+
|
|
415
|
+
def test_zone_config_validation(self):
|
|
416
|
+
"""Test zone configuration validation."""
|
|
417
|
+
# Valid zone
|
|
418
|
+
valid_polygon = [[0, 0], [100, 0], [100, 100], [0, 100]]
|
|
419
|
+
valid_zone = ZoneConfig(
|
|
420
|
+
name="valid_zone",
|
|
421
|
+
polygon=valid_polygon
|
|
422
|
+
)
|
|
423
|
+
|
|
424
|
+
errors = valid_zone.validate()
|
|
425
|
+
self.assertEqual(len(errors), 0)
|
|
426
|
+
|
|
427
|
+
# Invalid zone (too few points)
|
|
428
|
+
invalid_polygon = [[0, 0], [100, 0]]
|
|
429
|
+
invalid_zone = ZoneConfig(
|
|
430
|
+
name="invalid_zone",
|
|
431
|
+
polygon=invalid_polygon
|
|
432
|
+
)
|
|
433
|
+
|
|
434
|
+
errors = invalid_zone.validate()
|
|
435
|
+
self.assertGreater(len(errors), 0)
|
|
436
|
+
|
|
437
|
+
# Empty name
|
|
438
|
+
invalid_zone2 = ZoneConfig(
|
|
439
|
+
name="",
|
|
440
|
+
polygon=valid_polygon
|
|
441
|
+
)
|
|
442
|
+
|
|
443
|
+
errors = invalid_zone2.validate()
|
|
444
|
+
self.assertGreater(len(errors), 0)
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
class TestAlertConfig(BasePostProcessingTest):
|
|
448
|
+
"""Test AlertConfig functionality."""
|
|
449
|
+
|
|
450
|
+
def test_alert_config_creation(self):
|
|
451
|
+
"""Test alert configuration creation."""
|
|
452
|
+
alert_config = AlertConfig(
|
|
453
|
+
enabled=True,
|
|
454
|
+
count_thresholds={"person": 50, "vehicle": 20},
|
|
455
|
+
occupancy_thresholds={"lobby": 30, "entrance": 15},
|
|
456
|
+
alert_cooldown_minutes=5
|
|
457
|
+
)
|
|
458
|
+
|
|
459
|
+
self.assertTrue(alert_config.enabled)
|
|
460
|
+
self.assertEqual(alert_config.count_thresholds["person"], 50)
|
|
461
|
+
self.assertEqual(alert_config.occupancy_thresholds["lobby"], 30)
|
|
462
|
+
self.assertEqual(alert_config.alert_cooldown_minutes, 5)
|
|
463
|
+
|
|
464
|
+
def test_alert_config_validation(self):
|
|
465
|
+
"""Test alert configuration validation."""
|
|
466
|
+
# Valid config
|
|
467
|
+
valid_config = AlertConfig(
|
|
468
|
+
enabled=True,
|
|
469
|
+
count_thresholds={"person": 50},
|
|
470
|
+
alert_cooldown_minutes=5
|
|
471
|
+
)
|
|
472
|
+
|
|
473
|
+
errors = valid_config.validate()
|
|
474
|
+
self.assertEqual(len(errors), 0)
|
|
475
|
+
|
|
476
|
+
# Invalid cooldown
|
|
477
|
+
invalid_config = AlertConfig(
|
|
478
|
+
enabled=True,
|
|
479
|
+
alert_cooldown_minutes=-1 # Negative
|
|
480
|
+
)
|
|
481
|
+
|
|
482
|
+
errors = invalid_config.validate()
|
|
483
|
+
self.assertGreater(len(errors), 0)
|
|
484
|
+
|
|
485
|
+
# Invalid threshold values
|
|
486
|
+
invalid_config2 = AlertConfig(
|
|
487
|
+
enabled=True,
|
|
488
|
+
count_thresholds={"person": -10} # Negative threshold
|
|
489
|
+
)
|
|
490
|
+
|
|
491
|
+
errors = invalid_config2.validate()
|
|
492
|
+
self.assertGreater(len(errors), 0)
|
|
493
|
+
|
|
494
|
+
|
|
495
|
+
class TestTrackingConfig(BasePostProcessingTest):
|
|
496
|
+
"""Test TrackingConfig functionality."""
|
|
497
|
+
|
|
498
|
+
def test_tracking_config_creation(self):
|
|
499
|
+
"""Test tracking configuration creation."""
|
|
500
|
+
tracking_config = TrackingConfig(
|
|
501
|
+
enabled=True,
|
|
502
|
+
max_age=30,
|
|
503
|
+
min_hits=3,
|
|
504
|
+
iou_threshold=0.3,
|
|
505
|
+
track_buffer_size=1000
|
|
506
|
+
)
|
|
507
|
+
|
|
508
|
+
self.assertTrue(tracking_config.enabled)
|
|
509
|
+
self.assertEqual(tracking_config.max_age, 30)
|
|
510
|
+
self.assertEqual(tracking_config.min_hits, 3)
|
|
511
|
+
self.assertEqual(tracking_config.iou_threshold, 0.3)
|
|
512
|
+
self.assertEqual(tracking_config.track_buffer_size, 1000)
|
|
513
|
+
|
|
514
|
+
def test_tracking_config_validation(self):
|
|
515
|
+
"""Test tracking configuration validation."""
|
|
516
|
+
# Valid config
|
|
517
|
+
valid_config = TrackingConfig(
|
|
518
|
+
enabled=True,
|
|
519
|
+
max_age=30,
|
|
520
|
+
min_hits=3,
|
|
521
|
+
iou_threshold=0.3
|
|
522
|
+
)
|
|
523
|
+
|
|
524
|
+
errors = valid_config.validate()
|
|
525
|
+
self.assertEqual(len(errors), 0)
|
|
526
|
+
|
|
527
|
+
# Invalid IoU threshold
|
|
528
|
+
invalid_config = TrackingConfig(
|
|
529
|
+
enabled=True,
|
|
530
|
+
iou_threshold=1.5 # > 1.0
|
|
531
|
+
)
|
|
532
|
+
|
|
533
|
+
errors = invalid_config.validate()
|
|
534
|
+
self.assertGreater(len(errors), 0)
|
|
535
|
+
|
|
536
|
+
# Invalid max_age
|
|
537
|
+
invalid_config2 = TrackingConfig(
|
|
538
|
+
enabled=True,
|
|
539
|
+
max_age=0 # Should be > 0
|
|
540
|
+
)
|
|
541
|
+
|
|
542
|
+
errors = invalid_config2.validate()
|
|
543
|
+
self.assertGreater(len(errors), 0)
|
|
544
|
+
|
|
545
|
+
|
|
546
|
+
class TestConfigManager(BasePostProcessingTest):
|
|
547
|
+
"""Test ConfigManager functionality."""
|
|
548
|
+
|
|
549
|
+
def setUp(self):
|
|
550
|
+
"""Set up test environment."""
|
|
551
|
+
super().setUp()
|
|
552
|
+
self.config_manager = ConfigManager()
|
|
553
|
+
|
|
554
|
+
def test_config_registration(self):
|
|
555
|
+
"""Test configuration registration."""
|
|
556
|
+
# Register configs
|
|
557
|
+
self.config_manager.register_config("people_counting", PeopleCountingConfig)
|
|
558
|
+
self.config_manager.register_config("customer_service", CustomerServiceConfig)
|
|
559
|
+
|
|
560
|
+
# Check registration
|
|
561
|
+
self.assertIn("people_counting", self.config_manager.get_registered_configs())
|
|
562
|
+
self.assertIn("customer_service", self.config_manager.get_registered_configs())
|
|
563
|
+
|
|
564
|
+
def test_config_creation(self):
|
|
565
|
+
"""Test configuration creation through manager."""
|
|
566
|
+
self.config_manager.register_config("people_counting", PeopleCountingConfig)
|
|
567
|
+
|
|
568
|
+
# Create config
|
|
569
|
+
config = self.config_manager.create_config(
|
|
570
|
+
"people_counting",
|
|
571
|
+
confidence_threshold=0.7,
|
|
572
|
+
enable_tracking=True
|
|
573
|
+
)
|
|
574
|
+
|
|
575
|
+
self.assertIsInstance(config, PeopleCountingConfig)
|
|
576
|
+
self.assertEqual(config.confidence_threshold, 0.7)
|
|
577
|
+
self.assertTrue(config.enable_tracking)
|
|
578
|
+
|
|
579
|
+
def test_config_validation_through_manager(self):
|
|
580
|
+
"""Test configuration validation through manager."""
|
|
581
|
+
self.config_manager.register_config("people_counting", PeopleCountingConfig)
|
|
582
|
+
|
|
583
|
+
# Valid config
|
|
584
|
+
valid_config = self.config_manager.create_config(
|
|
585
|
+
"people_counting",
|
|
586
|
+
confidence_threshold=0.6
|
|
587
|
+
)
|
|
588
|
+
|
|
589
|
+
is_valid, errors = self.config_manager.validate_config(valid_config)
|
|
590
|
+
self.assertTrue(is_valid)
|
|
591
|
+
self.assertEqual(len(errors), 0)
|
|
592
|
+
|
|
593
|
+
# Invalid config
|
|
594
|
+
invalid_config = PeopleCountingConfig(
|
|
595
|
+
confidence_threshold=1.5 # Invalid
|
|
596
|
+
)
|
|
597
|
+
|
|
598
|
+
is_valid, errors = self.config_manager.validate_config(invalid_config)
|
|
599
|
+
self.assertFalse(is_valid)
|
|
600
|
+
self.assertGreater(len(errors), 0)
|
|
601
|
+
|
|
602
|
+
def test_config_file_operations(self):
|
|
603
|
+
"""Test configuration file save/load operations."""
|
|
604
|
+
self.config_manager.register_config("people_counting", PeopleCountingConfig)
|
|
605
|
+
|
|
606
|
+
# Create config
|
|
607
|
+
config = self.config_manager.create_config(
|
|
608
|
+
"people_counting",
|
|
609
|
+
confidence_threshold=0.8,
|
|
610
|
+
enable_tracking=True,
|
|
611
|
+
person_categories=["person", "people"]
|
|
612
|
+
)
|
|
613
|
+
|
|
614
|
+
# Save to file
|
|
615
|
+
config_file = os.path.join(self.temp_dir, "test_config.json")
|
|
616
|
+
self.config_manager.save_config_to_file(config, config_file)
|
|
617
|
+
|
|
618
|
+
# Check file exists
|
|
619
|
+
self.assertTrue(os.path.exists(config_file))
|
|
620
|
+
|
|
621
|
+
# Load from file
|
|
622
|
+
loaded_config = self.config_manager.load_config_from_file(config_file)
|
|
623
|
+
|
|
624
|
+
self.assertIsInstance(loaded_config, PeopleCountingConfig)
|
|
625
|
+
self.assertEqual(loaded_config.confidence_threshold, config.confidence_threshold)
|
|
626
|
+
self.assertEqual(loaded_config.enable_tracking, config.enable_tracking)
|
|
627
|
+
self.assertEqual(loaded_config.person_categories, config.person_categories)
|
|
628
|
+
|
|
629
|
+
def test_config_yaml_operations(self):
|
|
630
|
+
"""Test YAML configuration operations."""
|
|
631
|
+
self.config_manager.register_config("customer_service", CustomerServiceConfig)
|
|
632
|
+
|
|
633
|
+
areas = create_customer_service_areas()
|
|
634
|
+
config = self.config_manager.create_config(
|
|
635
|
+
"customer_service",
|
|
636
|
+
confidence_threshold=0.7,
|
|
637
|
+
customer_categories=["person"],
|
|
638
|
+
staff_categories=["staff"],
|
|
639
|
+
**areas
|
|
640
|
+
)
|
|
641
|
+
|
|
642
|
+
# Save to YAML file
|
|
643
|
+
yaml_file = os.path.join(self.temp_dir, "test_config.yaml")
|
|
644
|
+
self.config_manager.save_config_to_file(config, yaml_file, format="yaml")
|
|
645
|
+
|
|
646
|
+
# Load from YAML file
|
|
647
|
+
loaded_config = self.config_manager.load_config_from_file(yaml_file)
|
|
648
|
+
|
|
649
|
+
self.assertIsInstance(loaded_config, CustomerServiceConfig)
|
|
650
|
+
self.assertEqual(loaded_config.confidence_threshold, config.confidence_threshold)
|
|
651
|
+
self.assertEqual(loaded_config.customer_categories, config.customer_categories)
|
|
652
|
+
|
|
653
|
+
def test_config_import_export(self):
|
|
654
|
+
"""Test configuration import/export functionality."""
|
|
655
|
+
self.config_manager.register_config("people_counting", PeopleCountingConfig)
|
|
656
|
+
|
|
657
|
+
# Create multiple configs
|
|
658
|
+
configs = {
|
|
659
|
+
"config1": self.config_manager.create_config(
|
|
660
|
+
"people_counting",
|
|
661
|
+
confidence_threshold=0.6
|
|
662
|
+
),
|
|
663
|
+
"config2": self.config_manager.create_config(
|
|
664
|
+
"people_counting",
|
|
665
|
+
confidence_threshold=0.8,
|
|
666
|
+
enable_tracking=True
|
|
667
|
+
)
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
# Export configs
|
|
671
|
+
export_file = os.path.join(self.temp_dir, "exported_configs.json")
|
|
672
|
+
self.config_manager.export_configs(configs, export_file)
|
|
673
|
+
|
|
674
|
+
# Import configs
|
|
675
|
+
imported_configs = self.config_manager.import_configs(export_file)
|
|
676
|
+
|
|
677
|
+
self.assertEqual(len(imported_configs), 2)
|
|
678
|
+
self.assertIn("config1", imported_configs)
|
|
679
|
+
self.assertIn("config2", imported_configs)
|
|
680
|
+
|
|
681
|
+
# Check imported config values
|
|
682
|
+
self.assertEqual(imported_configs["config1"].confidence_threshold, 0.6)
|
|
683
|
+
self.assertEqual(imported_configs["config2"].confidence_threshold, 0.8)
|
|
684
|
+
self.assertTrue(imported_configs["config2"].enable_tracking)
|
|
685
|
+
|
|
686
|
+
def test_config_schema_generation(self):
|
|
687
|
+
"""Test configuration schema generation."""
|
|
688
|
+
self.config_manager.register_config("people_counting", PeopleCountingConfig)
|
|
689
|
+
|
|
690
|
+
# Get schema
|
|
691
|
+
schema = self.config_manager.get_config_schema("people_counting")
|
|
692
|
+
|
|
693
|
+
self.assertIsInstance(schema, dict)
|
|
694
|
+
self.assertIn("type", schema)
|
|
695
|
+
self.assertIn("properties", schema)
|
|
696
|
+
|
|
697
|
+
# Check for expected properties
|
|
698
|
+
properties = schema["properties"]
|
|
699
|
+
self.assertIn("confidence_threshold", properties)
|
|
700
|
+
self.assertIn("enable_tracking", properties)
|
|
701
|
+
self.assertIn("person_categories", properties)
|
|
702
|
+
|
|
703
|
+
def test_config_defaults(self):
|
|
704
|
+
"""Test configuration default values."""
|
|
705
|
+
self.config_manager.register_config("basic_counting_tracking", BasicCountingTrackingConfig)
|
|
706
|
+
|
|
707
|
+
# Create config with defaults
|
|
708
|
+
config = self.config_manager.create_config("basic_counting_tracking")
|
|
709
|
+
|
|
710
|
+
# Should have default values
|
|
711
|
+
self.assertIsNotNone(config.confidence_threshold)
|
|
712
|
+
self.assertIsNotNone(config.category)
|
|
713
|
+
self.assertIsNotNone(config.usecase)
|
|
714
|
+
|
|
715
|
+
# Validate defaults
|
|
716
|
+
is_valid, errors = self.config_manager.validate_config(config)
|
|
717
|
+
self.assertTrue(is_valid, f"Default config should be valid: {errors}")
|
|
718
|
+
|
|
719
|
+
|
|
720
|
+
class TestConfigIntegration(BasePostProcessingTest):
|
|
721
|
+
"""Integration tests for configuration system."""
|
|
722
|
+
|
|
723
|
+
def test_config_roundtrip_serialization(self):
|
|
724
|
+
"""Test complete serialization roundtrip."""
|
|
725
|
+
areas = create_customer_service_areas()
|
|
726
|
+
zones = create_zone_polygons(["entrance", "lobby"])
|
|
727
|
+
|
|
728
|
+
original_config = CustomerServiceConfig(
|
|
729
|
+
confidence_threshold=0.75,
|
|
730
|
+
enable_tracking=True,
|
|
731
|
+
customer_categories=["person", "customer"],
|
|
732
|
+
staff_categories=["staff", "employee"],
|
|
733
|
+
service_proximity_threshold=120.0,
|
|
734
|
+
max_service_time=480.0,
|
|
735
|
+
zones=zones,
|
|
736
|
+
**areas
|
|
737
|
+
)
|
|
738
|
+
|
|
739
|
+
# JSON roundtrip
|
|
740
|
+
json_str = original_config.to_json()
|
|
741
|
+
json_restored = CustomerServiceConfig.from_json(json_str)
|
|
742
|
+
|
|
743
|
+
self.assertEqual(json_restored.confidence_threshold, original_config.confidence_threshold)
|
|
744
|
+
self.assertEqual(json_restored.customer_categories, original_config.customer_categories)
|
|
745
|
+
self.assertEqual(json_restored.service_proximity_threshold, original_config.service_proximity_threshold)
|
|
746
|
+
|
|
747
|
+
# Dict roundtrip
|
|
748
|
+
config_dict = original_config.to_dict()
|
|
749
|
+
dict_restored = CustomerServiceConfig.from_dict(config_dict)
|
|
750
|
+
|
|
751
|
+
self.assertEqual(dict_restored.confidence_threshold, original_config.confidence_threshold)
|
|
752
|
+
self.assertEqual(dict_restored.zones, original_config.zones)
|
|
753
|
+
|
|
754
|
+
def test_config_parameter_override(self):
|
|
755
|
+
"""Test configuration parameter override behavior."""
|
|
756
|
+
base_config = PeopleCountingConfig(
|
|
757
|
+
confidence_threshold=0.5,
|
|
758
|
+
enable_tracking=False,
|
|
759
|
+
time_window_minutes=15
|
|
760
|
+
)
|
|
761
|
+
|
|
762
|
+
# Override with new parameters
|
|
763
|
+
override_dict = {
|
|
764
|
+
"confidence_threshold": 0.8,
|
|
765
|
+
"enable_tracking": True,
|
|
766
|
+
"enable_unique_counting": True # New parameter
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
# Create new config with overrides
|
|
770
|
+
overridden_config = PeopleCountingConfig(
|
|
771
|
+
**{**base_config.to_dict(), **override_dict}
|
|
772
|
+
)
|
|
773
|
+
|
|
774
|
+
# Check overrides
|
|
775
|
+
self.assertEqual(overridden_config.confidence_threshold, 0.8)
|
|
776
|
+
self.assertTrue(overridden_config.enable_tracking)
|
|
777
|
+
self.assertTrue(overridden_config.enable_unique_counting)
|
|
778
|
+
|
|
779
|
+
# Check preserved values
|
|
780
|
+
self.assertEqual(overridden_config.time_window_minutes, 15)
|
|
781
|
+
|
|
782
|
+
def test_config_validation_edge_cases(self):
|
|
783
|
+
"""Test configuration validation with edge cases."""
|
|
784
|
+
# Test with None values
|
|
785
|
+
config_with_none = PeopleCountingConfig(
|
|
786
|
+
confidence_threshold=None, # Should use default
|
|
787
|
+
person_categories=None # Should use default
|
|
788
|
+
)
|
|
789
|
+
|
|
790
|
+
# Should handle None values gracefully
|
|
791
|
+
errors = config_with_none.validate()
|
|
792
|
+
# May have errors or use defaults - depends on implementation
|
|
793
|
+
|
|
794
|
+
# Test with extreme values
|
|
795
|
+
extreme_config = PeopleCountingConfig(
|
|
796
|
+
confidence_threshold=0.0001, # Very low but valid
|
|
797
|
+
time_window_minutes=10080 # 1 week in minutes
|
|
798
|
+
)
|
|
799
|
+
|
|
800
|
+
errors = extreme_config.validate()
|
|
801
|
+
self.assertEqual(len(errors), 0) # Should be valid
|
|
802
|
+
|
|
803
|
+
def test_multiple_config_types_integration(self):
|
|
804
|
+
"""Test integration of multiple configuration types."""
|
|
805
|
+
manager = ConfigManager()
|
|
806
|
+
|
|
807
|
+
# Register all config types
|
|
808
|
+
manager.register_config("people_counting", PeopleCountingConfig)
|
|
809
|
+
manager.register_config("customer_service", CustomerServiceConfig)
|
|
810
|
+
manager.register_config("basic_counting_tracking", BasicCountingTrackingConfig)
|
|
811
|
+
|
|
812
|
+
# Create configs of different types
|
|
813
|
+
configs = {
|
|
814
|
+
"people_config": manager.create_config(
|
|
815
|
+
"people_counting",
|
|
816
|
+
confidence_threshold=0.6,
|
|
817
|
+
enable_tracking=True
|
|
818
|
+
),
|
|
819
|
+
"service_config": manager.create_config(
|
|
820
|
+
"customer_service",
|
|
821
|
+
confidence_threshold=0.7,
|
|
822
|
+
customer_categories=["person"],
|
|
823
|
+
staff_categories=["staff"]
|
|
824
|
+
),
|
|
825
|
+
"basic_config": manager.create_config(
|
|
826
|
+
"basic_counting_tracking",
|
|
827
|
+
confidence_threshold=0.5,
|
|
828
|
+
enable_tracking=True
|
|
829
|
+
)
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
# Validate all configs
|
|
833
|
+
for name, config in configs.items():
|
|
834
|
+
is_valid, errors = manager.validate_config(config)
|
|
835
|
+
self.assertTrue(is_valid, f"Config {name} should be valid: {errors}")
|
|
836
|
+
|
|
837
|
+
# Export all configs
|
|
838
|
+
export_file = os.path.join(self.temp_dir, "multi_configs.json")
|
|
839
|
+
manager.export_configs(configs, export_file)
|
|
840
|
+
|
|
841
|
+
# Import and verify
|
|
842
|
+
imported = manager.import_configs(export_file)
|
|
843
|
+
self.assertEqual(len(imported), 3)
|
|
844
|
+
|
|
845
|
+
# Check types are preserved
|
|
846
|
+
self.assertIsInstance(imported["people_config"], PeopleCountingConfig)
|
|
847
|
+
self.assertIsInstance(imported["service_config"], CustomerServiceConfig)
|
|
848
|
+
self.assertIsInstance(imported["basic_config"], BasicCountingTrackingConfig)
|
|
849
|
+
|
|
850
|
+
|
|
851
|
+
if __name__ == "__main__":
|
|
852
|
+
unittest.main()
|