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,356 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Test utilities and base classes for post processing tests.
|
|
3
|
+
|
|
4
|
+
This module provides base test classes, assertion helpers, performance monitoring,
|
|
5
|
+
and common utilities for testing post processing functionality.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import unittest
|
|
9
|
+
import time
|
|
10
|
+
import psutil
|
|
11
|
+
import os
|
|
12
|
+
from typing import Any, Dict, List, Optional, Union
|
|
13
|
+
from dataclasses import dataclass
|
|
14
|
+
import tempfile
|
|
15
|
+
import json
|
|
16
|
+
import yaml
|
|
17
|
+
|
|
18
|
+
# Fix imports for proper module resolution
|
|
19
|
+
import sys
|
|
20
|
+
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../../src'))
|
|
21
|
+
|
|
22
|
+
from src.matrice_analytics.post_processing import (
|
|
23
|
+
PostProcessor, ProcessingResult, ProcessingContext, ProcessingStatus,
|
|
24
|
+
PeopleCountingConfig, CustomerServiceConfig, BaseConfig
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass
|
|
29
|
+
class PerformanceMetrics:
|
|
30
|
+
"""Container for performance test metrics."""
|
|
31
|
+
execution_time: float
|
|
32
|
+
memory_usage_mb: float
|
|
33
|
+
cpu_usage_percent: float
|
|
34
|
+
peak_memory_mb: float
|
|
35
|
+
|
|
36
|
+
def is_within_limits(self, max_time: float = 5.0, max_memory_mb: float = 500.0) -> bool:
|
|
37
|
+
"""Check if metrics are within acceptable limits."""
|
|
38
|
+
return (self.execution_time <= max_time and
|
|
39
|
+
self.peak_memory_mb <= max_memory_mb)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class BasePostProcessingTest(unittest.TestCase):
|
|
43
|
+
"""Base test class for all post processing tests."""
|
|
44
|
+
|
|
45
|
+
def setUp(self):
|
|
46
|
+
"""Set up test environment."""
|
|
47
|
+
self.processor = PostProcessor()
|
|
48
|
+
self.temp_dir = tempfile.mkdtemp()
|
|
49
|
+
self.start_time = time.time()
|
|
50
|
+
self.process = psutil.Process(os.getpid())
|
|
51
|
+
self.initial_memory = self.process.memory_info().rss / 1024 / 1024 # MB
|
|
52
|
+
|
|
53
|
+
def tearDown(self):
|
|
54
|
+
"""Clean up test environment."""
|
|
55
|
+
# Clean up temp files
|
|
56
|
+
import shutil
|
|
57
|
+
if os.path.exists(self.temp_dir):
|
|
58
|
+
shutil.rmtree(self.temp_dir)
|
|
59
|
+
|
|
60
|
+
def measure_performance(self, func, *args, **kwargs) -> tuple[Any, PerformanceMetrics]:
|
|
61
|
+
"""Measure performance of a function call."""
|
|
62
|
+
# Record initial state
|
|
63
|
+
start_time = time.time()
|
|
64
|
+
start_memory = self.process.memory_info().rss / 1024 / 1024
|
|
65
|
+
cpu_percent_start = self.process.cpu_percent()
|
|
66
|
+
|
|
67
|
+
# Execute function
|
|
68
|
+
result = func(*args, **kwargs)
|
|
69
|
+
|
|
70
|
+
# Record final state
|
|
71
|
+
end_time = time.time()
|
|
72
|
+
end_memory = self.process.memory_info().rss / 1024 / 1024
|
|
73
|
+
cpu_percent_end = self.process.cpu_percent()
|
|
74
|
+
|
|
75
|
+
# Calculate metrics
|
|
76
|
+
metrics = PerformanceMetrics(
|
|
77
|
+
execution_time=end_time - start_time,
|
|
78
|
+
memory_usage_mb=end_memory - start_memory,
|
|
79
|
+
cpu_usage_percent=(cpu_percent_end + cpu_percent_start) / 2,
|
|
80
|
+
peak_memory_mb=max(start_memory, end_memory)
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
return result, metrics
|
|
84
|
+
|
|
85
|
+
def assert_processing_result(self, result: ProcessingResult,
|
|
86
|
+
expected_status: ProcessingStatus = ProcessingStatus.SUCCESS,
|
|
87
|
+
min_insights: int = 0,
|
|
88
|
+
max_warnings: int = 10,
|
|
89
|
+
required_metrics: List[str] = None):
|
|
90
|
+
"""Assert processing result meets expectations."""
|
|
91
|
+
self.assertIsInstance(result, ProcessingResult)
|
|
92
|
+
self.assertEqual(result.status, expected_status)
|
|
93
|
+
|
|
94
|
+
if expected_status == ProcessingStatus.SUCCESS:
|
|
95
|
+
self.assertIsNotNone(result.data)
|
|
96
|
+
self.assertGreaterEqual(len(result.insights), min_insights)
|
|
97
|
+
self.assertLessEqual(len(result.warnings), max_warnings)
|
|
98
|
+
|
|
99
|
+
if required_metrics:
|
|
100
|
+
for metric in required_metrics:
|
|
101
|
+
self.assertIn(metric, result.metrics)
|
|
102
|
+
|
|
103
|
+
elif expected_status == ProcessingStatus.ERROR:
|
|
104
|
+
self.assertIsNotNone(result.error_message)
|
|
105
|
+
self.assertIsNotNone(result.error_type)
|
|
106
|
+
|
|
107
|
+
def assert_config_valid(self, config: BaseConfig):
|
|
108
|
+
"""Assert configuration is valid."""
|
|
109
|
+
errors = config.validate()
|
|
110
|
+
self.assertEqual(len(errors), 0, f"Configuration validation failed: {errors}")
|
|
111
|
+
|
|
112
|
+
def create_temp_config_file(self, config: Dict[str, Any], format: str = "json") -> str:
|
|
113
|
+
"""Create temporary configuration file."""
|
|
114
|
+
if format == "json":
|
|
115
|
+
filename = os.path.join(self.temp_dir, "test_config.json")
|
|
116
|
+
with open(filename, 'w') as f:
|
|
117
|
+
json.dump(config, f, indent=2)
|
|
118
|
+
elif format == "yaml":
|
|
119
|
+
filename = os.path.join(self.temp_dir, "test_config.yaml")
|
|
120
|
+
with open(filename, 'w') as f:
|
|
121
|
+
yaml.dump(config, f, default_flow_style=False)
|
|
122
|
+
else:
|
|
123
|
+
raise ValueError(f"Unsupported format: {format}")
|
|
124
|
+
|
|
125
|
+
return filename
|
|
126
|
+
|
|
127
|
+
def assert_performance_acceptable(self, metrics: PerformanceMetrics,
|
|
128
|
+
max_time: float = 5.0,
|
|
129
|
+
max_memory_mb: float = 500.0):
|
|
130
|
+
"""Assert performance metrics are acceptable."""
|
|
131
|
+
self.assertLessEqual(metrics.execution_time, max_time,
|
|
132
|
+
f"Execution time {metrics.execution_time:.2f}s exceeds limit {max_time}s")
|
|
133
|
+
self.assertLessEqual(metrics.peak_memory_mb, max_memory_mb,
|
|
134
|
+
f"Peak memory {metrics.peak_memory_mb:.2f}MB exceeds limit {max_memory_mb}MB")
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
class StressTestMixin:
|
|
138
|
+
"""Mixin for stress testing functionality."""
|
|
139
|
+
|
|
140
|
+
def run_stress_test(self, test_func, iterations: int = 100,
|
|
141
|
+
max_failures: int = 5) -> Dict[str, Any]:
|
|
142
|
+
"""Run stress test with multiple iterations."""
|
|
143
|
+
results = {
|
|
144
|
+
"total_iterations": iterations,
|
|
145
|
+
"successful": 0,
|
|
146
|
+
"failed": 0,
|
|
147
|
+
"errors": [],
|
|
148
|
+
"execution_times": [],
|
|
149
|
+
"memory_usage": []
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
for i in range(iterations):
|
|
153
|
+
try:
|
|
154
|
+
start_time = time.time()
|
|
155
|
+
start_memory = psutil.Process(os.getpid()).memory_info().rss / 1024 / 1024
|
|
156
|
+
|
|
157
|
+
test_func()
|
|
158
|
+
|
|
159
|
+
end_time = time.time()
|
|
160
|
+
end_memory = psutil.Process(os.getpid()).memory_info().rss / 1024 / 1024
|
|
161
|
+
|
|
162
|
+
results["successful"] += 1
|
|
163
|
+
results["execution_times"].append(end_time - start_time)
|
|
164
|
+
results["memory_usage"].append(end_memory - start_memory)
|
|
165
|
+
|
|
166
|
+
except Exception as e:
|
|
167
|
+
results["failed"] += 1
|
|
168
|
+
results["errors"].append(f"Iteration {i}: {str(e)}")
|
|
169
|
+
|
|
170
|
+
if results["failed"] > max_failures:
|
|
171
|
+
break
|
|
172
|
+
|
|
173
|
+
# Calculate statistics
|
|
174
|
+
if results["execution_times"]:
|
|
175
|
+
results["avg_execution_time"] = sum(results["execution_times"]) / len(results["execution_times"])
|
|
176
|
+
results["max_execution_time"] = max(results["execution_times"])
|
|
177
|
+
results["min_execution_time"] = min(results["execution_times"])
|
|
178
|
+
|
|
179
|
+
if results["memory_usage"]:
|
|
180
|
+
results["avg_memory_usage"] = sum(results["memory_usage"]) / len(results["memory_usage"])
|
|
181
|
+
results["max_memory_usage"] = max(results["memory_usage"])
|
|
182
|
+
|
|
183
|
+
return results
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
class ValidationHelpers:
|
|
187
|
+
"""Helper methods for validation testing."""
|
|
188
|
+
|
|
189
|
+
@staticmethod
|
|
190
|
+
def create_invalid_bbox() -> List[float]:
|
|
191
|
+
"""Create invalid bounding box for testing."""
|
|
192
|
+
return [100, 100, 50, 50] # x2 < x1, y2 < y1
|
|
193
|
+
|
|
194
|
+
@staticmethod
|
|
195
|
+
def create_invalid_polygon() -> List[List[float]]:
|
|
196
|
+
"""Create invalid polygon for testing."""
|
|
197
|
+
return [[0, 0], [100, 0]] # Only 2 points
|
|
198
|
+
|
|
199
|
+
@staticmethod
|
|
200
|
+
def create_invalid_confidence() -> float:
|
|
201
|
+
"""Create invalid confidence value for testing."""
|
|
202
|
+
return 1.5 # > 1.0
|
|
203
|
+
|
|
204
|
+
@staticmethod
|
|
205
|
+
def create_empty_detection_results() -> List[Dict[str, Any]]:
|
|
206
|
+
"""Create empty detection results for edge case testing."""
|
|
207
|
+
return []
|
|
208
|
+
|
|
209
|
+
@staticmethod
|
|
210
|
+
def create_malformed_detection_results() -> List[Dict[str, Any]]:
|
|
211
|
+
"""Create malformed detection results for error testing."""
|
|
212
|
+
return [
|
|
213
|
+
{"bbox": [0, 0, 100]}, # Missing coordinate
|
|
214
|
+
{"confidence": 0.8}, # Missing bbox
|
|
215
|
+
{"bbox": [0, 0, 100, 100], "confidence": "high"}, # Wrong type
|
|
216
|
+
]
|
|
217
|
+
|
|
218
|
+
@staticmethod
|
|
219
|
+
def create_extreme_values() -> Dict[str, Any]:
|
|
220
|
+
"""Create extreme values for boundary testing."""
|
|
221
|
+
return {
|
|
222
|
+
"huge_bbox": [0, 0, 999999, 999999],
|
|
223
|
+
"tiny_bbox": [0, 0, 1, 1],
|
|
224
|
+
"negative_coords": [-100, -100, 100, 100],
|
|
225
|
+
"zero_confidence": 0.0,
|
|
226
|
+
"max_confidence": 1.0,
|
|
227
|
+
"huge_polygon": [[i, i] for i in range(1000)],
|
|
228
|
+
"empty_string": "",
|
|
229
|
+
"none_value": None
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
class ConcurrencyTestMixin:
|
|
234
|
+
"""Mixin for testing concurrent processing."""
|
|
235
|
+
|
|
236
|
+
def run_concurrent_test(self, test_func, num_threads: int = 5,
|
|
237
|
+
iterations_per_thread: int = 10) -> Dict[str, Any]:
|
|
238
|
+
"""Run concurrent test with multiple threads."""
|
|
239
|
+
import threading
|
|
240
|
+
import queue
|
|
241
|
+
|
|
242
|
+
results_queue = queue.Queue()
|
|
243
|
+
threads = []
|
|
244
|
+
|
|
245
|
+
def worker():
|
|
246
|
+
thread_results = []
|
|
247
|
+
for _ in range(iterations_per_thread):
|
|
248
|
+
try:
|
|
249
|
+
result = test_func()
|
|
250
|
+
thread_results.append(("success", result))
|
|
251
|
+
except Exception as e:
|
|
252
|
+
thread_results.append(("error", str(e)))
|
|
253
|
+
results_queue.put(thread_results)
|
|
254
|
+
|
|
255
|
+
# Start threads
|
|
256
|
+
for _ in range(num_threads):
|
|
257
|
+
thread = threading.Thread(target=worker)
|
|
258
|
+
threads.append(thread)
|
|
259
|
+
thread.start()
|
|
260
|
+
|
|
261
|
+
# Wait for completion
|
|
262
|
+
for thread in threads:
|
|
263
|
+
thread.join()
|
|
264
|
+
|
|
265
|
+
# Collect results
|
|
266
|
+
all_results = []
|
|
267
|
+
while not results_queue.empty():
|
|
268
|
+
all_results.extend(results_queue.get())
|
|
269
|
+
|
|
270
|
+
# Analyze results
|
|
271
|
+
successful = sum(1 for status, _ in all_results if status == "success")
|
|
272
|
+
failed = sum(1 for status, _ in all_results if status == "error")
|
|
273
|
+
errors = [result for status, result in all_results if status == "error"]
|
|
274
|
+
|
|
275
|
+
return {
|
|
276
|
+
"total_operations": len(all_results),
|
|
277
|
+
"successful": successful,
|
|
278
|
+
"failed": failed,
|
|
279
|
+
"success_rate": successful / len(all_results) if all_results else 0,
|
|
280
|
+
"errors": errors[:10] # Limit error list
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
class MockDataGenerator:
|
|
285
|
+
"""Generate mock data for testing."""
|
|
286
|
+
|
|
287
|
+
@staticmethod
|
|
288
|
+
def create_detection_batch(batch_size: int = 100) -> List[Dict[str, Any]]:
|
|
289
|
+
"""Create batch of detection results."""
|
|
290
|
+
import random
|
|
291
|
+
|
|
292
|
+
detections = []
|
|
293
|
+
categories = ["person", "car", "bike", "truck", "bus"]
|
|
294
|
+
|
|
295
|
+
for i in range(batch_size):
|
|
296
|
+
width = random.randint(20, 200)
|
|
297
|
+
height = random.randint(20, 200)
|
|
298
|
+
x1 = random.randint(0, 640 - width)
|
|
299
|
+
y1 = random.randint(0, 480 - height)
|
|
300
|
+
|
|
301
|
+
detections.append({
|
|
302
|
+
"bbox": [x1, y1, x1 + width, y1 + height],
|
|
303
|
+
"confidence": random.uniform(0.3, 0.95),
|
|
304
|
+
"category": random.choice(categories),
|
|
305
|
+
"detection_id": i
|
|
306
|
+
})
|
|
307
|
+
|
|
308
|
+
return detections
|
|
309
|
+
|
|
310
|
+
@staticmethod
|
|
311
|
+
def create_tracking_batch(num_tracks: int = 20, frames: int = 10) -> List[Dict[str, Any]]:
|
|
312
|
+
"""Create batch of tracking results."""
|
|
313
|
+
import random
|
|
314
|
+
|
|
315
|
+
tracks = []
|
|
316
|
+
categories = ["person", "car", "bike"]
|
|
317
|
+
|
|
318
|
+
for track_id in range(1, num_tracks + 1):
|
|
319
|
+
for frame in range(1, frames + 1):
|
|
320
|
+
# Simulate movement
|
|
321
|
+
base_x = 100 + track_id * 20
|
|
322
|
+
base_y = 100 + track_id * 15
|
|
323
|
+
x = base_x + frame * random.randint(-5, 5)
|
|
324
|
+
y = base_y + frame * random.randint(-5, 5)
|
|
325
|
+
|
|
326
|
+
tracks.append({
|
|
327
|
+
"track_id": track_id,
|
|
328
|
+
"bbox": [x, y, x + 50, y + 80],
|
|
329
|
+
"confidence": random.uniform(0.5, 0.9),
|
|
330
|
+
"category": random.choice(categories),
|
|
331
|
+
"frame": frame,
|
|
332
|
+
"timestamp": time.time() + frame * 0.033
|
|
333
|
+
})
|
|
334
|
+
|
|
335
|
+
return tracks
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def assert_processing_result(result: ProcessingResult,
|
|
339
|
+
expected_status: ProcessingStatus = ProcessingStatus.SUCCESS,
|
|
340
|
+
min_insights: int = 0,
|
|
341
|
+
required_data_keys: List[str] = None):
|
|
342
|
+
"""Standalone assertion helper for processing results."""
|
|
343
|
+
assert isinstance(result, ProcessingResult)
|
|
344
|
+
assert result.status == expected_status
|
|
345
|
+
|
|
346
|
+
if expected_status == ProcessingStatus.SUCCESS:
|
|
347
|
+
assert result.data is not None
|
|
348
|
+
assert len(result.insights) >= min_insights
|
|
349
|
+
|
|
350
|
+
if required_data_keys:
|
|
351
|
+
for key in required_data_keys:
|
|
352
|
+
assert key in result.data
|
|
353
|
+
|
|
354
|
+
elif expected_status == ProcessingStatus.ERROR:
|
|
355
|
+
assert result.error_message is not None
|
|
356
|
+
assert result.error_type is not None
|