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,523 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tests for BasicCountingTrackingUseCase.
|
|
3
|
+
|
|
4
|
+
This module tests the basic counting and tracking functionality including
|
|
5
|
+
line crossing detection, zone-based counting, and tracking integration.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import unittest
|
|
9
|
+
import time
|
|
10
|
+
from typing import Dict, List, Any
|
|
11
|
+
import tempfile
|
|
12
|
+
import os
|
|
13
|
+
|
|
14
|
+
# Fix imports for proper module resolution
|
|
15
|
+
import sys
|
|
16
|
+
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../../src'))
|
|
17
|
+
|
|
18
|
+
from src.matrice_analytics.post_processing import (
|
|
19
|
+
PostProcessor, ProcessingResult, ProcessingStatus, ProcessingContext,
|
|
20
|
+
BasicCountingTrackingUseCase, BaseConfig
|
|
21
|
+
)
|
|
22
|
+
from src.matrice_analytics.post_processing.usecases.basic_counting_tracking import BasicCountingTrackingUseCase, BasicCountingTrackingConfig
|
|
23
|
+
from src.matrice_analytics.post_processing.core.config import TrackingConfig, AlertConfig
|
|
24
|
+
|
|
25
|
+
from .test_utilities import BasePostProcessingTest, StressTestMixin, ConcurrencyTestMixin
|
|
26
|
+
from .test_data_generators import (
|
|
27
|
+
create_detection_results, create_tracking_results, create_zone_polygons,
|
|
28
|
+
create_line_crossing_data, create_basic_counting_tracking_scenarios,
|
|
29
|
+
create_edge_case_data, create_performance_test_data
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class TestBasicCountingTrackingUseCase(BasePostProcessingTest, StressTestMixin):
|
|
34
|
+
"""Test BasicCountingTrackingUseCase functionality."""
|
|
35
|
+
|
|
36
|
+
def setUp(self):
|
|
37
|
+
"""Set up test environment."""
|
|
38
|
+
super().setUp()
|
|
39
|
+
self.use_case = BasicCountingTrackingUseCase()
|
|
40
|
+
self.basic_config = BasicCountingTrackingConfig(
|
|
41
|
+
confidence_threshold=0.5,
|
|
42
|
+
enable_tracking=True
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
def test_use_case_initialization(self):
|
|
46
|
+
"""Test use case initialization."""
|
|
47
|
+
self.assertEqual(self.use_case.name, "basic_counting_tracking")
|
|
48
|
+
self.assertEqual(self.use_case.category, "general")
|
|
49
|
+
self.assertIsNotNone(self.use_case.get_config_schema())
|
|
50
|
+
|
|
51
|
+
def test_config_schema_validation(self):
|
|
52
|
+
"""Test configuration schema validation."""
|
|
53
|
+
schema = self.use_case.get_config_schema()
|
|
54
|
+
|
|
55
|
+
# Check required schema properties
|
|
56
|
+
self.assertIn("type", schema)
|
|
57
|
+
self.assertEqual(schema["type"], "object")
|
|
58
|
+
self.assertIn("properties", schema)
|
|
59
|
+
|
|
60
|
+
# Check key configuration properties
|
|
61
|
+
properties = schema["properties"]
|
|
62
|
+
self.assertIn("confidence_threshold", properties)
|
|
63
|
+
self.assertIn("enable_tracking", properties)
|
|
64
|
+
self.assertIn("zones", properties)
|
|
65
|
+
self.assertIn("lines", properties)
|
|
66
|
+
|
|
67
|
+
def test_default_config_creation(self):
|
|
68
|
+
"""Test default configuration creation."""
|
|
69
|
+
config = self.use_case.create_default_config()
|
|
70
|
+
|
|
71
|
+
self.assertIsInstance(config, BasicCountingTrackingConfig)
|
|
72
|
+
self.assertEqual(config.category, "general")
|
|
73
|
+
self.assertEqual(config.usecase, "basic_counting_tracking")
|
|
74
|
+
self.assertIsNotNone(config.confidence_threshold)
|
|
75
|
+
|
|
76
|
+
# Validate config
|
|
77
|
+
errors = config.validate()
|
|
78
|
+
self.assertEqual(len(errors), 0, f"Default config validation failed: {errors}")
|
|
79
|
+
|
|
80
|
+
def test_config_with_overrides(self):
|
|
81
|
+
"""Test configuration creation with overrides."""
|
|
82
|
+
config = self.use_case.create_default_config(
|
|
83
|
+
confidence_threshold=0.7,
|
|
84
|
+
enable_tracking=True,
|
|
85
|
+
zones={"entrance": [[0, 0], [100, 0], [100, 100], [0, 100]]}
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
self.assertEqual(config.confidence_threshold, 0.7)
|
|
89
|
+
self.assertTrue(config.enable_tracking)
|
|
90
|
+
self.assertIn("entrance", config.zones)
|
|
91
|
+
|
|
92
|
+
def test_basic_detection_processing(self):
|
|
93
|
+
"""Test basic detection processing."""
|
|
94
|
+
# Create test data
|
|
95
|
+
detections = create_detection_results(
|
|
96
|
+
num_detections=15,
|
|
97
|
+
categories=["person", "car"],
|
|
98
|
+
confidence_range=(0.6, 0.9)
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
# Process with use case
|
|
102
|
+
result = self.use_case.process(detections, self.basic_config)
|
|
103
|
+
|
|
104
|
+
# Validate result
|
|
105
|
+
self.assert_processing_result(
|
|
106
|
+
result,
|
|
107
|
+
expected_status=ProcessingStatus.SUCCESS,
|
|
108
|
+
min_insights=1,
|
|
109
|
+
required_metrics=["total_count", "category_counts"]
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
# Check specific metrics
|
|
113
|
+
self.assertIn("total_count", result.metrics)
|
|
114
|
+
self.assertIn("category_counts", result.metrics)
|
|
115
|
+
self.assertGreater(result.metrics["total_count"], 0)
|
|
116
|
+
|
|
117
|
+
def test_tracking_processing(self):
|
|
118
|
+
"""Test tracking data processing."""
|
|
119
|
+
# Create tracking data
|
|
120
|
+
tracks = create_tracking_results(
|
|
121
|
+
num_tracks=8,
|
|
122
|
+
frames=15,
|
|
123
|
+
categories=["person", "vehicle"]
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
# Create config with tracking enabled
|
|
127
|
+
config = self.use_case.create_default_config(
|
|
128
|
+
confidence_threshold=0.5,
|
|
129
|
+
enable_tracking=True
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
# Process
|
|
133
|
+
result = self.use_case.process(tracks, config)
|
|
134
|
+
|
|
135
|
+
# Validate result
|
|
136
|
+
self.assert_processing_result(result, min_insights=1)
|
|
137
|
+
|
|
138
|
+
# Check tracking-specific metrics
|
|
139
|
+
self.assertIn("unique_tracks", result.metrics)
|
|
140
|
+
self.assertIn("track_duration_stats", result.metrics)
|
|
141
|
+
self.assertGreater(result.metrics["unique_tracks"], 0)
|
|
142
|
+
|
|
143
|
+
def test_zone_based_counting(self):
|
|
144
|
+
"""Test zone-based counting functionality."""
|
|
145
|
+
# Create zones
|
|
146
|
+
zones = create_zone_polygons(["entrance", "lobby", "exit"])
|
|
147
|
+
|
|
148
|
+
# Create detection data
|
|
149
|
+
detections = create_detection_results(num_detections=20)
|
|
150
|
+
|
|
151
|
+
# Create config with zones
|
|
152
|
+
config = self.use_case.create_default_config(
|
|
153
|
+
confidence_threshold=0.5,
|
|
154
|
+
zones=zones
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
# Process
|
|
158
|
+
result = self.use_case.process(detections, config)
|
|
159
|
+
|
|
160
|
+
# Validate result
|
|
161
|
+
self.assert_processing_result(result, min_insights=1)
|
|
162
|
+
|
|
163
|
+
# Check zone analysis
|
|
164
|
+
self.assertIn("zone_analysis", result.metrics)
|
|
165
|
+
zone_analysis = result.metrics["zone_analysis"]
|
|
166
|
+
|
|
167
|
+
for zone_name in zones.keys():
|
|
168
|
+
self.assertIn(zone_name, zone_analysis)
|
|
169
|
+
|
|
170
|
+
def test_line_crossing_detection(self):
|
|
171
|
+
"""Test line crossing detection."""
|
|
172
|
+
# Define crossing lines
|
|
173
|
+
lines = {
|
|
174
|
+
"entrance_line": [[100, 200], [200, 200]],
|
|
175
|
+
"exit_line": [[400, 200], [500, 200]]
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
# Create crossing data
|
|
179
|
+
crossing_data = create_line_crossing_data(lines, num_tracks=6, frames=25)
|
|
180
|
+
|
|
181
|
+
# Create config with lines
|
|
182
|
+
config = self.use_case.create_default_config(
|
|
183
|
+
confidence_threshold=0.5,
|
|
184
|
+
enable_tracking=True,
|
|
185
|
+
lines=lines
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
# Process
|
|
189
|
+
result = self.use_case.process(crossing_data, config)
|
|
190
|
+
|
|
191
|
+
# Validate result
|
|
192
|
+
self.assert_processing_result(result, min_insights=1)
|
|
193
|
+
|
|
194
|
+
# Check line crossing analysis
|
|
195
|
+
self.assertIn("line_crossings", result.metrics)
|
|
196
|
+
line_crossings = result.metrics["line_crossings"]
|
|
197
|
+
|
|
198
|
+
for line_name in lines.keys():
|
|
199
|
+
self.assertIn(line_name, line_crossings)
|
|
200
|
+
|
|
201
|
+
def test_combined_zones_and_lines(self):
|
|
202
|
+
"""Test processing with both zones and lines."""
|
|
203
|
+
# Create zones and lines
|
|
204
|
+
zones = create_zone_polygons(["zone_a", "zone_b"])
|
|
205
|
+
lines = {"crossing_line": [[200, 100], [200, 300]]}
|
|
206
|
+
|
|
207
|
+
# Create tracking data
|
|
208
|
+
tracks = create_tracking_results(num_tracks=10, frames=20)
|
|
209
|
+
|
|
210
|
+
# Create comprehensive config
|
|
211
|
+
config = self.use_case.create_default_config(
|
|
212
|
+
confidence_threshold=0.5,
|
|
213
|
+
enable_tracking=True,
|
|
214
|
+
zones=zones,
|
|
215
|
+
lines=lines
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
# Process
|
|
219
|
+
result = self.use_case.process(tracks, config)
|
|
220
|
+
|
|
221
|
+
# Validate result
|
|
222
|
+
self.assert_processing_result(result, min_insights=2)
|
|
223
|
+
|
|
224
|
+
# Check both zone and line analysis
|
|
225
|
+
self.assertIn("zone_analysis", result.metrics)
|
|
226
|
+
self.assertIn("line_crossings", result.metrics)
|
|
227
|
+
|
|
228
|
+
def test_confidence_filtering(self):
|
|
229
|
+
"""Test confidence threshold filtering."""
|
|
230
|
+
# Create data with varying confidence levels
|
|
231
|
+
low_conf_detections = create_detection_results(
|
|
232
|
+
num_detections=10,
|
|
233
|
+
confidence_range=(0.2, 0.4)
|
|
234
|
+
)
|
|
235
|
+
high_conf_detections = create_detection_results(
|
|
236
|
+
num_detections=10,
|
|
237
|
+
confidence_range=(0.7, 0.9)
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
all_detections = low_conf_detections + high_conf_detections
|
|
241
|
+
|
|
242
|
+
# Test with high threshold
|
|
243
|
+
high_threshold_config = self.use_case.create_default_config(
|
|
244
|
+
confidence_threshold=0.6
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
result = self.use_case.process(all_detections, high_threshold_config)
|
|
248
|
+
|
|
249
|
+
# Should filter out low confidence detections
|
|
250
|
+
self.assert_processing_result(result)
|
|
251
|
+
filtered_count = result.metrics["total_count"]
|
|
252
|
+
|
|
253
|
+
# Test with low threshold
|
|
254
|
+
low_threshold_config = self.use_case.create_default_config(
|
|
255
|
+
confidence_threshold=0.1
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
result = self.use_case.process(all_detections, low_threshold_config)
|
|
259
|
+
unfiltered_count = result.metrics["total_count"]
|
|
260
|
+
|
|
261
|
+
# High threshold should result in fewer detections
|
|
262
|
+
self.assertLessEqual(filtered_count, unfiltered_count)
|
|
263
|
+
|
|
264
|
+
def test_empty_data_handling(self):
|
|
265
|
+
"""Test handling of empty input data."""
|
|
266
|
+
empty_data = []
|
|
267
|
+
|
|
268
|
+
result = self.use_case.process(empty_data, self.basic_config)
|
|
269
|
+
|
|
270
|
+
self.assert_processing_result(result)
|
|
271
|
+
self.assertEqual(result.metrics["total_count"], 0)
|
|
272
|
+
self.assertIn("No objects detected", result.insights)
|
|
273
|
+
|
|
274
|
+
def test_malformed_data_handling(self):
|
|
275
|
+
"""Test handling of malformed input data."""
|
|
276
|
+
edge_cases = create_edge_case_data()
|
|
277
|
+
malformed_data = edge_cases["malformed_data"]
|
|
278
|
+
|
|
279
|
+
# Should handle gracefully without crashing
|
|
280
|
+
result = self.use_case.process(malformed_data, self.basic_config)
|
|
281
|
+
|
|
282
|
+
# May succeed with warnings or fail gracefully
|
|
283
|
+
self.assertIn(result.status, [ProcessingStatus.SUCCESS, ProcessingStatus.WARNING, ProcessingStatus.ERROR])
|
|
284
|
+
|
|
285
|
+
if result.status == ProcessingStatus.SUCCESS:
|
|
286
|
+
self.assertGreaterEqual(len(result.warnings), 1)
|
|
287
|
+
|
|
288
|
+
def test_invalid_configuration(self):
|
|
289
|
+
"""Test handling of invalid configuration."""
|
|
290
|
+
# Create invalid config
|
|
291
|
+
invalid_config = BasicCountingTrackingConfig(
|
|
292
|
+
confidence_threshold=1.5, # Invalid: > 1.0
|
|
293
|
+
enable_tracking=True
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
# Validation should fail
|
|
297
|
+
errors = invalid_config.validate()
|
|
298
|
+
self.assertGreater(len(errors), 0)
|
|
299
|
+
|
|
300
|
+
# Processing should handle invalid config gracefully
|
|
301
|
+
detections = create_detection_results(5)
|
|
302
|
+
result = self.use_case.process(detections, invalid_config)
|
|
303
|
+
|
|
304
|
+
# Should return error or warning
|
|
305
|
+
self.assertIn(result.status, [ProcessingStatus.ERROR, ProcessingStatus.WARNING])
|
|
306
|
+
|
|
307
|
+
def test_large_dataset_processing(self):
|
|
308
|
+
"""Test processing of large datasets."""
|
|
309
|
+
# Create large dataset
|
|
310
|
+
large_data = create_performance_test_data("large")
|
|
311
|
+
detections = large_data["detection_data"]
|
|
312
|
+
|
|
313
|
+
# Measure performance
|
|
314
|
+
result, metrics = self.measure_performance(
|
|
315
|
+
self.use_case.process,
|
|
316
|
+
detections,
|
|
317
|
+
self.basic_config
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
# Validate result
|
|
321
|
+
self.assert_processing_result(result)
|
|
322
|
+
|
|
323
|
+
# Check performance is acceptable
|
|
324
|
+
self.assert_performance_acceptable(metrics, max_time=10.0, max_memory_mb=1000.0)
|
|
325
|
+
|
|
326
|
+
def test_stress_testing(self):
|
|
327
|
+
"""Test stress testing with multiple iterations."""
|
|
328
|
+
def test_iteration():
|
|
329
|
+
detections = create_detection_results(50)
|
|
330
|
+
result = self.use_case.process(detections, self.basic_config)
|
|
331
|
+
self.assert_processing_result(result)
|
|
332
|
+
return result
|
|
333
|
+
|
|
334
|
+
stress_results = self.run_stress_test(test_iteration, iterations=50, max_failures=5)
|
|
335
|
+
|
|
336
|
+
# Check stress test results
|
|
337
|
+
self.assertGreaterEqual(stress_results["successful"], 45) # 90% success rate
|
|
338
|
+
self.assertLessEqual(stress_results["failed"], 5)
|
|
339
|
+
|
|
340
|
+
if stress_results["execution_times"]:
|
|
341
|
+
self.assertLess(stress_results["avg_execution_time"], 2.0)
|
|
342
|
+
|
|
343
|
+
def test_scenarios_from_data_generator(self):
|
|
344
|
+
"""Test predefined scenarios from data generator."""
|
|
345
|
+
scenarios = create_basic_counting_tracking_scenarios()
|
|
346
|
+
|
|
347
|
+
for scenario in scenarios:
|
|
348
|
+
with self.subTest(scenario=scenario["name"]):
|
|
349
|
+
# Create config from scenario
|
|
350
|
+
config_dict = scenario["config"]
|
|
351
|
+
config = self.use_case.create_default_config(**config_dict)
|
|
352
|
+
|
|
353
|
+
# Process scenario data
|
|
354
|
+
result = self.use_case.process(scenario["data"], config)
|
|
355
|
+
|
|
356
|
+
# Validate result
|
|
357
|
+
self.assert_processing_result(result, min_insights=1)
|
|
358
|
+
|
|
359
|
+
# Check scenario-specific expectations
|
|
360
|
+
if scenario["name"] == "line_crossing":
|
|
361
|
+
self.assertIn("line_crossings", result.metrics)
|
|
362
|
+
elif scenario["name"] == "zone_tracking":
|
|
363
|
+
self.assertIn("zone_analysis", result.metrics)
|
|
364
|
+
|
|
365
|
+
def test_processing_context_usage(self):
|
|
366
|
+
"""Test processing with custom context."""
|
|
367
|
+
detections = create_detection_results(10)
|
|
368
|
+
|
|
369
|
+
# Create custom context
|
|
370
|
+
context = ProcessingContext(
|
|
371
|
+
confidence_threshold=0.6,
|
|
372
|
+
enable_tracking=True,
|
|
373
|
+
enable_counting=True,
|
|
374
|
+
metadata={"test_id": "context_test"}
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
result = self.use_case.process(detections, self.basic_config, context)
|
|
378
|
+
|
|
379
|
+
self.assert_processing_result(result)
|
|
380
|
+
self.assertIsNotNone(result.context)
|
|
381
|
+
self.assertEqual(result.context.metadata["test_id"], "context_test")
|
|
382
|
+
|
|
383
|
+
def test_metrics_completeness(self):
|
|
384
|
+
"""Test that all expected metrics are present."""
|
|
385
|
+
# Create comprehensive test data
|
|
386
|
+
tracks = create_tracking_results(num_tracks=5, frames=10)
|
|
387
|
+
zones = create_zone_polygons(["test_zone"])
|
|
388
|
+
|
|
389
|
+
config = self.use_case.create_default_config(
|
|
390
|
+
confidence_threshold=0.5,
|
|
391
|
+
enable_tracking=True,
|
|
392
|
+
zones=zones
|
|
393
|
+
)
|
|
394
|
+
|
|
395
|
+
result = self.use_case.process(tracks, config)
|
|
396
|
+
|
|
397
|
+
# Check expected metrics
|
|
398
|
+
expected_metrics = [
|
|
399
|
+
"total_count",
|
|
400
|
+
"category_counts",
|
|
401
|
+
"unique_tracks",
|
|
402
|
+
"zone_analysis",
|
|
403
|
+
"processing_info"
|
|
404
|
+
]
|
|
405
|
+
|
|
406
|
+
for metric in expected_metrics:
|
|
407
|
+
self.assertIn(metric, result.metrics, f"Missing expected metric: {metric}")
|
|
408
|
+
|
|
409
|
+
def test_insights_generation(self):
|
|
410
|
+
"""Test insight generation."""
|
|
411
|
+
detections = create_detection_results(
|
|
412
|
+
num_detections=25,
|
|
413
|
+
categories=["person", "vehicle"]
|
|
414
|
+
)
|
|
415
|
+
|
|
416
|
+
result = self.use_case.process(detections, self.basic_config)
|
|
417
|
+
|
|
418
|
+
self.assert_processing_result(result, min_insights=1)
|
|
419
|
+
|
|
420
|
+
# Check that insights are meaningful
|
|
421
|
+
insights_text = " ".join(result.insights).lower()
|
|
422
|
+
self.assertTrue(
|
|
423
|
+
any(keyword in insights_text for keyword in ["detected", "counted", "objects", "categories"]),
|
|
424
|
+
"Insights should contain meaningful information"
|
|
425
|
+
)
|
|
426
|
+
|
|
427
|
+
def test_summary_generation(self):
|
|
428
|
+
"""Test summary generation."""
|
|
429
|
+
detections = create_detection_results(15)
|
|
430
|
+
result = self.use_case.process(detections, self.basic_config)
|
|
431
|
+
|
|
432
|
+
self.assert_processing_result(result)
|
|
433
|
+
self.assertIsNotNone(result.summary)
|
|
434
|
+
self.assertGreater(len(result.summary), 0)
|
|
435
|
+
|
|
436
|
+
# Summary should contain key information
|
|
437
|
+
summary_lower = result.summary.lower()
|
|
438
|
+
self.assertTrue(
|
|
439
|
+
any(keyword in summary_lower for keyword in ["detected", "counted", "objects"]),
|
|
440
|
+
f"Summary should contain meaningful information: {result.summary}"
|
|
441
|
+
)
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
class TestBasicCountingTrackingIntegration(BasePostProcessingTest, ConcurrencyTestMixin):
|
|
445
|
+
"""Integration tests for BasicCountingTrackingUseCase."""
|
|
446
|
+
|
|
447
|
+
def setUp(self):
|
|
448
|
+
"""Set up test environment."""
|
|
449
|
+
super().setUp()
|
|
450
|
+
self.use_case = BasicCountingTrackingUseCase()
|
|
451
|
+
|
|
452
|
+
def test_processor_integration(self):
|
|
453
|
+
"""Test integration with PostProcessor."""
|
|
454
|
+
detections = create_detection_results(10)
|
|
455
|
+
|
|
456
|
+
# Test through PostProcessor
|
|
457
|
+
result = self.processor.process_simple(
|
|
458
|
+
detections,
|
|
459
|
+
"basic_counting_tracking",
|
|
460
|
+
confidence_threshold=0.5,
|
|
461
|
+
enable_tracking=True
|
|
462
|
+
)
|
|
463
|
+
|
|
464
|
+
self.assert_processing_result(result)
|
|
465
|
+
|
|
466
|
+
def test_concurrent_processing(self):
|
|
467
|
+
"""Test concurrent processing."""
|
|
468
|
+
def process_data():
|
|
469
|
+
detections = create_detection_results(20)
|
|
470
|
+
config = self.use_case.create_default_config(confidence_threshold=0.5)
|
|
471
|
+
return self.use_case.process(detections, config)
|
|
472
|
+
|
|
473
|
+
concurrent_results = self.run_concurrent_test(
|
|
474
|
+
process_data,
|
|
475
|
+
num_threads=3,
|
|
476
|
+
iterations_per_thread=5
|
|
477
|
+
)
|
|
478
|
+
|
|
479
|
+
# Check concurrent processing results
|
|
480
|
+
self.assertGreaterEqual(concurrent_results["success_rate"], 0.9)
|
|
481
|
+
self.assertEqual(concurrent_results["failed"], 0)
|
|
482
|
+
|
|
483
|
+
def test_configuration_file_integration(self):
|
|
484
|
+
"""Test integration with configuration files."""
|
|
485
|
+
# Create config dict
|
|
486
|
+
config_dict = {
|
|
487
|
+
"category": "general",
|
|
488
|
+
"usecase": "basic_counting_tracking",
|
|
489
|
+
"confidence_threshold": 0.6,
|
|
490
|
+
"enable_tracking": True,
|
|
491
|
+
"zones": create_zone_polygons(["test_zone"])
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
# Create temporary config file
|
|
495
|
+
config_file = self.create_temp_config_file(config_dict, "json")
|
|
496
|
+
|
|
497
|
+
# Test processing from file
|
|
498
|
+
detections = create_detection_results(15)
|
|
499
|
+
result = self.processor.process_from_file(detections, config_file)
|
|
500
|
+
|
|
501
|
+
self.assert_processing_result(result)
|
|
502
|
+
|
|
503
|
+
def test_memory_stability(self):
|
|
504
|
+
"""Test memory stability over multiple processing cycles."""
|
|
505
|
+
initial_memory = self.process.memory_info().rss / 1024 / 1024
|
|
506
|
+
|
|
507
|
+
# Process multiple batches
|
|
508
|
+
for i in range(10):
|
|
509
|
+
detections = create_detection_results(100)
|
|
510
|
+
config = self.use_case.create_default_config(confidence_threshold=0.5)
|
|
511
|
+
result = self.use_case.process(detections, config)
|
|
512
|
+
self.assert_processing_result(result)
|
|
513
|
+
|
|
514
|
+
final_memory = self.process.memory_info().rss / 1024 / 1024
|
|
515
|
+
memory_increase = final_memory - initial_memory
|
|
516
|
+
|
|
517
|
+
# Memory increase should be reasonable (< 100MB)
|
|
518
|
+
self.assertLess(memory_increase, 100.0,
|
|
519
|
+
f"Memory increased by {memory_increase:.2f}MB, possible memory leak")
|
|
520
|
+
|
|
521
|
+
|
|
522
|
+
if __name__ == "__main__":
|
|
523
|
+
unittest.main()
|