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