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.
Files changed (196) hide show
  1. matrice_analytics/__init__.py +28 -0
  2. matrice_analytics/boundary_drawing_internal/README.md +305 -0
  3. matrice_analytics/boundary_drawing_internal/__init__.py +45 -0
  4. matrice_analytics/boundary_drawing_internal/boundary_drawing_internal.py +1207 -0
  5. matrice_analytics/boundary_drawing_internal/boundary_drawing_tool.py +429 -0
  6. matrice_analytics/boundary_drawing_internal/boundary_tool_template.html +1036 -0
  7. matrice_analytics/boundary_drawing_internal/data/.gitignore +12 -0
  8. matrice_analytics/boundary_drawing_internal/example_usage.py +206 -0
  9. matrice_analytics/boundary_drawing_internal/usage/README.md +110 -0
  10. matrice_analytics/boundary_drawing_internal/usage/boundary_drawer_launcher.py +102 -0
  11. matrice_analytics/boundary_drawing_internal/usage/simple_boundary_launcher.py +107 -0
  12. matrice_analytics/post_processing/README.md +455 -0
  13. matrice_analytics/post_processing/__init__.py +732 -0
  14. matrice_analytics/post_processing/advanced_tracker/README.md +650 -0
  15. matrice_analytics/post_processing/advanced_tracker/__init__.py +17 -0
  16. matrice_analytics/post_processing/advanced_tracker/base.py +99 -0
  17. matrice_analytics/post_processing/advanced_tracker/config.py +77 -0
  18. matrice_analytics/post_processing/advanced_tracker/kalman_filter.py +370 -0
  19. matrice_analytics/post_processing/advanced_tracker/matching.py +195 -0
  20. matrice_analytics/post_processing/advanced_tracker/strack.py +230 -0
  21. matrice_analytics/post_processing/advanced_tracker/tracker.py +367 -0
  22. matrice_analytics/post_processing/config.py +146 -0
  23. matrice_analytics/post_processing/core/__init__.py +63 -0
  24. matrice_analytics/post_processing/core/base.py +704 -0
  25. matrice_analytics/post_processing/core/config.py +3291 -0
  26. matrice_analytics/post_processing/core/config_utils.py +925 -0
  27. matrice_analytics/post_processing/face_reg/__init__.py +43 -0
  28. matrice_analytics/post_processing/face_reg/compare_similarity.py +556 -0
  29. matrice_analytics/post_processing/face_reg/embedding_manager.py +950 -0
  30. matrice_analytics/post_processing/face_reg/face_recognition.py +2234 -0
  31. matrice_analytics/post_processing/face_reg/face_recognition_client.py +606 -0
  32. matrice_analytics/post_processing/face_reg/people_activity_logging.py +321 -0
  33. matrice_analytics/post_processing/ocr/__init__.py +0 -0
  34. matrice_analytics/post_processing/ocr/easyocr_extractor.py +250 -0
  35. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/__init__.py +9 -0
  36. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/__init__.py +4 -0
  37. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/cli.py +33 -0
  38. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/dataset_stats.py +139 -0
  39. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/export.py +398 -0
  40. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/train.py +447 -0
  41. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/utils.py +129 -0
  42. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/valid.py +93 -0
  43. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/validate_dataset.py +240 -0
  44. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/visualize_augmentation.py +176 -0
  45. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/visualize_predictions.py +96 -0
  46. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/core/__init__.py +3 -0
  47. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/core/process.py +246 -0
  48. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/core/types.py +60 -0
  49. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/core/utils.py +87 -0
  50. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/inference/__init__.py +3 -0
  51. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/inference/config.py +82 -0
  52. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/inference/hub.py +141 -0
  53. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/inference/plate_recognizer.py +323 -0
  54. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/py.typed +0 -0
  55. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/__init__.py +0 -0
  56. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/data/__init__.py +0 -0
  57. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/data/augmentation.py +101 -0
  58. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/data/dataset.py +97 -0
  59. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/__init__.py +0 -0
  60. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/config.py +114 -0
  61. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/layers.py +553 -0
  62. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/loss.py +55 -0
  63. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/metric.py +86 -0
  64. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/model_builders.py +95 -0
  65. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/model_schema.py +395 -0
  66. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/utilities/__init__.py +0 -0
  67. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/utilities/backend_utils.py +38 -0
  68. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/utilities/utils.py +214 -0
  69. matrice_analytics/post_processing/ocr/postprocessing.py +270 -0
  70. matrice_analytics/post_processing/ocr/preprocessing.py +52 -0
  71. matrice_analytics/post_processing/post_processor.py +1175 -0
  72. matrice_analytics/post_processing/test_cases/__init__.py +1 -0
  73. matrice_analytics/post_processing/test_cases/run_tests.py +143 -0
  74. matrice_analytics/post_processing/test_cases/test_advanced_customer_service.py +841 -0
  75. matrice_analytics/post_processing/test_cases/test_basic_counting_tracking.py +523 -0
  76. matrice_analytics/post_processing/test_cases/test_comprehensive.py +531 -0
  77. matrice_analytics/post_processing/test_cases/test_config.py +852 -0
  78. matrice_analytics/post_processing/test_cases/test_customer_service.py +585 -0
  79. matrice_analytics/post_processing/test_cases/test_data_generators.py +583 -0
  80. matrice_analytics/post_processing/test_cases/test_people_counting.py +510 -0
  81. matrice_analytics/post_processing/test_cases/test_processor.py +524 -0
  82. matrice_analytics/post_processing/test_cases/test_usecases.py +165 -0
  83. matrice_analytics/post_processing/test_cases/test_utilities.py +356 -0
  84. matrice_analytics/post_processing/test_cases/test_utils.py +743 -0
  85. matrice_analytics/post_processing/usecases/Histopathological_Cancer_Detection_img.py +604 -0
  86. matrice_analytics/post_processing/usecases/__init__.py +267 -0
  87. matrice_analytics/post_processing/usecases/abandoned_object_detection.py +797 -0
  88. matrice_analytics/post_processing/usecases/advanced_customer_service.py +1601 -0
  89. matrice_analytics/post_processing/usecases/age_detection.py +842 -0
  90. matrice_analytics/post_processing/usecases/age_gender_detection.py +1085 -0
  91. matrice_analytics/post_processing/usecases/anti_spoofing_detection.py +656 -0
  92. matrice_analytics/post_processing/usecases/assembly_line_detection.py +841 -0
  93. matrice_analytics/post_processing/usecases/banana_defect_detection.py +624 -0
  94. matrice_analytics/post_processing/usecases/basic_counting_tracking.py +667 -0
  95. matrice_analytics/post_processing/usecases/blood_cancer_detection_img.py +881 -0
  96. matrice_analytics/post_processing/usecases/car_damage_detection.py +834 -0
  97. matrice_analytics/post_processing/usecases/car_part_segmentation.py +946 -0
  98. matrice_analytics/post_processing/usecases/car_service.py +1601 -0
  99. matrice_analytics/post_processing/usecases/cardiomegaly_classification.py +864 -0
  100. matrice_analytics/post_processing/usecases/cell_microscopy_segmentation.py +897 -0
  101. matrice_analytics/post_processing/usecases/chicken_pose_detection.py +648 -0
  102. matrice_analytics/post_processing/usecases/child_monitoring.py +814 -0
  103. matrice_analytics/post_processing/usecases/color/clip.py +660 -0
  104. matrice_analytics/post_processing/usecases/color/clip_processor/merges.txt +48895 -0
  105. matrice_analytics/post_processing/usecases/color/clip_processor/preprocessor_config.json +28 -0
  106. matrice_analytics/post_processing/usecases/color/clip_processor/special_tokens_map.json +30 -0
  107. matrice_analytics/post_processing/usecases/color/clip_processor/tokenizer.json +245079 -0
  108. matrice_analytics/post_processing/usecases/color/clip_processor/tokenizer_config.json +32 -0
  109. matrice_analytics/post_processing/usecases/color/clip_processor/vocab.json +1 -0
  110. matrice_analytics/post_processing/usecases/color/color_map_utils.py +70 -0
  111. matrice_analytics/post_processing/usecases/color/color_mapper.py +468 -0
  112. matrice_analytics/post_processing/usecases/color_detection.py +1936 -0
  113. matrice_analytics/post_processing/usecases/color_map_utils.py +70 -0
  114. matrice_analytics/post_processing/usecases/concrete_crack_detection.py +827 -0
  115. matrice_analytics/post_processing/usecases/crop_weed_detection.py +781 -0
  116. matrice_analytics/post_processing/usecases/customer_service.py +1008 -0
  117. matrice_analytics/post_processing/usecases/defect_detection_products.py +936 -0
  118. matrice_analytics/post_processing/usecases/distracted_driver_detection.py +822 -0
  119. matrice_analytics/post_processing/usecases/drone_traffic_monitoring.py +585 -0
  120. matrice_analytics/post_processing/usecases/drowsy_driver_detection.py +829 -0
  121. matrice_analytics/post_processing/usecases/dwell_detection.py +829 -0
  122. matrice_analytics/post_processing/usecases/emergency_vehicle_detection.py +827 -0
  123. matrice_analytics/post_processing/usecases/face_emotion.py +813 -0
  124. matrice_analytics/post_processing/usecases/face_recognition.py +827 -0
  125. matrice_analytics/post_processing/usecases/fashion_detection.py +835 -0
  126. matrice_analytics/post_processing/usecases/field_mapping.py +902 -0
  127. matrice_analytics/post_processing/usecases/fire_detection.py +1146 -0
  128. matrice_analytics/post_processing/usecases/flare_analysis.py +836 -0
  129. matrice_analytics/post_processing/usecases/flower_segmentation.py +1006 -0
  130. matrice_analytics/post_processing/usecases/gas_leak_detection.py +837 -0
  131. matrice_analytics/post_processing/usecases/gender_detection.py +832 -0
  132. matrice_analytics/post_processing/usecases/human_activity_recognition.py +871 -0
  133. matrice_analytics/post_processing/usecases/intrusion_detection.py +1672 -0
  134. matrice_analytics/post_processing/usecases/leaf.py +821 -0
  135. matrice_analytics/post_processing/usecases/leaf_disease.py +840 -0
  136. matrice_analytics/post_processing/usecases/leak_detection.py +837 -0
  137. matrice_analytics/post_processing/usecases/license_plate_detection.py +1188 -0
  138. matrice_analytics/post_processing/usecases/license_plate_monitoring.py +1781 -0
  139. matrice_analytics/post_processing/usecases/litter_monitoring.py +717 -0
  140. matrice_analytics/post_processing/usecases/mask_detection.py +869 -0
  141. matrice_analytics/post_processing/usecases/natural_disaster.py +907 -0
  142. matrice_analytics/post_processing/usecases/parking.py +787 -0
  143. matrice_analytics/post_processing/usecases/parking_space_detection.py +822 -0
  144. matrice_analytics/post_processing/usecases/pcb_defect_detection.py +888 -0
  145. matrice_analytics/post_processing/usecases/pedestrian_detection.py +808 -0
  146. matrice_analytics/post_processing/usecases/people_counting.py +706 -0
  147. matrice_analytics/post_processing/usecases/people_counting_bckp.py +1683 -0
  148. matrice_analytics/post_processing/usecases/people_tracking.py +1842 -0
  149. matrice_analytics/post_processing/usecases/pipeline_detection.py +605 -0
  150. matrice_analytics/post_processing/usecases/plaque_segmentation_img.py +874 -0
  151. matrice_analytics/post_processing/usecases/pothole_segmentation.py +915 -0
  152. matrice_analytics/post_processing/usecases/ppe_compliance.py +645 -0
  153. matrice_analytics/post_processing/usecases/price_tag_detection.py +822 -0
  154. matrice_analytics/post_processing/usecases/proximity_detection.py +1901 -0
  155. matrice_analytics/post_processing/usecases/road_lane_detection.py +623 -0
  156. matrice_analytics/post_processing/usecases/road_traffic_density.py +832 -0
  157. matrice_analytics/post_processing/usecases/road_view_segmentation.py +915 -0
  158. matrice_analytics/post_processing/usecases/shelf_inventory_detection.py +583 -0
  159. matrice_analytics/post_processing/usecases/shoplifting_detection.py +822 -0
  160. matrice_analytics/post_processing/usecases/shopping_cart_analysis.py +899 -0
  161. matrice_analytics/post_processing/usecases/skin_cancer_classification_img.py +864 -0
  162. matrice_analytics/post_processing/usecases/smoker_detection.py +833 -0
  163. matrice_analytics/post_processing/usecases/solar_panel.py +810 -0
  164. matrice_analytics/post_processing/usecases/suspicious_activity_detection.py +1030 -0
  165. matrice_analytics/post_processing/usecases/template_usecase.py +380 -0
  166. matrice_analytics/post_processing/usecases/theft_detection.py +648 -0
  167. matrice_analytics/post_processing/usecases/traffic_sign_monitoring.py +724 -0
  168. matrice_analytics/post_processing/usecases/underground_pipeline_defect_detection.py +775 -0
  169. matrice_analytics/post_processing/usecases/underwater_pollution_detection.py +842 -0
  170. matrice_analytics/post_processing/usecases/vehicle_monitoring.py +1029 -0
  171. matrice_analytics/post_processing/usecases/warehouse_object_segmentation.py +899 -0
  172. matrice_analytics/post_processing/usecases/waterbody_segmentation.py +923 -0
  173. matrice_analytics/post_processing/usecases/weapon_detection.py +771 -0
  174. matrice_analytics/post_processing/usecases/weld_defect_detection.py +615 -0
  175. matrice_analytics/post_processing/usecases/wildlife_monitoring.py +898 -0
  176. matrice_analytics/post_processing/usecases/windmill_maintenance.py +834 -0
  177. matrice_analytics/post_processing/usecases/wound_segmentation.py +856 -0
  178. matrice_analytics/post_processing/utils/__init__.py +150 -0
  179. matrice_analytics/post_processing/utils/advanced_counting_utils.py +400 -0
  180. matrice_analytics/post_processing/utils/advanced_helper_utils.py +317 -0
  181. matrice_analytics/post_processing/utils/advanced_tracking_utils.py +461 -0
  182. matrice_analytics/post_processing/utils/alerting_utils.py +213 -0
  183. matrice_analytics/post_processing/utils/category_mapping_utils.py +94 -0
  184. matrice_analytics/post_processing/utils/color_utils.py +592 -0
  185. matrice_analytics/post_processing/utils/counting_utils.py +182 -0
  186. matrice_analytics/post_processing/utils/filter_utils.py +261 -0
  187. matrice_analytics/post_processing/utils/format_utils.py +293 -0
  188. matrice_analytics/post_processing/utils/geometry_utils.py +300 -0
  189. matrice_analytics/post_processing/utils/smoothing_utils.py +358 -0
  190. matrice_analytics/post_processing/utils/tracking_utils.py +234 -0
  191. matrice_analytics/py.typed +0 -0
  192. matrice_analytics-0.1.60.dist-info/METADATA +481 -0
  193. matrice_analytics-0.1.60.dist-info/RECORD +196 -0
  194. matrice_analytics-0.1.60.dist-info/WHEEL +5 -0
  195. matrice_analytics-0.1.60.dist-info/licenses/LICENSE.txt +21 -0
  196. matrice_analytics-0.1.60.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1175 @@
1
+ """
2
+ Main post-processing processor with unified, clean API.
3
+
4
+ This module provides the main PostProcessor class that serves as the entry point
5
+ for all post-processing operations. It manages use cases, configurations, and
6
+ provides both simple and advanced processing interfaces.
7
+ """
8
+
9
+ from typing import Any, Dict, List, Optional, Union
10
+ from pathlib import Path
11
+ import logging
12
+ import time
13
+ from datetime import datetime, timezone
14
+ import hashlib
15
+ import json
16
+
17
+ from .core.base import ProcessingResult, ProcessingContext, ProcessingStatus, registry
18
+ from .core.config import (
19
+ BaseConfig,
20
+ PeopleCountingConfig,
21
+ CustomerServiceConfig,
22
+ IntrusionConfig,
23
+ ProximityConfig,
24
+ config_manager,
25
+ ConfigValidationError,
26
+ PeopleTrackingConfig,
27
+ )
28
+ from .usecases import (
29
+ PeopleCountingUseCase,
30
+ DroneTrafficMonitoringUsecase,
31
+ IntrusionUseCase,
32
+ ProximityUseCase,
33
+ CustomerServiceUseCase,
34
+ AdvancedCustomerServiceUseCase,
35
+ LicensePlateUseCase,
36
+ ColorDetectionUseCase,
37
+ PotholeSegmentationUseCase,
38
+ PPEComplianceUseCase,
39
+ VehicleMonitoringUseCase,
40
+ ShopliftingDetectionUseCase,
41
+ BananaMonitoringUseCase,
42
+ FieldMappingUseCase,
43
+ MaskDetectionUseCase,
44
+ LeafUseCase,
45
+ CarDamageDetectionUseCase,
46
+ LeafDiseaseDetectionUseCase,
47
+ FireSmokeUseCase,
48
+ ShopliftingDetectionConfig,
49
+ FlareAnalysisUseCase,
50
+ WoundSegmentationUseCase,
51
+ ParkingSpaceUseCase,
52
+ ParkingUseCase,
53
+ FaceEmotionUseCase,
54
+ UnderwaterPlasticUseCase,
55
+ PipelineDetectionUseCase,
56
+ PedestrianDetectionUseCase,
57
+ ChickenPoseDetectionUseCase,
58
+ TheftDetectionUseCase,
59
+ TrafficSignMonitoringUseCase,
60
+ AntiSpoofingDetectionUseCase,
61
+ ShelfInventoryUseCase,
62
+ LaneDetectionUseCase,
63
+ LitterDetectionUseCase,
64
+ AbandonedObjectDetectionUseCase,
65
+ LeakDetectionUseCase,
66
+ HumanActivityUseCase,
67
+ GasLeakDetectionUseCase,
68
+ AgeDetectionUseCase,
69
+ WeldDefectUseCase,
70
+ WeaponDetectionUseCase,
71
+ PriceTagUseCase,
72
+ DistractedDriverUseCase,
73
+ EmergencyVehicleUseCase,
74
+ SolarPanelUseCase,
75
+ CropWeedDetectionUseCase,
76
+ ChildMonitoringUseCase,
77
+ GenderDetectionUseCase,
78
+ ConcreteCrackUseCase,
79
+ FashionDetectionUseCase,
80
+ WarehouseObjectUseCase,
81
+ ShoppingCartUseCase,
82
+ BottleDefectUseCase,
83
+ AssemblyLineUseCase,
84
+ CarPartSegmentationUseCase,
85
+ WindmillMaintenanceUseCase,
86
+ FlowerUseCase,
87
+ SmokerDetectionUseCase,
88
+ RoadTrafficUseCase,
89
+ RoadViewSegmentationUseCase,
90
+ # FaceRecognitionUseCase,
91
+ DrowsyDriverUseCase,
92
+ WaterBodyUseCase,
93
+ LicensePlateMonitorUseCase,
94
+ DwellUseCase,
95
+ AgeGenderUseCase,
96
+ PeopleTrackingUseCase,
97
+ WildLifeMonitoringUseCase,
98
+ PCBDefectUseCase,
99
+ UndergroundPipelineDefectUseCase,
100
+ SusActivityUseCase,
101
+ NaturalDisasterUseCase,
102
+ # Put all IMAGE based usecases here
103
+ BloodCancerDetectionUseCase,
104
+ SkinCancerClassificationUseCase,
105
+ PlaqueSegmentationUseCase,
106
+ CardiomegalyUseCase,
107
+ HistopathologicalCancerDetectionUseCase,
108
+ CellMicroscopyUseCase,
109
+ )
110
+
111
+ # Face recognition with embeddings (from face_reg module)
112
+ from .face_reg.face_recognition import FaceRecognitionEmbeddingUseCase
113
+
114
+ from .core.config_utils import create_config_from_template
115
+ from .core.config import BaseConfig, AlertConfig, ZoneConfig, TrackingConfig
116
+ from .config import (
117
+ get_usecase_from_app_name,
118
+ get_category_from_app_name,
119
+ )
120
+
121
+ logger = logging.getLogger(__name__)
122
+
123
+
124
+ class PostProcessor:
125
+ """
126
+ Unified post-processing interface with clean API and comprehensive functionality.
127
+
128
+ This processor provides a simple yet powerful interface for processing model outputs
129
+ with various use cases, centralized configuration management, and comprehensive
130
+ error handling.
131
+
132
+ Examples:
133
+ # Simple usage
134
+ processor = PostProcessor()
135
+ result = processor.process_simple(
136
+ raw_results, "people_counting",
137
+ confidence_threshold=0.6,
138
+ zones={"entrance": [[0, 0], [100, 0], [100, 100], [0, 100]]}
139
+ )
140
+
141
+ # Configuration-based usage
142
+ config = processor.create_config("people_counting", confidence_threshold=0.5)
143
+ result = processor.process(raw_results, config)
144
+
145
+ # File-based configuration
146
+ result = processor.process_from_file(raw_results, "config.json")
147
+ """
148
+
149
+ def __init__(
150
+ self,
151
+ post_processing_config: Optional[Union[Dict[str, Any], BaseConfig, str]] = None,
152
+ app_name: Optional[str] = None,
153
+ index_to_category: Optional[Dict[int, str]] = None,
154
+ target_categories: Optional[List[str]] = None,
155
+ ):
156
+ """Initialize the PostProcessor with registered use cases."""
157
+ self._statistics = {
158
+ "total_processed": 0,
159
+ "successful": 0,
160
+ "failed": 0,
161
+ "total_processing_time": 0.0,
162
+ }
163
+ self.cache = {}
164
+ self._use_case_cache = {} # Cache for use case instances
165
+
166
+ # Register available use cases
167
+ self._register_use_cases()
168
+
169
+ # Set up default post-processing configuration
170
+ self.post_processing_config = None
171
+ self.app_name = app_name
172
+ self.index_to_category = index_to_category
173
+ self.target_categories = target_categories
174
+ if post_processing_config or self.app_name:
175
+ logging.debug(f"Parsing post-processing config: {post_processing_config}")
176
+ self.post_processing_config = self._parse_post_processing_config(
177
+ post_processing_config, self.app_name
178
+ )
179
+ if self.post_processing_config:
180
+ logging.info(
181
+ f"Successfully parsed post-processing config for usecase: {self.post_processing_config.usecase}"
182
+ )
183
+ else:
184
+ logging.warning("Failed to parse post-processing config")
185
+ else:
186
+ logging.info("No post-processing config provided")
187
+
188
+ def _load_config_from_app_name(self, app_name: str) -> Optional[BaseConfig]:
189
+ """Load default post-processing configuration based on app name."""
190
+ usecase = get_usecase_from_app_name(app_name)
191
+ category = get_category_from_app_name(app_name)
192
+ if not usecase or not category:
193
+ logging.warning(f"No usecase or category found for app: {app_name}")
194
+ return None
195
+ config = self.create_config(usecase, category)
196
+ return config
197
+
198
+ def _parse_post_processing_config(
199
+ self,
200
+ config: Union[Dict[str, Any], BaseConfig, str],
201
+ app_name: Optional[str] = None,
202
+ ) -> Optional[BaseConfig]:
203
+ """Parse post-processing configuration from various formats."""
204
+ try:
205
+ if not config and not app_name:
206
+ return None
207
+
208
+ # Handle app-name based configuration first
209
+ if app_name:
210
+ app_config = self._load_config_from_app_name(app_name)
211
+ if app_config and config and isinstance(config, dict):
212
+ return self._merge_config_into_app_config(app_config, config)
213
+ elif app_config:
214
+ return app_config
215
+ else:
216
+ logging.warning(f"No config found for app: {app_name}")
217
+
218
+ # Handle different config input types
219
+ parsed_config = self._parse_config_by_type(config)
220
+ if parsed_config:
221
+ self._apply_instance_config_overrides(parsed_config)
222
+
223
+ return parsed_config
224
+
225
+ except Exception as e:
226
+ logging.error(f"Failed to parse post-processing config: {str(e)}")
227
+ return None
228
+
229
+ def _merge_config_into_app_config(
230
+ self, app_config: BaseConfig, config_dict: Dict[str, Any]
231
+ ) -> BaseConfig:
232
+ """Merge provided configuration dictionary into app-based config."""
233
+ logging.debug(f"Merging provided config into app config")
234
+ logging.debug(f"Provided config keys: {list(config_dict.keys())}")
235
+
236
+ for key, value in config_dict.items():
237
+ if value is None:
238
+ continue
239
+
240
+ if hasattr(app_config, key):
241
+ self._apply_config_value(app_config, key, value)
242
+ else:
243
+ logging.warning(f"Config key '{key}' not found in app config, skipping")
244
+
245
+ logging.debug(f"Final app config zone_config: {getattr(app_config, 'zone_config', None)}")
246
+ return app_config
247
+
248
+ def _apply_config_value(self, config: BaseConfig, key: str, value: Any) -> None:
249
+ """Apply a configuration value to a config object, handling nested dicts."""
250
+ if isinstance(value, dict):
251
+ current_value = getattr(config, key)
252
+ try:
253
+ # Try to convert known config dicts to dataclasses
254
+ if key == "alert_config":
255
+ setattr(config, key, AlertConfig(**value))
256
+ elif key == "zone_config":
257
+ setattr(config, key, ZoneConfig(**value))
258
+ elif key == "tracking_config":
259
+ setattr(config, key, TrackingConfig(**value))
260
+ elif isinstance(current_value, dict):
261
+ # Merge dictionaries
262
+ merged_dict = {**(current_value or {}), **value}
263
+ setattr(config, key, merged_dict)
264
+ logging.debug(f"Merged nested dict for {key}: {merged_dict}")
265
+ else:
266
+ setattr(config, key, value)
267
+ except Exception:
268
+ # Fallback to direct assignment
269
+ setattr(config, key, value)
270
+ logging.debug(f"Applied config parameter {key}={value} (fallback)")
271
+ else:
272
+ setattr(config, key, value)
273
+ logging.debug(f"Applied config parameter {key}={value}")
274
+
275
+ def _parse_config_by_type(
276
+ self, config: Union[Dict[str, Any], BaseConfig, str]
277
+ ) -> Optional[BaseConfig]:
278
+ """Parse configuration based on its input type."""
279
+ if isinstance(config, BaseConfig):
280
+ return config
281
+ elif isinstance(config, dict):
282
+ return self._parse_config_dict(config)
283
+ elif isinstance(config, str):
284
+ return create_config_from_template(config)
285
+ else:
286
+ logging.warning(f"Unsupported config type: {type(config)}")
287
+ return None
288
+
289
+ def _parse_config_dict(self, config: Dict[str, Any]) -> Optional[BaseConfig]:
290
+ """Parse configuration from a dictionary."""
291
+ usecase = config.get("usecase")
292
+ if not usecase:
293
+ raise ValueError("Configuration dict must contain 'usecase' key")
294
+
295
+ # Prepare config parameters
296
+ config_params = config.copy()
297
+ config_params.pop("usecase", None)
298
+ config_params.pop("category", None)
299
+ category = config.get("category", "general")
300
+
301
+ # Clean up use-case specific parameters
302
+ self._clean_use_case_specific_params(usecase, config_params)
303
+
304
+ # Normalize nested config objects
305
+ self._normalize_nested_configs(config_params)
306
+
307
+ # Create config using the factory
308
+ return self.create_config(usecase, category, **config_params)
309
+
310
+ def _clean_use_case_specific_params(
311
+ self, usecase: str, config_params: Dict[str, Any]
312
+ ) -> None:
313
+ """Remove parameters that aren't needed for specific use cases."""
314
+ facial_recognition_usecases = {"face_recognition"}
315
+ license_plate_monitoring_usecases = {"license_plate_monitor"}
316
+
317
+ if usecase not in facial_recognition_usecases:
318
+ if "facial_recognition_server_id" in config_params:
319
+ logging.debug(
320
+ f"Removing facial_recognition_server_id from {usecase} config"
321
+ )
322
+ config_params.pop("facial_recognition_server_id", None)
323
+ config_params.pop("deployment_id", None)
324
+
325
+ if usecase not in license_plate_monitoring_usecases:
326
+ if "lpr_server_id" in config_params:
327
+ logging.debug(f"Removing lpr_server_id from {usecase} config")
328
+ config_params.pop("lpr_server_id", None)
329
+
330
+ # Keep session and lpr_server_id only for use cases that need them
331
+ if usecase not in facial_recognition_usecases and usecase not in license_plate_monitoring_usecases:
332
+ if "session" in config_params:
333
+ logging.debug(f"Removing session from {usecase} config")
334
+ config_params.pop("session", None)
335
+
336
+ def _normalize_nested_configs(self, config_params: Dict[str, Any]) -> None:
337
+ """Convert nested config dictionaries to dataclass instances."""
338
+ config_mappings = {
339
+ "alert_config": AlertConfig,
340
+ "zone_config": ZoneConfig,
341
+ "tracking_config": TrackingConfig,
342
+ }
343
+
344
+ for key, config_class in config_mappings.items():
345
+ if isinstance(config_params.get(key), dict):
346
+ try:
347
+ config_params[key] = config_class(**config_params[key])
348
+ except Exception:
349
+ # Leave as dict; downstream create_config will handle it
350
+ pass
351
+
352
+ def _apply_instance_config_overrides(self, config: BaseConfig) -> None:
353
+ """Apply instance-level configuration overrides."""
354
+ if hasattr(config, "index_to_category"):
355
+ if not config.index_to_category:
356
+ config.index_to_category = self.index_to_category or {}
357
+ else:
358
+ self.index_to_category = config.index_to_category
359
+
360
+ if hasattr(config, "target_categories"):
361
+ if not config.target_categories:
362
+ config.target_categories = self.target_categories
363
+ else:
364
+ self.target_categories = config.target_categories
365
+
366
+ def _register_use_cases(self) -> None:
367
+ """Register all available use cases."""
368
+ # Register people counting use case
369
+ registry.register_use_case("general", "people_counting", PeopleCountingUseCase)
370
+
371
+ # Register intrusion detection use case
372
+ registry.register_use_case("security", "intrusion_detection", IntrusionUseCase)
373
+
374
+ # Register proximity detection use case
375
+ registry.register_use_case("security", "proximity_detection", ProximityUseCase)
376
+
377
+ # Register customer service use case
378
+ registry.register_use_case("sales", "customer_service", CustomerServiceUseCase)
379
+
380
+ # Register advanced customer service use case
381
+ registry.register_use_case(
382
+ "sales", "advanced_customer_service", AdvancedCustomerServiceUseCase
383
+ )
384
+
385
+ # Register license plate detection use case
386
+ registry.register_use_case(
387
+ "license_plate", "license_plate_detection", LicensePlateUseCase
388
+ )
389
+
390
+ # Register color detection use case
391
+ registry.register_use_case(
392
+ "visual_appearance", "color_detection", ColorDetectionUseCase
393
+ )
394
+
395
+ # Register video_color_classification as alias for color_detection
396
+ registry.register_use_case(
397
+ "visual_appearance", "video_color_classification", ColorDetectionUseCase
398
+ )
399
+
400
+ # Register PPE compliance use case
401
+ registry.register_use_case(
402
+ "ppe", "ppe_compliance_detection", PPEComplianceUseCase
403
+ )
404
+ registry.register_use_case(
405
+ "infrastructure", "pothole_segmentation", PotholeSegmentationUseCase
406
+ )
407
+ registry.register_use_case(
408
+ "car_damage", "car_damage_detection", CarDamageDetectionUseCase
409
+ )
410
+
411
+ registry.register_use_case(
412
+ "traffic", "vehicle_monitoring", VehicleMonitoringUseCase
413
+ )
414
+ registry.register_use_case(
415
+ "traffic", "fruit_monitoring", BananaMonitoringUseCase
416
+ )
417
+ registry.register_use_case("security", "theft_detection", TheftDetectionUseCase)
418
+ registry.register_use_case(
419
+ "traffic", "traffic_sign_monitoring", TrafficSignMonitoringUseCase
420
+ )
421
+ registry.register_use_case(
422
+ "traffic", "drone_traffic_monitoring", DroneTrafficMonitoringUsecase
423
+ )
424
+ registry.register_use_case(
425
+ "security", "anti_spoofing_detection", AntiSpoofingDetectionUseCase
426
+ )
427
+ registry.register_use_case("retail", "shelf_inventory", ShelfInventoryUseCase)
428
+ registry.register_use_case("traffic", "lane_detection", LaneDetectionUseCase)
429
+ registry.register_use_case(
430
+ "security", "abandoned_object_detection", AbandonedObjectDetectionUseCase
431
+ )
432
+ registry.register_use_case("hazard", "fire_smoke_detection", FireSmokeUseCase)
433
+ registry.register_use_case(
434
+ "flare_detection", "flare_analysis", FlareAnalysisUseCase
435
+ )
436
+ registry.register_use_case("general", "face_emotion", FaceEmotionUseCase)
437
+ registry.register_use_case(
438
+ "parking_space", "parking_space_detection", ParkingSpaceUseCase
439
+ )
440
+ registry.register_use_case(
441
+ "environmental", "underwater_pollution_detection", UnderwaterPlasticUseCase
442
+ )
443
+ registry.register_use_case(
444
+ "pedestrian", "pedestrian_detection", PedestrianDetectionUseCase
445
+ )
446
+ registry.register_use_case("general", "age_detection", AgeDetectionUseCase)
447
+ registry.register_use_case("weld", "weld_defect_detection", WeldDefectUseCase)
448
+ registry.register_use_case("price_tag", "price_tag_detection", PriceTagUseCase)
449
+ registry.register_use_case(
450
+ "mask_detection", "mask_detection", MaskDetectionUseCase
451
+ )
452
+ registry.register_use_case(
453
+ "pipeline_detection", "pipeline_detection", PipelineDetectionUseCase
454
+ )
455
+ registry.register_use_case(
456
+ "automobile", "distracted_driver_detection", DistractedDriverUseCase
457
+ )
458
+ registry.register_use_case(
459
+ "traffic", "emergency_vehicle_detection", EmergencyVehicleUseCase
460
+ )
461
+ registry.register_use_case("energy", "solar_panel", SolarPanelUseCase)
462
+ registry.register_use_case(
463
+ "agriculture", "chicken_pose_detection", ChickenPoseDetectionUseCase
464
+ )
465
+ registry.register_use_case(
466
+ "agriculture", "crop_weed_detection", CropWeedDetectionUseCase
467
+ )
468
+ registry.register_use_case(
469
+ "security", "child_monitoring", ChildMonitoringUseCase
470
+ )
471
+ registry.register_use_case(
472
+ "general", "gender_detection", GenderDetectionUseCase
473
+ )
474
+ registry.register_use_case(
475
+ "security", "weapon_detection", WeaponDetectionUseCase
476
+ )
477
+ registry.register_use_case(
478
+ "general", "concrete_crack_detection", ConcreteCrackUseCase
479
+ )
480
+ registry.register_use_case(
481
+ "retail", "fashion_detection", FashionDetectionUseCase
482
+ )
483
+
484
+ registry.register_use_case(
485
+ "retail", "warehouse_object_segmentation", WarehouseObjectUseCase
486
+ )
487
+ registry.register_use_case(
488
+ "retail", "shopping_cart_analysis", ShoppingCartUseCase
489
+ )
490
+
491
+ registry.register_use_case(
492
+ "security", "shoplifting_detection", ShopliftingDetectionUseCase
493
+ )
494
+ registry.register_use_case(
495
+ "retail", "defect_detection_products", BottleDefectUseCase
496
+ )
497
+ registry.register_use_case(
498
+ "manufacturing", "assembly_line_detection", AssemblyLineUseCase
499
+ )
500
+ registry.register_use_case(
501
+ "automobile", "car_part_segmentation", CarPartSegmentationUseCase
502
+ )
503
+
504
+ registry.register_use_case(
505
+ "manufacturing", "windmill_maintenance", WindmillMaintenanceUseCase
506
+ )
507
+
508
+ registry.register_use_case(
509
+ "infrastructure", "field_mapping", FieldMappingUseCase
510
+ )
511
+ registry.register_use_case(
512
+ "medical", "wound_segmentation", WoundSegmentationUseCase
513
+ )
514
+ registry.register_use_case(
515
+ "agriculture", "leaf_disease_detection", LeafDiseaseDetectionUseCase
516
+ )
517
+ registry.register_use_case("agriculture", "flower_segmentation", FlowerUseCase)
518
+ registry.register_use_case("general", "parking_det", ParkingUseCase)
519
+ registry.register_use_case("agriculture", "leaf_det", LeafUseCase)
520
+ registry.register_use_case(
521
+ "general", "smoker_detection", SmokerDetectionUseCase
522
+ )
523
+ registry.register_use_case(
524
+ "automobile", "road_traffic_density", RoadTrafficUseCase
525
+ )
526
+ registry.register_use_case(
527
+ "automobile", "road_view_segmentation", RoadViewSegmentationUseCase
528
+ )
529
+ # registry.register_use_case("security", "face_recognition", FaceRecognitionUseCase)
530
+ registry.register_use_case(
531
+ "security", "face_recognition", FaceRecognitionEmbeddingUseCase
532
+ )
533
+ registry.register_use_case(
534
+ "automobile", "drowsy_driver_detection", DrowsyDriverUseCase
535
+ )
536
+ registry.register_use_case(
537
+ "agriculture", "waterbody_segmentation", WaterBodyUseCase
538
+ )
539
+ registry.register_use_case(
540
+ "litter_detection", "litter_detection", LitterDetectionUseCase
541
+ )
542
+ registry.register_use_case("oil_gas", "leak_detection", LeakDetectionUseCase)
543
+ registry.register_use_case(
544
+ "general", "human_activity_recognition", HumanActivityUseCase
545
+ )
546
+ registry.register_use_case(
547
+ "oil_gas", "gas_leak_detection", GasLeakDetectionUseCase
548
+ )
549
+ registry.register_use_case(
550
+ "license_plate_monitor", "license_plate_monitor", LicensePlateMonitorUseCase
551
+ )
552
+ registry.register_use_case("general", "dwell", DwellUseCase)
553
+ registry.register_use_case(
554
+ "age_gender_detection", "age_gender_detection", AgeGenderUseCase
555
+ )
556
+ registry.register_use_case("general", "people_tracking", PeopleTrackingUseCase)
557
+ registry.register_use_case(
558
+ "environmental", "wildlife_monitoring", WildLifeMonitoringUseCase
559
+ )
560
+ registry.register_use_case(
561
+ "manufacturing", "pcb_defect_detection", PCBDefectUseCase
562
+ )
563
+ registry.register_use_case(
564
+ "general", "underground_pipeline_defect", UndergroundPipelineDefectUseCase
565
+ )
566
+ registry.register_use_case(
567
+ "security", "suspicious_activity_detection", SusActivityUseCase
568
+ )
569
+ registry.register_use_case(
570
+ "environmental", "natural_disaster_detection", NaturalDisasterUseCase
571
+ )
572
+
573
+ # Put all IMAGE based usecases here
574
+ registry.register_use_case(
575
+ "healthcare", "bloodcancer_img_detection", BloodCancerDetectionUseCase
576
+ )
577
+ registry.register_use_case(
578
+ "healthcare",
579
+ "skincancer_img_classification",
580
+ SkinCancerClassificationUseCase,
581
+ )
582
+ registry.register_use_case(
583
+ "healthcare", "plaque_img_segmentation", PlaqueSegmentationUseCase
584
+ )
585
+ registry.register_use_case(
586
+ "healthcare", "cardiomegaly_classification", CardiomegalyUseCase
587
+ )
588
+ registry.register_use_case(
589
+ "healthcare",
590
+ "histopathological_cancer_detection",
591
+ HistopathologicalCancerDetectionUseCase,
592
+ )
593
+ registry.register_use_case(
594
+ "healthcare", "cell_microscopy_segmentation", CellMicroscopyUseCase
595
+ )
596
+
597
+ logger.debug("Registered use cases with registry")
598
+
599
+ def _generate_cache_key(self, config: BaseConfig, stream_key: Optional[str] = None) -> str:
600
+ """
601
+ Generate a cache key for use case instances based on config and stream key.
602
+
603
+ Args:
604
+ config: Configuration object
605
+ stream_key: Optional stream key
606
+
607
+ Returns:
608
+ str: Cache key for the use case instance
609
+ """
610
+ def _make_json_serializable(obj):
611
+ """Convert objects to JSON-serializable format."""
612
+ if hasattr(obj, 'to_dict') and callable(getattr(obj, 'to_dict')):
613
+ return _make_json_serializable(obj.to_dict())
614
+ elif isinstance(obj, dict):
615
+ return {k: _make_json_serializable(v) for k, v in obj.items()}
616
+ elif isinstance(obj, list):
617
+ return [_make_json_serializable(item) for item in obj]
618
+ elif isinstance(obj, (str, int, float, bool, type(None))):
619
+ return obj
620
+ else:
621
+ return str(obj)
622
+
623
+ # Create a deterministic cache key based on config parameters and stream key
624
+ cache_data = {
625
+ 'category': getattr(config, 'category', 'general'),
626
+ 'usecase': getattr(config, 'usecase', 'unknown'),
627
+ 'stream_key': stream_key or 'default',
628
+ }
629
+
630
+ # Add key configuration parameters that might affect use case behavior
631
+ try:
632
+ config_dict = config.to_dict() if hasattr(config, 'to_dict') else {}
633
+ # Only include parameters that affect use case instantiation/behavior
634
+ relevant_params = ['confidence_threshold', 'zones', 'tracking_config', 'alert_config']
635
+ for param in relevant_params:
636
+ if param in config_dict:
637
+ cache_data[param] = _make_json_serializable(config_dict[param])
638
+ except Exception:
639
+ # Fallback to basic cache key if config serialization fails
640
+ pass
641
+
642
+ # Sort keys for consistent hashing
643
+ config_str = json.dumps(cache_data, sort_keys=True, default=str)
644
+ return hashlib.md5(config_str.encode()).hexdigest()[:16] # Shorter hash for readability
645
+
646
+ async def _get_use_case_instance(
647
+ self, config: BaseConfig, stream_key: Optional[str] = None
648
+ ):
649
+ """
650
+ Get or create a cached use case instance.
651
+
652
+ Args:
653
+ config: Configuration object
654
+ stream_key: Optional stream key
655
+
656
+ Returns:
657
+ Use case instance
658
+ """
659
+ # Generate cache key
660
+ cache_key = self._generate_cache_key(config, stream_key)
661
+
662
+ # Check if we have a cached instance
663
+ if cache_key in self._use_case_cache:
664
+ logger.debug(f"Using cached use case instance for key: {cache_key}")
665
+ return self._use_case_cache[cache_key]
666
+
667
+ # Get appropriate use case class
668
+ use_case_class = registry.get_use_case(config.category, config.usecase)
669
+ if not use_case_class:
670
+ raise ValueError(f"Use case '{config.category}/{config.usecase}' not found")
671
+
672
+
673
+ if use_case_class == FaceRecognitionEmbeddingUseCase:
674
+ use_case = use_case_class(config=config)
675
+ # Await async initialization for face recognition use case
676
+ await use_case.initialize(config)
677
+ else:
678
+ use_case = use_case_class()
679
+ logger.info(f"Created use case instance for: {config.category}/{config.usecase}")
680
+
681
+
682
+ # Cache the instance
683
+ self._use_case_cache[cache_key] = use_case
684
+ logger.debug(f"Cached new use case instance for key: {cache_key}")
685
+
686
+ return use_case
687
+
688
+ async def _dispatch_use_case_processing(
689
+ self,
690
+ use_case,
691
+ data: Any,
692
+ config: BaseConfig,
693
+ input_bytes: Optional[bytes],
694
+ context: ProcessingContext,
695
+ stream_info: Optional[Dict[str, Any]]
696
+ ) -> ProcessingResult:
697
+ """
698
+ Dispatch processing to the appropriate use case with correct parameters.
699
+
700
+ This method handles the different method signatures required by different use cases.
701
+ """
702
+ # Use cases that require input_bytes parameter
703
+ use_cases_with_bytes = {
704
+ ColorDetectionUseCase,
705
+ FlareAnalysisUseCase,
706
+ LicensePlateMonitorUseCase,
707
+ AgeGenderUseCase,
708
+ PeopleTrackingUseCase,
709
+ FaceRecognitionEmbeddingUseCase
710
+ }
711
+
712
+ # Async use cases
713
+ async_use_cases = {
714
+ FaceRecognitionEmbeddingUseCase,
715
+ LicensePlateMonitorUseCase
716
+ }
717
+
718
+ # Determine the appropriate method signature and call
719
+ use_case_type = type(use_case)
720
+
721
+ if use_case_type in async_use_cases:
722
+ # Handle async use cases
723
+ if use_case_type in use_cases_with_bytes:
724
+ result = await use_case.process(data, config, input_bytes, context, stream_info)
725
+ else:
726
+ result = await use_case.process(data, config, context, stream_info)
727
+ else:
728
+ # Handle synchronous use cases
729
+ if use_case_type in use_cases_with_bytes:
730
+ result = use_case.process(data, config, input_bytes, context, stream_info)
731
+ else:
732
+ # Default signature for most use cases
733
+ result = use_case.process(data, config, context, stream_info)
734
+
735
+ return result
736
+
737
+ async def process(
738
+ self,
739
+ data: Any,
740
+ config: Union[BaseConfig, Dict[str, Any], str, Path] = {},
741
+ input_bytes: Optional[bytes] = None,
742
+ stream_key: Optional[str] = "default_stream",
743
+ stream_info: Optional[Dict[str, Any]] = None,
744
+ context: Optional[ProcessingContext] = None,
745
+ custom_post_processing_config: Optional[Union[Dict[str, Any], BaseConfig, str]] = None,
746
+ ) -> ProcessingResult:
747
+ """
748
+ Process data using the specified configuration.
749
+
750
+ Args:
751
+ data: Raw model output (detection, tracking, classification results)
752
+ config: Configuration object, dict, or path to config file
753
+ input_bytes: Optional input bytes for certain use cases
754
+ custom_post_processing_config: Optional custom post processing configuration
755
+ stream_key: Stream key for the inference
756
+ stream_info: Stream info for the inference (optional)
757
+ context: Optional processing context
758
+ custom_post_processing_config: Optional custom post processing configuration
759
+ Returns:
760
+ ProcessingResult: Standardized result object
761
+ """
762
+ start_time = time.time()
763
+
764
+ try:
765
+ if config:
766
+ try:
767
+ config = self._parse_config(config)
768
+ except Exception as e:
769
+ logger.error(f"Failed to parse config: {e}", exc_info=True)
770
+ raise ValueError(f"Failed to parse config: {e}")
771
+
772
+ parsed_config = config or self.post_processing_config
773
+
774
+ if not parsed_config:
775
+ raise ValueError("No valid configuration found")
776
+
777
+
778
+ # Get cached use case instance (await since it's async now)
779
+ use_case = await self._get_use_case_instance(parsed_config, stream_key)
780
+
781
+ # Create context if not provided
782
+ if context is None:
783
+ context = ProcessingContext()
784
+
785
+ # Process with use case using dispatch pattern
786
+ result = await self._dispatch_use_case_processing(
787
+ use_case, data, parsed_config, input_bytes, context, stream_info
788
+ )
789
+
790
+ # Add processing time
791
+ result.processing_time = time.time() - start_time
792
+
793
+ # Update statistics
794
+ self._update_statistics(result)
795
+
796
+ return result
797
+
798
+ except Exception as e:
799
+ processing_time = time.time() - start_time
800
+ logger.error(f"Processing failed: {str(e)}", exc_info=True)
801
+
802
+ error_result = self._create_error_result(
803
+ str(e), type(e).__name__, context=context
804
+ )
805
+ error_result.processing_time = processing_time
806
+
807
+ # Update statistics
808
+ self._update_statistics(error_result)
809
+
810
+ return error_result
811
+
812
+ async def process_simple(
813
+ self,
814
+ data: Any,
815
+ usecase: str,
816
+ category: Optional[str] = None,
817
+ context: Optional[ProcessingContext] = None,
818
+ stream_key: Optional[str] = None,
819
+ stream_info: Optional[Dict[str, Any]] = None,
820
+ **config_params,
821
+ ) -> ProcessingResult:
822
+ """
823
+ Simple processing interface for quick use cases.
824
+
825
+ Args:
826
+ data: Raw model output
827
+ usecase: Use case name ('people_counting', 'customer_service', etc.)
828
+ category: Use case category (auto-detected if not provided)
829
+ context: Optional processing context
830
+ stream_key: Optional stream key for caching
831
+ stream_info: Stream info for the inference (optional)
832
+ **config_params: Configuration parameters
833
+
834
+ Returns:
835
+ ProcessingResult: Standardized result object
836
+ """
837
+ try:
838
+ # Auto-detect category if not provided
839
+ if category is None:
840
+ if usecase == "people_counting":
841
+ category = "general"
842
+ elif usecase == "customer_service":
843
+ category = "sales"
844
+ elif usecase in ["color_detection", "video_color_classification"]:
845
+ category = "visual_appearance"
846
+ elif usecase == "people_tracking":
847
+ category = "general"
848
+ else:
849
+ category = "general" # Default fallback
850
+
851
+ # Create configuration
852
+ config = self.create_config(usecase, category=category, **config_params)
853
+ return await self.process(
854
+ data,
855
+ config,
856
+ context=context,
857
+ stream_key=stream_key,
858
+ stream_info=stream_info,
859
+ )
860
+
861
+ except Exception as e:
862
+ logger.error(f"Simple processing failed: {str(e)}", exc_info=True)
863
+ return self._create_error_result(
864
+ str(e), type(e).__name__, usecase, category or "general", context
865
+ )
866
+
867
+ async def process_from_file(
868
+ self,
869
+ data: Any,
870
+ config_file: Union[str, Path],
871
+ context: Optional[ProcessingContext] = None,
872
+ stream_key: Optional[str] = None,
873
+ stream_info: Optional[Dict[str, Any]] = None,
874
+ ) -> ProcessingResult:
875
+ """
876
+ Process data using configuration from file.
877
+
878
+ Args:
879
+ data: Raw model output
880
+ config_file: Path to configuration file (JSON or YAML)
881
+ context: Optional processing context
882
+ stream_key: Optional stream key for caching
883
+ stream_info: Stream info for the inference (optional)
884
+ Returns:
885
+ ProcessingResult: Standardized result object
886
+ """
887
+ try:
888
+ config = config_manager.load_from_file(config_file)
889
+ return await self.process(
890
+ data,
891
+ config,
892
+ context=context,
893
+ stream_key=stream_key,
894
+ stream_info=stream_info,
895
+ )
896
+
897
+ except Exception as e:
898
+ logger.error(f"File-based processing failed: {str(e)}", exc_info=True)
899
+ return self._create_error_result(
900
+ f"Failed to process with config file: {str(e)}",
901
+ type(e).__name__,
902
+ context=context,
903
+ )
904
+
905
+ def create_config(
906
+ self, usecase: str, category: str = "general", **kwargs
907
+ ) -> BaseConfig:
908
+ """
909
+ Create a validated configuration object.
910
+
911
+ Args:
912
+ usecase: Use case name
913
+ category: Use case category
914
+ **kwargs: Configuration parameters
915
+
916
+ Returns:
917
+ BaseConfig: Validated configuration object
918
+ """
919
+ return config_manager.create_config(usecase, category=category, **kwargs)
920
+
921
+ def load_config(self, file_path: Union[str, Path]) -> BaseConfig:
922
+ """Load configuration from file."""
923
+ return config_manager.load_from_file(file_path)
924
+
925
+ def save_config(
926
+ self, config: BaseConfig, file_path: Union[str, Path], format: str = "json"
927
+ ) -> None:
928
+ """Save configuration to file."""
929
+ config_manager.save_to_file(config, file_path, format)
930
+
931
+ def get_config_template(self, usecase: str) -> Dict[str, Any]:
932
+ """Get configuration template for a use case."""
933
+ return config_manager.get_config_template(usecase)
934
+
935
+ def list_available_usecases(self) -> Dict[str, List[str]]:
936
+ """List all available use cases by category."""
937
+ return registry.list_use_cases()
938
+
939
+ def get_supported_usecases(self) -> List[str]:
940
+ """Get list of supported use case names."""
941
+ return config_manager.list_supported_usecases()
942
+
943
+ def get_use_case_schema(
944
+ self, usecase: str, category: str = "general"
945
+ ) -> Dict[str, Any]:
946
+ """
947
+ Get JSON schema for a use case configuration.
948
+
949
+ Args:
950
+ usecase: Use case name
951
+ category: Use case category
952
+
953
+ Returns:
954
+ Dict[str, Any]: JSON schema for the use case
955
+ """
956
+ use_case_class = registry.get_use_case(category, usecase)
957
+ if not use_case_class:
958
+ raise ValueError(f"Use case '{category}/{usecase}' not found")
959
+
960
+ use_case = use_case_class()
961
+ return use_case.get_config_schema()
962
+
963
+ def validate_config(self, config: Union[BaseConfig, Dict[str, Any]]) -> List[str]:
964
+ """
965
+ Validate a configuration object or dictionary.
966
+
967
+ Args:
968
+ config: Configuration to validate
969
+
970
+ Returns:
971
+ List[str]: List of validation errors (empty if valid)
972
+ """
973
+ try:
974
+ if isinstance(config, dict):
975
+ usecase = config.get("usecase")
976
+ if not usecase:
977
+ return ["Configuration must specify 'usecase'"]
978
+
979
+ category = config.get("category", "general")
980
+ parsed_config = config_manager.create_config(
981
+ usecase, category=category, **config
982
+ )
983
+ return parsed_config.validate()
984
+ elif isinstance(config, BaseConfig):
985
+ return config.validate()
986
+ else:
987
+ return [f"Invalid configuration type: {type(config)}"]
988
+
989
+ except Exception as e:
990
+ return [f"Configuration validation failed: {str(e)}"]
991
+
992
+ def clear_use_case_cache(self) -> None:
993
+ """Clear the use case instance cache."""
994
+ self._use_case_cache.clear()
995
+ logger.debug("Cleared use case instance cache")
996
+
997
+ def get_cache_stats(self) -> Dict[str, Any]:
998
+ """
999
+ Get statistics about the use case cache.
1000
+
1001
+ Returns:
1002
+ Dict[str, Any]: Cache statistics
1003
+ """
1004
+ return {
1005
+ "cached_instances": len(self._use_case_cache),
1006
+ "cache_keys": list(self._use_case_cache.keys()),
1007
+ }
1008
+
1009
+ def get_statistics(self) -> Dict[str, Any]:
1010
+ """
1011
+ Get processing statistics.
1012
+
1013
+ Returns:
1014
+ Dict[str, Any]: Processing statistics
1015
+ """
1016
+ stats = self._statistics.copy()
1017
+ if stats["total_processed"] > 0:
1018
+ stats["success_rate"] = stats["successful"] / stats["total_processed"]
1019
+ stats["failure_rate"] = stats["failed"] / stats["total_processed"]
1020
+ stats["average_processing_time"] = (
1021
+ stats["total_processing_time"] / stats["total_processed"]
1022
+ )
1023
+ else:
1024
+ stats["success_rate"] = 0.0
1025
+ stats["failure_rate"] = 0.0
1026
+ stats["average_processing_time"] = 0.0
1027
+
1028
+ # Add cache statistics
1029
+ stats["cache_stats"] = self.get_cache_stats()
1030
+
1031
+ return stats
1032
+
1033
+ def reset_statistics(self) -> None:
1034
+ """Reset processing statistics."""
1035
+ self._statistics = {
1036
+ "total_processed": 0,
1037
+ "successful": 0,
1038
+ "failed": 0,
1039
+ "total_processing_time": 0.0,
1040
+ }
1041
+
1042
+ def _parse_config( # TODO: remove all of the kwargs that are not in the use case config
1043
+ self, config: Union[BaseConfig, Dict[str, Any], str, Path]
1044
+ ) -> BaseConfig:
1045
+ """Parse configuration from various input formats."""
1046
+ if isinstance(config, BaseConfig):
1047
+ return config
1048
+ elif isinstance(config, dict):
1049
+ usecase = config.get("usecase")
1050
+ if not usecase:
1051
+ raise ValueError("Configuration dict must contain 'usecase' key")
1052
+
1053
+ category = config.get("category", "general")
1054
+ return config_manager.create_config(usecase, category=category, **config)
1055
+ elif isinstance(config, (str, Path)):
1056
+ return config_manager.load_from_file(config)
1057
+ else:
1058
+ raise ValueError(f"Unsupported config type: {type(config)}")
1059
+
1060
+ def _create_error_result(
1061
+ self,
1062
+ message: str,
1063
+ error_type: str = "ProcessingError",
1064
+ usecase: str = "",
1065
+ category: str = "",
1066
+ context: Optional[ProcessingContext] = None,
1067
+ ) -> ProcessingResult:
1068
+ """Create an error result with structured events."""
1069
+ # Create structured error event
1070
+ error_event = {
1071
+ "type": "processing_error",
1072
+ "stream_time": datetime.now(timezone.utc).strftime("%Y-%m-%d-%H:%M:%S UTC"),
1073
+ "level": "critical",
1074
+ "intensity": 5,
1075
+ "config": {
1076
+ "min_value": 0,
1077
+ "max_value": 10,
1078
+ "level_settings": {"info": 2, "warning": 5, "critical": 7},
1079
+ },
1080
+ "application_name": (
1081
+ f"{usecase.title()} Processing" if usecase else "Post Processing"
1082
+ ),
1083
+ "application_version": "1.0",
1084
+ "location_info": None,
1085
+ "human_text": f"Event: Processing Error\nLevel: Critical\nTime: {datetime.now(timezone.utc).strftime('%Y-%m-%d-%H:%M:%S UTC')}\nError: {message}",
1086
+ }
1087
+
1088
+ result = ProcessingResult(
1089
+ data={
1090
+ "events": [error_event],
1091
+ "tracking_stats": [],
1092
+ "error_details": {"message": message, "type": error_type},
1093
+ },
1094
+ status=ProcessingStatus.ERROR,
1095
+ usecase=usecase,
1096
+ category=category,
1097
+ context=context,
1098
+ error_message=message,
1099
+ error_type=error_type,
1100
+ summary=f"Processing failed: {message}",
1101
+ )
1102
+
1103
+ if context:
1104
+ result.processing_time = context.processing_time or 0.0
1105
+
1106
+ return result
1107
+
1108
+ def _update_statistics(self, result: ProcessingResult) -> None:
1109
+ """Update processing statistics."""
1110
+ self._statistics["total_processed"] += 1
1111
+ self._statistics["total_processing_time"] += result.processing_time
1112
+
1113
+ if result.is_success():
1114
+ self._statistics["successful"] += 1
1115
+ else:
1116
+ self._statistics["failed"] += 1
1117
+
1118
+
1119
+ # Convenience functions for backward compatibility and simple usage
1120
+ async def process_simple(
1121
+ data: Any, usecase: str, category: Optional[str] = None, **config
1122
+ ) -> ProcessingResult:
1123
+ """
1124
+ Simple processing function for quick use cases.
1125
+
1126
+ Args:
1127
+ data: Raw model output
1128
+ usecase: Use case name ('people_counting', 'customer_service', etc.)
1129
+ category: Use case category (auto-detected if not provided)
1130
+ **config: Configuration parameters
1131
+
1132
+ Returns:
1133
+ ProcessingResult: Standardized result object
1134
+ """
1135
+ processor = PostProcessor()
1136
+ return await processor.process_simple(data, usecase, category, **config)
1137
+
1138
+
1139
+ def create_config_template(usecase: str) -> Dict[str, Any]:
1140
+ """
1141
+ Create a configuration template for a use case.
1142
+
1143
+ Args:
1144
+ usecase: Use case name
1145
+
1146
+ Returns:
1147
+ Dict[str, Any]: Configuration template
1148
+ """
1149
+ processor = PostProcessor()
1150
+ return processor.get_config_template(usecase)
1151
+
1152
+
1153
+ def list_available_usecases() -> Dict[str, List[str]]:
1154
+ """
1155
+ List all available use cases.
1156
+
1157
+ Returns:
1158
+ Dict[str, List[str]]: Available use cases by category
1159
+ """
1160
+ processor = PostProcessor()
1161
+ return processor.list_available_usecases()
1162
+
1163
+
1164
+ def validate_config(config: Union[BaseConfig, Dict[str, Any]]) -> List[str]:
1165
+ """
1166
+ Validate a configuration.
1167
+
1168
+ Args:
1169
+ config: Configuration to validate
1170
+
1171
+ Returns:
1172
+ List[str]: List of validation errors
1173
+ """
1174
+ processor = PostProcessor()
1175
+ return processor.validate_config(config)