matrice-analytics 0.1.2__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.

Potentially problematic release.


This version of matrice-analytics might be problematic. Click here for more details.

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