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,524 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Comprehensive tests for the PostProcessor class.
|
|
3
|
+
|
|
4
|
+
This module tests the main PostProcessor functionality including:
|
|
5
|
+
- Simple processing interface
|
|
6
|
+
- Configuration-based processing
|
|
7
|
+
- File-based configuration
|
|
8
|
+
- Error handling
|
|
9
|
+
- Performance characteristics
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import unittest
|
|
13
|
+
import json
|
|
14
|
+
import os
|
|
15
|
+
import time
|
|
16
|
+
from unittest.mock import patch, MagicMock
|
|
17
|
+
|
|
18
|
+
from .test_utilities import BasePostProcessingTest
|
|
19
|
+
from .test_data_generators import (
|
|
20
|
+
create_detection_results, create_tracking_results,
|
|
21
|
+
create_people_counting_config, create_customer_service_config,
|
|
22
|
+
create_edge_case_data, create_performance_test_data
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
from src.matrice_analytics.post_processing import (
|
|
26
|
+
PostProcessor, ProcessingStatus, ProcessingContext,
|
|
27
|
+
PeopleCountingConfig, CustomerServiceConfig, ConfigValidationError
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class TestPostProcessor(BasePostProcessingTest):
|
|
32
|
+
"""Test cases for PostProcessor class."""
|
|
33
|
+
|
|
34
|
+
def test_processor_initialization(self):
|
|
35
|
+
"""Test that PostProcessor initializes correctly."""
|
|
36
|
+
processor = PostProcessor()
|
|
37
|
+
|
|
38
|
+
# Check that statistics are initialized
|
|
39
|
+
stats = processor.get_statistics()
|
|
40
|
+
self.assertIsInstance(stats, dict)
|
|
41
|
+
self.assertEqual(stats["total_processed"], 0)
|
|
42
|
+
self.assertEqual(stats["successful"], 0)
|
|
43
|
+
self.assertEqual(stats["failed"], 0)
|
|
44
|
+
|
|
45
|
+
# Check that use cases are available
|
|
46
|
+
use_cases = processor.list_available_usecases()
|
|
47
|
+
self.assertIsInstance(use_cases, dict)
|
|
48
|
+
self.assertIn("general", use_cases)
|
|
49
|
+
self.assertIn("sales", use_cases)
|
|
50
|
+
|
|
51
|
+
def test_simple_people_counting_processing(self):
|
|
52
|
+
"""Test simple people counting processing."""
|
|
53
|
+
# Create test data
|
|
54
|
+
test_data = create_detection_results(
|
|
55
|
+
num_detections=10,
|
|
56
|
+
categories=["person", "car", "bicycle"]
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
# Process with simple interface
|
|
60
|
+
result = self.processor.process_simple(
|
|
61
|
+
test_data,
|
|
62
|
+
usecase="people_counting",
|
|
63
|
+
confidence_threshold=0.5,
|
|
64
|
+
enable_tracking=True
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
# Validate result
|
|
68
|
+
self.assert_processing_result_valid(
|
|
69
|
+
result,
|
|
70
|
+
expected_usecase="people_counting",
|
|
71
|
+
expected_category="general"
|
|
72
|
+
)
|
|
73
|
+
self.assertEqual(result.status, ProcessingStatus.SUCCESS)
|
|
74
|
+
self.assert_insights_generated(result, min_insights=1)
|
|
75
|
+
|
|
76
|
+
# Check for expected metrics (using actual names from implementation)
|
|
77
|
+
expected_metrics = ["total_people", "processing_time", "input_format"]
|
|
78
|
+
self.assert_metrics_present(result, expected_metrics)
|
|
79
|
+
|
|
80
|
+
def test_simple_customer_service_processing(self):
|
|
81
|
+
"""Test simple customer service processing."""
|
|
82
|
+
# Create test data with relevant categories
|
|
83
|
+
test_data = create_detection_results(
|
|
84
|
+
num_detections=15,
|
|
85
|
+
categories=["person", "staff", "customer"]
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
# Process with simple interface
|
|
89
|
+
result = self.processor.process_simple(
|
|
90
|
+
test_data,
|
|
91
|
+
usecase="customer_service",
|
|
92
|
+
confidence_threshold=0.6,
|
|
93
|
+
service_proximity_threshold=100.0
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
# Validate result (may return WARNING status for some scenarios)
|
|
97
|
+
self.assert_processing_result_valid(
|
|
98
|
+
result,
|
|
99
|
+
expected_usecase="customer_service",
|
|
100
|
+
expected_category="sales"
|
|
101
|
+
)
|
|
102
|
+
self.assertIn(result.status, [ProcessingStatus.SUCCESS, ProcessingStatus.WARNING])
|
|
103
|
+
self.assert_insights_generated(result, min_insights=1)
|
|
104
|
+
|
|
105
|
+
def test_configuration_based_processing(self):
|
|
106
|
+
"""Test processing with configuration objects."""
|
|
107
|
+
# Create test data
|
|
108
|
+
test_data = create_detection_results(num_detections=8)
|
|
109
|
+
|
|
110
|
+
# Create configuration
|
|
111
|
+
config = create_people_counting_config(
|
|
112
|
+
confidence_threshold=0.7,
|
|
113
|
+
enable_tracking=True,
|
|
114
|
+
include_zones=True,
|
|
115
|
+
include_alerts=True
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
# Process with configuration
|
|
119
|
+
result = self.processor.process(test_data, config)
|
|
120
|
+
|
|
121
|
+
# Validate result
|
|
122
|
+
self.assert_processing_result_valid(
|
|
123
|
+
result,
|
|
124
|
+
expected_usecase="people_counting",
|
|
125
|
+
expected_category="general"
|
|
126
|
+
)
|
|
127
|
+
self.assertEqual(result.status, ProcessingStatus.SUCCESS)
|
|
128
|
+
|
|
129
|
+
# Check that zone analysis was performed (using actual metric names)
|
|
130
|
+
if result.metrics.get("zones_analyzed", 0) > 0:
|
|
131
|
+
self.assertIn("zone_metrics", result.metrics)
|
|
132
|
+
|
|
133
|
+
def test_file_based_configuration(self):
|
|
134
|
+
"""Test processing with configuration files."""
|
|
135
|
+
# Create test data
|
|
136
|
+
test_data = create_detection_results(num_detections=5)
|
|
137
|
+
|
|
138
|
+
# Create configuration
|
|
139
|
+
config = create_people_counting_config(confidence_threshold=0.6)
|
|
140
|
+
|
|
141
|
+
# Save configuration to file
|
|
142
|
+
config_file = self.create_temp_config_file(config, "people_counting_test.json")
|
|
143
|
+
|
|
144
|
+
# Process from file
|
|
145
|
+
result = self.processor.process_from_file(test_data, config_file)
|
|
146
|
+
|
|
147
|
+
# Validate result
|
|
148
|
+
self.assert_processing_result_valid(
|
|
149
|
+
result,
|
|
150
|
+
expected_usecase="people_counting",
|
|
151
|
+
expected_category="general"
|
|
152
|
+
)
|
|
153
|
+
self.assertEqual(result.status, ProcessingStatus.SUCCESS)
|
|
154
|
+
|
|
155
|
+
def test_invalid_configuration_handling(self):
|
|
156
|
+
"""Test handling of invalid configurations."""
|
|
157
|
+
test_data = create_detection_results(num_detections=3)
|
|
158
|
+
|
|
159
|
+
# Test with invalid confidence threshold
|
|
160
|
+
config = PeopleCountingConfig(
|
|
161
|
+
category="general",
|
|
162
|
+
usecase="people_counting",
|
|
163
|
+
confidence_threshold=1.5 # Invalid: >1.0
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
result = self.processor.process(test_data, config)
|
|
167
|
+
|
|
168
|
+
# Should handle gracefully
|
|
169
|
+
self.assertIsInstance(result.status, ProcessingStatus)
|
|
170
|
+
if result.status == ProcessingStatus.ERROR:
|
|
171
|
+
self.assertIsNotNone(result.error_message)
|
|
172
|
+
|
|
173
|
+
def test_unknown_usecase_handling(self):
|
|
174
|
+
"""Test handling of unknown use cases."""
|
|
175
|
+
test_data = create_detection_results(num_detections=3)
|
|
176
|
+
|
|
177
|
+
# Try to process with unknown use case
|
|
178
|
+
result = self.processor.process_simple(
|
|
179
|
+
test_data,
|
|
180
|
+
usecase="unknown_usecase",
|
|
181
|
+
confidence_threshold=0.5
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
# Should return error result
|
|
185
|
+
self.assertEqual(result.status, ProcessingStatus.ERROR)
|
|
186
|
+
self.assertIsNotNone(result.error_message)
|
|
187
|
+
# Check for appropriate error message
|
|
188
|
+
self.assertTrue(
|
|
189
|
+
"unknown" in result.error_message.lower() or
|
|
190
|
+
"not found" in result.error_message.lower()
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
def test_empty_data_handling(self):
|
|
194
|
+
"""Test handling of empty input data."""
|
|
195
|
+
# Test with empty list
|
|
196
|
+
result = self.processor.process_simple(
|
|
197
|
+
[],
|
|
198
|
+
usecase="people_counting",
|
|
199
|
+
confidence_threshold=0.5
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
# Should handle gracefully
|
|
203
|
+
self.assertIsInstance(result.status, ProcessingStatus)
|
|
204
|
+
if result.status == ProcessingStatus.SUCCESS:
|
|
205
|
+
# Check for appropriate message indicating no data
|
|
206
|
+
self.assertTrue(
|
|
207
|
+
"no people" in result.summary.lower() or
|
|
208
|
+
"no detections" in result.summary.lower() or
|
|
209
|
+
"empty" in result.summary.lower()
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
def test_malformed_data_handling(self):
|
|
213
|
+
"""Test handling of malformed input data."""
|
|
214
|
+
# Test with malformed detections
|
|
215
|
+
malformed_data = create_edge_case_data()
|
|
216
|
+
|
|
217
|
+
result = self.processor.process_simple(
|
|
218
|
+
malformed_data,
|
|
219
|
+
usecase="people_counting",
|
|
220
|
+
confidence_threshold=0.5
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
# Should handle gracefully - either success with warnings or error
|
|
224
|
+
self.assertIsInstance(result.status, ProcessingStatus)
|
|
225
|
+
if result.status == ProcessingStatus.WARNING:
|
|
226
|
+
self.assertTrue(len(result.warnings) > 0)
|
|
227
|
+
|
|
228
|
+
def test_context_propagation(self):
|
|
229
|
+
"""Test that processing context is properly propagated."""
|
|
230
|
+
test_data = create_detection_results(num_detections=3)
|
|
231
|
+
|
|
232
|
+
# Create context with specific parameters
|
|
233
|
+
context = ProcessingContext(
|
|
234
|
+
confidence_threshold=0.8,
|
|
235
|
+
enable_tracking=True,
|
|
236
|
+
enable_analytics=True,
|
|
237
|
+
metadata={"source": "test_camera", "location": "test_location"}
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
# Process with context
|
|
241
|
+
result = self.processor.process_simple(
|
|
242
|
+
test_data,
|
|
243
|
+
usecase="people_counting",
|
|
244
|
+
confidence_threshold=0.8,
|
|
245
|
+
context=context
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
# Check context propagation
|
|
249
|
+
self.assertIsNotNone(result.context)
|
|
250
|
+
self.assertEqual(result.context.confidence_threshold, 0.8)
|
|
251
|
+
self.assertTrue(result.context.enable_tracking)
|
|
252
|
+
self.assertTrue(result.context.enable_analytics)
|
|
253
|
+
self.assertEqual(result.context.metadata.get("source"), "test_camera")
|
|
254
|
+
self.assertEqual(result.context.metadata.get("location"), "test_location")
|
|
255
|
+
|
|
256
|
+
def test_statistics_tracking(self):
|
|
257
|
+
"""Test that processing statistics are tracked correctly."""
|
|
258
|
+
# Get initial statistics
|
|
259
|
+
initial_stats = self.processor.get_statistics()
|
|
260
|
+
|
|
261
|
+
# Process some data
|
|
262
|
+
test_data = create_detection_results(num_detections=3)
|
|
263
|
+
|
|
264
|
+
# Successful processing
|
|
265
|
+
result1 = self.processor.process_simple(
|
|
266
|
+
test_data,
|
|
267
|
+
usecase="people_counting",
|
|
268
|
+
confidence_threshold=0.5
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
# Check intermediate statistics
|
|
272
|
+
intermediate_stats = self.processor.get_statistics()
|
|
273
|
+
self.assertEqual(intermediate_stats["total_processed"], initial_stats["total_processed"] + 1)
|
|
274
|
+
self.assertEqual(intermediate_stats["successful"], initial_stats["successful"] + 1)
|
|
275
|
+
|
|
276
|
+
# Failed processing (unknown use case) - this should not increment counters
|
|
277
|
+
try:
|
|
278
|
+
result2 = self.processor.process_simple(
|
|
279
|
+
test_data,
|
|
280
|
+
usecase="unknown_usecase"
|
|
281
|
+
)
|
|
282
|
+
except Exception:
|
|
283
|
+
pass # Expected to fail
|
|
284
|
+
|
|
285
|
+
# Check final statistics - failed request shouldn't be counted
|
|
286
|
+
final_stats = self.processor.get_statistics()
|
|
287
|
+
self.assertEqual(final_stats["total_processed"], initial_stats["total_processed"] + 1)
|
|
288
|
+
self.assertEqual(final_stats["successful"], initial_stats["successful"] + 1)
|
|
289
|
+
|
|
290
|
+
def test_config_template_generation(self):
|
|
291
|
+
"""Test configuration template generation."""
|
|
292
|
+
# Test people counting template
|
|
293
|
+
template = self.processor.get_config_template("people_counting")
|
|
294
|
+
|
|
295
|
+
self.assertIsInstance(template, dict)
|
|
296
|
+
self.assertIn("confidence_threshold", template)
|
|
297
|
+
self.assertIn("enable_tracking", template)
|
|
298
|
+
self.assertIn("person_categories", template)
|
|
299
|
+
|
|
300
|
+
# Test customer service template
|
|
301
|
+
template = self.processor.get_config_template("customer_service")
|
|
302
|
+
|
|
303
|
+
self.assertIsInstance(template, dict)
|
|
304
|
+
self.assertIn("service_proximity_threshold", template)
|
|
305
|
+
self.assertIn("staff_categories", template)
|
|
306
|
+
self.assertIn("customer_categories", template)
|
|
307
|
+
|
|
308
|
+
def test_config_validation(self):
|
|
309
|
+
"""Test configuration validation."""
|
|
310
|
+
# Valid configuration
|
|
311
|
+
valid_config = create_people_counting_config(confidence_threshold=0.5)
|
|
312
|
+
errors = self.processor.validate_config(valid_config)
|
|
313
|
+
self.assertEqual(len(errors), 0)
|
|
314
|
+
|
|
315
|
+
# Invalid configuration
|
|
316
|
+
invalid_config = PeopleCountingConfig(
|
|
317
|
+
category="general",
|
|
318
|
+
usecase="people_counting",
|
|
319
|
+
confidence_threshold=2.0, # Invalid
|
|
320
|
+
time_window_minutes=-5 # Invalid
|
|
321
|
+
)
|
|
322
|
+
errors = self.processor.validate_config(invalid_config)
|
|
323
|
+
self.assertGreater(len(errors), 0)
|
|
324
|
+
|
|
325
|
+
def test_available_usecases_listing(self):
|
|
326
|
+
"""Test listing of available use cases."""
|
|
327
|
+
use_cases = self.processor.list_available_usecases()
|
|
328
|
+
|
|
329
|
+
self.assertIsInstance(use_cases, dict)
|
|
330
|
+
|
|
331
|
+
# Check expected categories and use cases
|
|
332
|
+
self.assertIn("general", use_cases)
|
|
333
|
+
self.assertIn("people_counting", use_cases["general"])
|
|
334
|
+
|
|
335
|
+
self.assertIn("sales", use_cases)
|
|
336
|
+
self.assertIn("customer_service", use_cases["sales"])
|
|
337
|
+
|
|
338
|
+
def test_supported_usecases_listing(self):
|
|
339
|
+
"""Test getting list of supported use case names."""
|
|
340
|
+
supported = self.processor.get_supported_usecases()
|
|
341
|
+
|
|
342
|
+
self.assertIsInstance(supported, list)
|
|
343
|
+
self.assertIn("people_counting", supported)
|
|
344
|
+
self.assertIn("customer_service", supported)
|
|
345
|
+
|
|
346
|
+
def test_processing_with_tracking_data(self):
|
|
347
|
+
"""Test processing with tracking format data."""
|
|
348
|
+
# Create tracking data
|
|
349
|
+
tracking_data = create_tracking_results(
|
|
350
|
+
num_tracks=5,
|
|
351
|
+
categories=["person", "car"]
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
result = self.processor.process_simple(
|
|
355
|
+
tracking_data,
|
|
356
|
+
usecase="people_counting",
|
|
357
|
+
confidence_threshold=0.5,
|
|
358
|
+
enable_tracking=True
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
# Should handle tracking data format
|
|
362
|
+
self.assert_processing_result_valid(result)
|
|
363
|
+
|
|
364
|
+
# Should have basic metrics
|
|
365
|
+
self.assertIn("total_people", result.metrics)
|
|
366
|
+
self.assertIn("processing_time", result.metrics)
|
|
367
|
+
|
|
368
|
+
def test_config_save_and_load(self):
|
|
369
|
+
"""Test saving and loading configurations."""
|
|
370
|
+
# Create configuration
|
|
371
|
+
original_config = create_customer_service_config(
|
|
372
|
+
confidence_threshold=0.7,
|
|
373
|
+
service_proximity_threshold=150.0
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
# Save configuration
|
|
377
|
+
config_file = os.path.join(self.temp_dir, "customer_service_config.json")
|
|
378
|
+
self.processor.save_config(original_config, config_file)
|
|
379
|
+
|
|
380
|
+
# Load configuration
|
|
381
|
+
loaded_config = self.processor.load_config(config_file)
|
|
382
|
+
|
|
383
|
+
# Compare configurations
|
|
384
|
+
self.assertEqual(original_config.usecase, loaded_config.usecase)
|
|
385
|
+
self.assertEqual(original_config.category, loaded_config.category)
|
|
386
|
+
self.assertEqual(original_config.confidence_threshold, loaded_config.confidence_threshold)
|
|
387
|
+
self.assertEqual(original_config.service_proximity_threshold, loaded_config.service_proximity_threshold)
|
|
388
|
+
|
|
389
|
+
def test_reset_statistics(self):
|
|
390
|
+
"""Test resetting processing statistics."""
|
|
391
|
+
# Process some data to generate statistics
|
|
392
|
+
test_data = create_detection_results(num_detections=3)
|
|
393
|
+
self.processor.process_simple(test_data, usecase="people_counting")
|
|
394
|
+
|
|
395
|
+
# Check that statistics exist
|
|
396
|
+
stats_before = self.processor.get_statistics()
|
|
397
|
+
self.assertGreater(stats_before["total_processed"], 0)
|
|
398
|
+
|
|
399
|
+
# Reset statistics
|
|
400
|
+
self.processor.reset_statistics()
|
|
401
|
+
|
|
402
|
+
# Check that statistics are reset
|
|
403
|
+
stats_after = self.processor.get_statistics()
|
|
404
|
+
self.assertEqual(stats_after["total_processed"], 0)
|
|
405
|
+
self.assertEqual(stats_after["successful"], 0)
|
|
406
|
+
self.assertEqual(stats_after["failed"], 0)
|
|
407
|
+
self.assertEqual(stats_after["total_processing_time"], 0.0)
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
class TestPostProcessorPerformance(BasePostProcessingTest):
|
|
411
|
+
"""Performance tests for PostProcessor."""
|
|
412
|
+
|
|
413
|
+
def test_large_dataset_processing(self):
|
|
414
|
+
"""Test processing performance with large datasets."""
|
|
415
|
+
# Create large dataset
|
|
416
|
+
large_data = create_performance_test_data(size=1000)
|
|
417
|
+
|
|
418
|
+
# Measure processing time
|
|
419
|
+
result, processing_time = self.measure_processing_time(
|
|
420
|
+
self.processor.process_simple,
|
|
421
|
+
large_data,
|
|
422
|
+
usecase="people_counting",
|
|
423
|
+
confidence_threshold=0.5
|
|
424
|
+
)
|
|
425
|
+
|
|
426
|
+
# Check that processing completed successfully
|
|
427
|
+
self.assertEqual(result.status, ProcessingStatus.SUCCESS)
|
|
428
|
+
|
|
429
|
+
# Check performance (should complete within reasonable time)
|
|
430
|
+
self.assert_performance_acceptable(processing_time, max_time=5.0)
|
|
431
|
+
|
|
432
|
+
# Check basic metrics are present
|
|
433
|
+
self.assertIn("total_people", result.metrics)
|
|
434
|
+
self.assertIn("processing_time", result.metrics)
|
|
435
|
+
|
|
436
|
+
def test_batch_processing_performance(self):
|
|
437
|
+
"""Test performance with multiple batch processing calls."""
|
|
438
|
+
# Create multiple small batches
|
|
439
|
+
batches = [create_detection_results(num_detections=50) for _ in range(10)]
|
|
440
|
+
|
|
441
|
+
total_start_time = time.time()
|
|
442
|
+
results = []
|
|
443
|
+
|
|
444
|
+
for batch in batches:
|
|
445
|
+
result = self.processor.process_simple(
|
|
446
|
+
batch,
|
|
447
|
+
usecase="people_counting",
|
|
448
|
+
confidence_threshold=0.5
|
|
449
|
+
)
|
|
450
|
+
results.append(result)
|
|
451
|
+
|
|
452
|
+
total_processing_time = time.time() - total_start_time
|
|
453
|
+
|
|
454
|
+
# Check that all batches processed successfully
|
|
455
|
+
for result in results:
|
|
456
|
+
self.assertEqual(result.status, ProcessingStatus.SUCCESS)
|
|
457
|
+
|
|
458
|
+
# Check overall performance
|
|
459
|
+
self.assert_performance_acceptable(total_processing_time, max_time=10.0)
|
|
460
|
+
|
|
461
|
+
# Check statistics
|
|
462
|
+
stats = self.processor.get_statistics()
|
|
463
|
+
self.assertEqual(stats["total_processed"], len(batches))
|
|
464
|
+
self.assertEqual(stats["successful"], len(batches))
|
|
465
|
+
|
|
466
|
+
def test_memory_usage_stability(self):
|
|
467
|
+
"""Test that memory usage remains stable during processing."""
|
|
468
|
+
import psutil
|
|
469
|
+
import os
|
|
470
|
+
|
|
471
|
+
# Get initial memory usage
|
|
472
|
+
process = psutil.Process(os.getpid())
|
|
473
|
+
initial_memory = process.memory_info().rss
|
|
474
|
+
|
|
475
|
+
# Process multiple datasets
|
|
476
|
+
for i in range(20):
|
|
477
|
+
test_data = create_detection_results(num_detections=100)
|
|
478
|
+
result = self.processor.process_simple(
|
|
479
|
+
test_data,
|
|
480
|
+
usecase="people_counting",
|
|
481
|
+
confidence_threshold=0.5
|
|
482
|
+
)
|
|
483
|
+
self.assertEqual(result.status, ProcessingStatus.SUCCESS)
|
|
484
|
+
|
|
485
|
+
# Check final memory usage
|
|
486
|
+
final_memory = process.memory_info().rss
|
|
487
|
+
memory_increase = final_memory - initial_memory
|
|
488
|
+
|
|
489
|
+
# Memory increase should be reasonable (less than 100MB)
|
|
490
|
+
max_memory_increase = 100 * 1024 * 1024 # 100MB
|
|
491
|
+
self.assertLess(memory_increase, max_memory_increase,
|
|
492
|
+
f"Memory increased by {memory_increase / 1024 / 1024:.2f}MB")
|
|
493
|
+
|
|
494
|
+
|
|
495
|
+
if __name__ == "__main__":
|
|
496
|
+
import time
|
|
497
|
+
|
|
498
|
+
# Create test suite
|
|
499
|
+
suite = unittest.TestSuite()
|
|
500
|
+
|
|
501
|
+
# Add test cases
|
|
502
|
+
suite.addTest(unittest.makeSuite(TestPostProcessor))
|
|
503
|
+
suite.addTest(unittest.makeSuite(TestPostProcessorPerformance))
|
|
504
|
+
|
|
505
|
+
# Run tests
|
|
506
|
+
runner = unittest.TextTestRunner(verbosity=2)
|
|
507
|
+
start_time = time.time()
|
|
508
|
+
result = runner.run(suite)
|
|
509
|
+
end_time = time.time()
|
|
510
|
+
|
|
511
|
+
print(f"\nTest execution completed in {end_time - start_time:.2f} seconds")
|
|
512
|
+
print(f"Tests run: {result.testsRun}")
|
|
513
|
+
print(f"Failures: {len(result.failures)}")
|
|
514
|
+
print(f"Errors: {len(result.errors)}")
|
|
515
|
+
|
|
516
|
+
if result.failures:
|
|
517
|
+
print("\nFailures:")
|
|
518
|
+
for test, traceback in result.failures:
|
|
519
|
+
print(f"- {test}: {traceback}")
|
|
520
|
+
|
|
521
|
+
if result.errors:
|
|
522
|
+
print("\nErrors:")
|
|
523
|
+
for test, traceback in result.errors:
|
|
524
|
+
print(f"- {test}: {traceback}")
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
import cv2
|
|
4
|
+
import json
|
|
5
|
+
import importlib
|
|
6
|
+
import argparse
|
|
7
|
+
from ultralytics import YOLO
|
|
8
|
+
from src.matrice_analytics.post_processing.core.base import ProcessingContext
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class UseCaseTestProcessor:
|
|
12
|
+
"""
|
|
13
|
+
A flexible YOLO-based video processor for testing different post-processing use cases.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def __init__(self, file_name, config_name, usecase_name, model_path, video_path, post_process=None, max_frames=None):
|
|
17
|
+
self.file_name = file_name
|
|
18
|
+
self.config_name = config_name
|
|
19
|
+
self.usecase_name = usecase_name
|
|
20
|
+
self.model_path = model_path
|
|
21
|
+
self.video_path = video_path
|
|
22
|
+
self.post_process = post_process
|
|
23
|
+
self.max_frames = max_frames
|
|
24
|
+
self.json_dir = "jsons"
|
|
25
|
+
|
|
26
|
+
self._setup_environment()
|
|
27
|
+
self.ConfigClass, self.UsecaseClass = self._load_usecase()
|
|
28
|
+
self.config = self._initialize_config()
|
|
29
|
+
self.processor = self.UsecaseClass()
|
|
30
|
+
self.model = YOLO(self.model_path)
|
|
31
|
+
os.makedirs(self.json_dir, exist_ok=True)
|
|
32
|
+
|
|
33
|
+
def _setup_environment(self):
|
|
34
|
+
"""Ensure project root is added to sys.path."""
|
|
35
|
+
project_root = os.path.abspath("/content/py_analytics")
|
|
36
|
+
if project_root not in sys.path:
|
|
37
|
+
sys.path.append(project_root)
|
|
38
|
+
|
|
39
|
+
def _load_usecase(self):
|
|
40
|
+
"""Dynamically import config and usecase classes."""
|
|
41
|
+
module_path = f"src.matrice_analytics.post_processing.usecases.{self.file_name}"
|
|
42
|
+
module = importlib.import_module(module_path)
|
|
43
|
+
return getattr(module, self.config_name), getattr(module, self.usecase_name)
|
|
44
|
+
|
|
45
|
+
def _initialize_config(self):
|
|
46
|
+
"""Initialize config object, applying overrides if provided."""
|
|
47
|
+
if self.post_process:
|
|
48
|
+
return self.ConfigClass(**self.post_process)
|
|
49
|
+
return self.ConfigClass()
|
|
50
|
+
|
|
51
|
+
def _serialize_result(self, result):
|
|
52
|
+
"""Convert result object into JSON-serializable dict."""
|
|
53
|
+
def to_serializable(obj):
|
|
54
|
+
if hasattr(obj, "to_dict"):
|
|
55
|
+
return obj.to_dict()
|
|
56
|
+
if hasattr(obj, "__dict__"):
|
|
57
|
+
return obj.__dict__
|
|
58
|
+
return str(obj)
|
|
59
|
+
return json.loads(json.dumps(result, default=to_serializable))
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def process_video(self):
|
|
63
|
+
"""Run YOLO inference on video and post-process frame by frame."""
|
|
64
|
+
cap = cv2.VideoCapture(self.video_path)
|
|
65
|
+
if not cap.isOpened():
|
|
66
|
+
raise ValueError(f"Failed to open video at {self.video_path}")
|
|
67
|
+
|
|
68
|
+
frame_idx = 0
|
|
69
|
+
stream_info = {
|
|
70
|
+
'input_settings': {
|
|
71
|
+
'start_frame': 0,
|
|
72
|
+
'original_fps': cap.get(cv2.CAP_PROP_FPS),
|
|
73
|
+
'camera_info': {'id': 'cam1', 'name': 'Test Camera'}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
print(f"\nStarting video processing: {self.video_path}")
|
|
78
|
+
print(f"Model: {self.model_path}")
|
|
79
|
+
print(f"Output directory: {self.json_dir}\n")
|
|
80
|
+
|
|
81
|
+
while cap.isOpened():
|
|
82
|
+
ret, frame = cap.read()
|
|
83
|
+
if not ret:
|
|
84
|
+
break
|
|
85
|
+
|
|
86
|
+
frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
|
|
87
|
+
results = self.model(frame_rgb)
|
|
88
|
+
|
|
89
|
+
detections = []
|
|
90
|
+
for xyxy, conf, cls in zip(results[0].boxes.xyxy, results[0].boxes.conf, results[0].boxes.cls):
|
|
91
|
+
x1, y1, x2, y2 = xyxy.tolist()
|
|
92
|
+
detections.append({
|
|
93
|
+
'category_id': int(cls),
|
|
94
|
+
'confidence': conf.item(),
|
|
95
|
+
'bounding_box': {
|
|
96
|
+
'xmin': int(x1),
|
|
97
|
+
'ymin': int(y1),
|
|
98
|
+
'xmax': int(x2),
|
|
99
|
+
'ymax': int(y2)
|
|
100
|
+
}
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
success, encoded_image = cv2.imencode(".jpg", frame)
|
|
104
|
+
input_bytes = encoded_image.tobytes() if success else None
|
|
105
|
+
|
|
106
|
+
try:
|
|
107
|
+
result = self.processor.process(
|
|
108
|
+
detections, self.config, input_bytes, ProcessingContext(), stream_info
|
|
109
|
+
)
|
|
110
|
+
except TypeError:
|
|
111
|
+
result = self.processor.process(
|
|
112
|
+
detections, self.config, ProcessingContext(), stream_info
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
json_path = os.path.join(self.json_dir, f"frame_{frame_idx:04d}.json")
|
|
116
|
+
with open(json_path, "w") as f:
|
|
117
|
+
json.dump(self._serialize_result(result), f, indent=2)
|
|
118
|
+
|
|
119
|
+
print(f"Frame {frame_idx} processed — detections: {len(detections)} — saved: {json_path}")
|
|
120
|
+
|
|
121
|
+
frame_idx += 1
|
|
122
|
+
stream_info['input_settings']['start_frame'] += 1
|
|
123
|
+
|
|
124
|
+
if self.max_frames and frame_idx >= self.max_frames:
|
|
125
|
+
print(f"\nMax frame limit ({self.max_frames}) reached.")
|
|
126
|
+
break
|
|
127
|
+
|
|
128
|
+
cap.release()
|
|
129
|
+
print(f"\nProcessing complete. JSON outputs saved in: {self.json_dir}")
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def main():
|
|
133
|
+
parser = argparse.ArgumentParser(description="YOLO Use Case Test Processor")
|
|
134
|
+
|
|
135
|
+
parser.add_argument("--file_name", type=str, required=True,
|
|
136
|
+
help="Usecase file name under src/matrice_analytics/post_processing/usecases/")
|
|
137
|
+
parser.add_argument("--config_name", type=str, required=True,
|
|
138
|
+
help="Config class name (e.g., PeopleCountingConfig)")
|
|
139
|
+
parser.add_argument("--usecase_name", type=str, required=True,
|
|
140
|
+
help="Use case class name (e.g., PeopleCountingUseCase)")
|
|
141
|
+
parser.add_argument("--model_path", type=str, required=True,
|
|
142
|
+
help="Path to YOLO model file (.pt)")
|
|
143
|
+
parser.add_argument("--video_path", type=str, required=True,
|
|
144
|
+
help="Path to input video")
|
|
145
|
+
parser.add_argument("--post_process", type=json.loads, default=None,
|
|
146
|
+
help="JSON string for config overrides, e.g. '{\"min_confidence\": 0.5}'")
|
|
147
|
+
parser.add_argument("--max_frames", type=int, default=None,
|
|
148
|
+
help="Limit number of frames processed")
|
|
149
|
+
|
|
150
|
+
args = parser.parse_args()
|
|
151
|
+
|
|
152
|
+
processor = UseCaseTestProcessor(
|
|
153
|
+
file_name=args.file_name,
|
|
154
|
+
config_name=args.config_name,
|
|
155
|
+
usecase_name=args.usecase_name,
|
|
156
|
+
model_path=args.model_path,
|
|
157
|
+
video_path=args.video_path,
|
|
158
|
+
post_process=args.post_process,
|
|
159
|
+
max_frames=args.max_frames
|
|
160
|
+
)
|
|
161
|
+
processor.process_video()
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
if __name__ == "__main__":
|
|
165
|
+
main()
|