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,704 @@
1
+ """
2
+ Base classes and interfaces for the post-processing system.
3
+
4
+ This module provides the core abstractions that all post-processing components should follow.
5
+ """
6
+
7
+ from abc import ABC, abstractmethod
8
+ from dataclasses import dataclass, field
9
+ from typing import Any, Dict, List, Optional, Union, Type, Protocol, runtime_checkable
10
+ from enum import Enum
11
+ import time
12
+ import logging
13
+ from datetime import datetime, timezone
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ class ResultFormat(Enum):
19
+ """Supported result formats."""
20
+ DETECTION = "detection"
21
+ TRACKING = "tracking"
22
+ OBJECT_TRACKING = "object_tracking"
23
+ CLASSIFICATION = "classification"
24
+ INSTANCE_SEGMENTATION = "instance_segmentation"
25
+ ACTIVITY_RECOGNITION = "activity_recognition"
26
+ FACE_RECOGNITION = "face_recognition"
27
+ UNKNOWN = "unknown"
28
+
29
+
30
+ class ProcessingStatus(Enum):
31
+ """Processing status indicators."""
32
+ SUCCESS = "success"
33
+ ERROR = "error"
34
+ WARNING = "warning"
35
+ PARTIAL = "partial"
36
+
37
+
38
+ @dataclass
39
+ class ProcessingContext:
40
+ """Context information for processing operations."""
41
+
42
+ # Input information
43
+ input_format: ResultFormat = ResultFormat.UNKNOWN
44
+ input_size: Optional[int] = None
45
+ timestamp: float = field(default_factory=time.time)
46
+
47
+ # Processing configuration
48
+ confidence_threshold: Optional[float] = None
49
+ enable_tracking: bool = False
50
+ enable_counting: bool = False
51
+ enable_analytics: bool = False
52
+
53
+ # Performance tracking
54
+ processing_start: float = field(default_factory=time.time)
55
+ processing_time: Optional[float] = None
56
+
57
+ # Added for latency measurement
58
+ processing_latency_ms: Optional[float] = None
59
+ fps: Optional[float] = None
60
+
61
+ # Additional context
62
+ metadata: Dict[str, Any] = field(default_factory=dict)
63
+
64
+ def mark_completed(self) -> None:
65
+ """Mark processing as completed and calculate processing time, latency in ms, and fps."""
66
+ self.processing_time = time.time() - self.processing_start
67
+ if self.processing_time is not None:
68
+ # Calculate latency in milliseconds and frames per second (fps)
69
+ self.processing_latency_ms = self.processing_time * 1000.0
70
+ self.fps = (1.0 / self.processing_time) if self.processing_time > 0 else None
71
+ # Log the performance metrics using the module-level logger
72
+ logger.info(
73
+ "Processing completed in %.2f ms (%.2f fps)",
74
+ self.processing_latency_ms,
75
+ self.fps or 0.0,
76
+ )
77
+
78
+
79
+ @dataclass
80
+ class ProcessingResult:
81
+ """Standardized result container for all post-processing operations."""
82
+
83
+ # Core data
84
+ data: Any
85
+ status: ProcessingStatus = ProcessingStatus.SUCCESS
86
+
87
+ # Metadata
88
+ usecase: str = ""
89
+ category: str = ""
90
+ processing_time: float = 0.0
91
+ timestamp: float = field(default_factory=time.time)
92
+
93
+ # Human-readable information
94
+ summary: str = ""
95
+ insights: List[str] = field(default_factory=list)
96
+ warnings: List[str] = field(default_factory=list)
97
+
98
+ # Error information
99
+ error_message: Optional[str] = None
100
+ error_type: Optional[str] = None
101
+ error_details: Dict[str, Any] = field(default_factory=dict)
102
+
103
+ # Additional context
104
+ context: Optional[ProcessingContext] = None
105
+ predictions: List[Dict[str, Any]] = field(default_factory=list)
106
+ metrics: Dict[str, Any] = field(default_factory=dict)
107
+
108
+ def is_success(self) -> bool:
109
+ """Check if processing was successful."""
110
+ return self.status == ProcessingStatus.SUCCESS
111
+
112
+ def add_insight(self, message: str) -> None:
113
+ """Add insight message."""
114
+ self.insights.append(message)
115
+
116
+ def add_warning(self, message: str) -> None:
117
+ """Add warning message."""
118
+ self.warnings.append(message)
119
+ if self.status == ProcessingStatus.SUCCESS:
120
+ self.status = ProcessingStatus.WARNING
121
+
122
+ def set_error(self, message: str, error_type: str = "ProcessingError",
123
+ details: Optional[Dict[str, Any]] = None) -> None:
124
+ """Set error information."""
125
+ self.error_message = message
126
+ self.error_type = error_type
127
+ self.error_details = details or {}
128
+ self.status = ProcessingStatus.ERROR
129
+ self.summary = f"Processing failed: {message}"
130
+
131
+ def to_dict(self) -> Dict[str, Any]:
132
+ """Convert to dictionary for serialization."""
133
+ return {
134
+ "data": self.data,
135
+ "status": self.status.value,
136
+ "usecase": self.usecase,
137
+ "category": self.category,
138
+ "processing_time": self.processing_time,
139
+ "timestamp": self.timestamp,
140
+ "summary": self.summary,
141
+ "insights": self.insights,
142
+ "warnings": self.warnings,
143
+ "error_message": self.error_message,
144
+ "error_type": self.error_type,
145
+ "error_details": self.error_details,
146
+ "predictions": self.predictions,
147
+ "metrics": self.metrics,
148
+ "context": self.context.__dict__ if self.context else None
149
+ }
150
+
151
+
152
+ @runtime_checkable
153
+ class ConfigProtocol(Protocol):
154
+ """Protocol for configuration objects."""
155
+
156
+ def validate(self) -> List[str]:
157
+ """Validate configuration and return list of errors."""
158
+ ...
159
+
160
+ def to_dict(self) -> Dict[str, Any]:
161
+ """Convert to dictionary."""
162
+ ...
163
+
164
+
165
+ @runtime_checkable
166
+ class ProcessorProtocol(Protocol):
167
+ """Protocol for processors."""
168
+
169
+ def process(self, data: Any, config: ConfigProtocol, context: Optional[ProcessingContext] = None) -> ProcessingResult:
170
+ """Process data with given configuration."""
171
+ ...
172
+
173
+
174
+ class BaseProcessor(ABC):
175
+ """Base class for all processors with standardized agg_summary generation."""
176
+
177
+ def __init__(self, name: str):
178
+ """Initialize processor with name."""
179
+ self.name = name
180
+ self.logger = logging.getLogger(f"{__name__}.{name}")
181
+
182
+ @abstractmethod
183
+ def process(self, data: Any, config: ConfigProtocol, context: Optional[ProcessingContext] = None) -> ProcessingResult:
184
+ """Process data with given configuration."""
185
+ pass
186
+
187
+ def create_result(self, data: Any, usecase: str = "", category: str = "",
188
+ context: Optional[ProcessingContext] = None) -> ProcessingResult:
189
+ """Create a successful result."""
190
+ result = ProcessingResult(
191
+ data=data,
192
+ usecase=usecase,
193
+ category=category,
194
+ context=context
195
+ )
196
+
197
+ if context:
198
+ result.processing_time = context.processing_time or 0.0
199
+
200
+ return result
201
+
202
+ def create_error_result(self, message: str, error_type: str = "ProcessingError",
203
+ usecase: str = "", category: str = "",
204
+ context: Optional[ProcessingContext] = None) -> ProcessingResult:
205
+ """Create an error result."""
206
+ result = ProcessingResult(
207
+ data={},
208
+ usecase=usecase,
209
+ category=category,
210
+ context=context
211
+ )
212
+ result.set_error(message, error_type)
213
+
214
+ if context:
215
+ result.processing_time = context.processing_time or 0.0
216
+
217
+ return result
218
+
219
+ # ===============================================================================
220
+ # STANDARDIZED AGG_SUMMARY STRUCTURE METHODS
221
+ # ===============================================================================
222
+
223
+ def get_high_precision_timestamp(self) -> str:
224
+ """Get high precision timestamp with microsecond granularity."""
225
+ return datetime.now(timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC")
226
+
227
+ def get_standard_timestamp(self) -> str:
228
+ """Get standard timestamp without microseconds."""
229
+ return datetime.now(timezone.utc).strftime("%Y-%m-%d-%H:%M:%S UTC")
230
+
231
+ def get_default_camera_info(self) -> Dict[str, str]:
232
+ """Get default camera info structure."""
233
+ return {
234
+ "camera_name": "camera_1",
235
+ "camera_group": "camera_group_1",
236
+ "location": "Location TBD"
237
+ }
238
+
239
+ def get_default_level_settings(self) -> Dict[str, int]:
240
+ """Get default severity level settings."""
241
+ return {
242
+ "low": 1,
243
+ "medium": 3,
244
+ "significant": 4,
245
+ "critical": 7
246
+ }
247
+
248
+ def get_default_reset_settings(self) -> List[Dict[str, Any]]:
249
+ """Get default reset settings."""
250
+ return [
251
+ {
252
+ "interval_type": "daily",
253
+ "reset_time": {
254
+ "value": 9,
255
+ "time_unit": "hour"
256
+ }
257
+ }
258
+ ]
259
+
260
+ def get_camera_info_from_stream(self, stream_info: Optional[Dict[str, Any]] = None,
261
+ camera_info: Optional[Dict[str, Any]] = None) -> Dict[str, str]:
262
+ """Extract camera info from stream_info or use provided camera_info."""
263
+ default_camera_info = self.get_default_camera_info()
264
+
265
+ if camera_info:
266
+ result = default_camera_info.copy()
267
+ result.update(camera_info)
268
+ return result
269
+
270
+ if stream_info and stream_info.get("camera_info"):
271
+ result = default_camera_info.copy()
272
+ result.update(stream_info["camera_info"])
273
+ return result
274
+
275
+ return default_camera_info
276
+
277
+ def create_incident(self, incident_id: str, incident_type: str, severity_level: str,
278
+ human_text: str = "", camera_info: Optional[Dict[str, Any]] = None,
279
+ alerts: Optional[List[Dict]] = None, alert_settings: Optional[List[Dict]] = None,
280
+ start_time: Optional[str] = None, end_time: Optional[str] = None,
281
+ level_settings: Optional[Dict[str, int]] = None) -> Dict[str, Any]:
282
+ """Create a standardized incident object following the agg_summary format."""
283
+ timestamp = start_time or self.get_high_precision_timestamp()
284
+
285
+ return {
286
+ "incident_id": incident_id,
287
+ "incident_type": incident_type,
288
+ "severity_level": severity_level,
289
+ "human_text": human_text or f"{incident_type} detected @ {timestamp} [Severity Level: {severity_level}]",
290
+ "start_time": timestamp,
291
+ "end_time": end_time or timestamp,
292
+ "camera_info": camera_info or self.get_default_camera_info(),
293
+ "level_settings": level_settings or self.get_default_level_settings(),
294
+ "alerts": alerts or [],
295
+ "alert_settings": alert_settings or []
296
+ }
297
+
298
+ def create_tracking_stats(self, total_counts: List[Dict[str, Any]], current_counts: List[Dict[str, Any]],
299
+ detections: List[Dict[str, Any]], human_text: str,
300
+ camera_info: Optional[Dict[str, Any]] = None,
301
+ alerts: Optional[List[Dict]] = None, alert_settings: Optional[List[Dict]] = None,
302
+ reset_settings: Optional[List[Dict]] = None, start_time: Optional[str] = None,
303
+ reset_time: Optional[str] = None) -> Dict[str, Any]:
304
+ """Create standardized tracking stats object following the agg_summary format."""
305
+ start_timestamp = start_time or self.get_high_precision_timestamp()
306
+ reset_timestamp = reset_time or start_timestamp
307
+
308
+ return {
309
+ "input_timestamp": start_timestamp,
310
+ "reset_timestamp": reset_timestamp,
311
+ "camera_info": camera_info or self.get_default_camera_info(),
312
+ "total_counts": total_counts,
313
+ "current_counts": current_counts,
314
+ "detections": detections,
315
+ "alerts": alerts or [],
316
+ "alert_settings": alert_settings or [],
317
+ "reset_settings": reset_settings or self.get_default_reset_settings(),
318
+ "human_text": human_text
319
+ }
320
+
321
+ def create_business_analytics(self, analysis_name: str, statistics: Dict[str, Any],
322
+ human_text: str, camera_info: Optional[Dict[str, Any]] = None,
323
+ alerts: Optional[List[Dict]] = None, alert_settings: Optional[List[Dict]] = None,
324
+ reset_settings: Optional[List[Dict]] = None, start_time: Optional[str] = None,
325
+ reset_time: Optional[str] = None) -> Dict[str, Any]:
326
+ """Create standardized business analytics object following the agg_summary format."""
327
+ start_timestamp = start_time or self.get_high_precision_timestamp()
328
+ reset_timestamp = reset_time or start_timestamp
329
+
330
+ return {
331
+ "analysis_name": analysis_name,
332
+ "input_timestamp": start_timestamp,
333
+ "reset_timestamp": reset_timestamp,
334
+ "camera_info": camera_info or self.get_default_camera_info(),
335
+ "statistics": statistics,
336
+ "alerts": alerts or [],
337
+ "alert_settings": alert_settings or [],
338
+ "reset_settings": reset_settings or self.get_default_reset_settings(),
339
+ "human_text": human_text
340
+ }
341
+
342
+ def create_agg_summary(self, frame_id: Union[str, int], incidents: Optional[List[Dict]] = None,
343
+ tracking_stats: Optional[List[Dict]] = None, business_analytics: Optional[List[Dict]] = None,
344
+ alerts: Optional[List[Dict]] = None, human_text: str = "") -> Dict[str, Any]:
345
+ """Create standardized agg_summary structure following the expected format."""
346
+ frame_key = str(frame_id)
347
+
348
+ return {
349
+ frame_key: {
350
+ "incidents": incidents or {},
351
+ "tracking_stats": tracking_stats or {},
352
+ "business_analytics": business_analytics or {},
353
+ "alerts": alerts or [],
354
+ "human_text": human_text
355
+ }
356
+ }
357
+
358
+ def create_detection_object(self, category: str, bounding_box: Dict[str, Any],
359
+ confidence: Optional[float] = None, segmentation: Optional[List] = None,
360
+ track_id: Optional[Any] = None,plate_text: Optional[str] = None) -> Dict[str, Any]:
361
+ """Create a standardized detection object for tracking stats."""
362
+ detection = {
363
+ "category": category,
364
+ "bounding_box": bounding_box
365
+ }
366
+ if plate_text:
367
+ detection["category"] = plate_text
368
+
369
+ if segmentation is not None:
370
+ detection["segmentation"] = segmentation
371
+
372
+ # Note: confidence and track_id are typically excluded from agg_summary detections
373
+ # but can be included if needed for specific use cases
374
+
375
+ return detection
376
+
377
+ def create_count_object(self, category: str, count: int) -> Dict[str, Any]:
378
+ """Create a standardized count object for total_counts and current_counts."""
379
+ return {
380
+ "category": category,
381
+ "count": count
382
+ }
383
+
384
+ def create_alert_object(self, alert_type: str, alert_id: str, incident_category: str,
385
+ threshold_value: float, ascending: bool = True,
386
+ settings: Optional[Dict] = None) -> Dict[str, Any]:
387
+ """Create a standardized alert object."""
388
+ return {
389
+ "alert_type": alert_type,
390
+ "alert_id": alert_id,
391
+ "incident_category": incident_category,
392
+ "threshold_value": threshold_value,
393
+ "ascending": ascending,
394
+ "settings": settings or {}
395
+ }
396
+
397
+ def determine_severity_level(self, count: int, threshold_low: int = 3,
398
+ threshold_medium: int = 7, threshold_critical: int = 15) -> str:
399
+ """Determine severity level based on count and thresholds."""
400
+ if count >= threshold_critical:
401
+ return "critical"
402
+ elif count >= threshold_medium:
403
+ return "significant"
404
+ elif count >= threshold_low:
405
+ return "medium"
406
+ else:
407
+ return "low"
408
+
409
+ def generate_tracking_human_text(self, current_counts: Dict[str, int], total_counts: Dict[str, int],
410
+ current_timestamp: str, reset_timestamp: str,
411
+ alerts_summary: str = "None") -> str:
412
+ """Generate standardized human text for tracking stats."""
413
+ lines = ["Tracking Statistics:"]
414
+ lines.append(f"CURRENT FRAME @ {current_timestamp}")
415
+
416
+ for category, count in current_counts.items():
417
+ lines.append(f"\t{category}: {count}")
418
+
419
+ lines.append(f"TOTAL SINCE {reset_timestamp}")
420
+ for category, count in total_counts.items():
421
+ lines.append(f"\t{category}: {count}")
422
+
423
+ lines.append(f"Alerts: {alerts_summary}")
424
+
425
+ return "\n".join(lines)
426
+
427
+ def generate_analytics_human_text(self, analysis_name: str, statistics: Dict[str, Any],
428
+ current_timestamp: str, reset_timestamp: str,
429
+ alerts_summary: str = "None") -> str:
430
+ """Generate standardized human text for business analytics."""
431
+ lines = ["Analytics Statistics:"]
432
+ lines.append(f"CURRENT FRAME @ {current_timestamp}")
433
+
434
+ for key, value in statistics.items():
435
+ lines.append(f"\t{key}: {value}")
436
+
437
+ lines.append(f"TOTAL SINCE {reset_timestamp}")
438
+ for key, value in statistics.items():
439
+ lines.append(f"\t{key}: {value}")
440
+
441
+ lines.append(f"Alerts: {alerts_summary}")
442
+
443
+ return "\n".join(lines)
444
+
445
+ def detect_frame_structure(self, data: Any) -> bool:
446
+ """Detect if data has frame-based structure (multi-frame) or single frame."""
447
+ if isinstance(data, dict):
448
+ # Check if all keys are numeric (frame IDs) and values are lists
449
+ return all(
450
+ isinstance(k, (str, int)) for k in data.keys()
451
+ if str(k).isdigit()
452
+ )
453
+ return False
454
+
455
+ def extract_frame_ids(self, data: Any) -> List[str]:
456
+ """Extract frame IDs from frame-based data structure."""
457
+ if isinstance(data, dict):
458
+ return [str(k) for k in data.keys() if str(k).isdigit() or k.startswith('frame')]
459
+ return ["current_frame"]
460
+
461
+ def create_frame_wise_agg_summary(self, frame_incidents: Dict[str, List[Dict]],
462
+ frame_tracking_stats: Dict[str, List[Dict]],
463
+ frame_business_analytics: Dict[str, List[Dict]],
464
+ frame_alerts: Dict[str, List[Dict]] = None,
465
+ frame_human_text: Dict[str, str] = None) -> Dict[str, Any]:
466
+ """Create frame-wise agg_summary structure for multiple frames."""
467
+ agg_summary = {}
468
+
469
+ # Get all frame IDs from all sources
470
+ all_frame_ids = set()
471
+ all_frame_ids.update(frame_incidents.keys())
472
+ all_frame_ids.update(frame_tracking_stats.keys())
473
+ all_frame_ids.update(frame_business_analytics.keys())
474
+ if frame_alerts:
475
+ all_frame_ids.update(frame_alerts.keys())
476
+
477
+ for frame_id in all_frame_ids:
478
+ agg_summary[str(frame_id)] = {
479
+ "incidents": frame_incidents.get(frame_id, {}),
480
+ "tracking_stats": frame_tracking_stats.get(frame_id, {}),
481
+ "business_analytics": frame_business_analytics.get(frame_id, {}),
482
+ "alerts": frame_alerts.get(frame_id, []) if frame_alerts else [],
483
+ "human_text": frame_human_text.get(frame_id, "") if frame_human_text else ""
484
+ }
485
+
486
+ return agg_summary
487
+
488
+ # ===============================================================================
489
+ # LEGACY HELPER METHODS (DEPRECATED - USE NEW STANDARDIZED METHODS ABOVE)
490
+ # ===============================================================================
491
+
492
+ def create_structured_event(self, event_type: str, level: str, intensity: float,
493
+ application_name: str, location_info: str = None,
494
+ additional_info: str = "", application_version: str = "1.0") -> Dict:
495
+ """Create a structured event in the required format."""
496
+ from datetime import datetime, timezone
497
+
498
+ return {
499
+ "type": event_type,
500
+ "stream_time": datetime.now(timezone.utc).strftime("%Y-%m-%d-%H:%M:%S UTC"),
501
+ "level": level,
502
+ "intensity": round(intensity, 1),
503
+ "config": {
504
+ "min_value": 0,
505
+ "max_value": 10,
506
+ "level_settings": {"info": 2, "warning": 5, "critical": 7}
507
+ },
508
+ "application_name": application_name,
509
+ "application_version": application_version,
510
+ "location_info": location_info,
511
+ "human_text": f"Event: {event_type.replace('_', ' ').title()}\nLevel: {level.title()}\nTime: {datetime.now(timezone.utc).strftime('%Y-%m-%d-%H:%M:%S UTC')}\n{additional_info}"
512
+ }
513
+
514
+ def create_structured_tracking_stats(self, results_data: Dict, human_text: str) -> Dict:
515
+ """Create structured tracking stats in the required format."""
516
+ from datetime import datetime, timezone
517
+
518
+ return {
519
+ "input_timestamp": datetime.now(timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC"),
520
+ "reset_timestamp": datetime.now(timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC"),
521
+ "camera_info": None, # Should be populated from stream info
522
+ "total_counts": [],
523
+ "current_counts": [],
524
+ "detections": [],
525
+ "alerts": [],
526
+ "alert_settings": [],
527
+ "reset_settings": [
528
+ {
529
+ "interval_type": "daily",
530
+ "reset_time": {
531
+ "value": 9,
532
+ "time_unit": "hour"
533
+ }
534
+ }
535
+ ],
536
+ "human_text": human_text
537
+ }
538
+
539
+ def determine_event_level_and_intensity(self, count: int, threshold: int = 10) -> tuple:
540
+ """Determine event level and intensity based on count and threshold."""
541
+ if threshold > 0:
542
+ intensity = min(10.0, (count / threshold) * 10)
543
+ else:
544
+ intensity = min(10.0, count / 2.0)
545
+
546
+ if intensity >= 7:
547
+ level = "critical"
548
+ elif intensity >= 5:
549
+ level = "warning"
550
+ else:
551
+ level = "info"
552
+
553
+ return level, intensity
554
+
555
+ def create_agg_summary_for_frame(self, frame_number: Union[int, str],
556
+ incidents: List[Dict] = None,
557
+ tracking_stats: List[Dict] = None,
558
+ business_analytics: List[Dict] = None,
559
+ alerts: List[Dict] = None,
560
+ human_text: str = "") -> Dict[str, Any]:
561
+ """Create agg_summary structure for a specific frame matching the expected format."""
562
+ frame_key = str(frame_number)
563
+
564
+ return {
565
+ frame_key: {
566
+ "incidents": incidents or [],
567
+ "tracking_stats": tracking_stats or [],
568
+ "business_analytics": business_analytics or [],
569
+ "alerts": alerts or [],
570
+ "human_text": human_text
571
+ }
572
+ }
573
+
574
+ def create_structured_incident(self, incident_id: str, incident_type: str,
575
+ severity_level: str, start_time: str = None,
576
+ end_time: str = None, camera_info: Dict = None,
577
+ alerts: List[Dict] = None,
578
+ alert_settings: List[Dict] = None) -> Dict:
579
+ """Create a structured incident in the required format."""
580
+ from datetime import datetime, timezone
581
+
582
+ if start_time is None:
583
+ start_time = datetime.now(timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC")
584
+ if end_time is None:
585
+ end_time = start_time
586
+
587
+ return {
588
+ "incident_id": incident_id,
589
+ "incident_type": incident_type,
590
+ "severity_level": severity_level,
591
+ "human_text": f"{incident_type} detected @ {start_time} [Severity Level: {severity_level}]",
592
+ "start_time": start_time,
593
+ "end_time": end_time,
594
+ "camera_info": camera_info or {
595
+ "camera_name": "No Camera Name",
596
+ "camera_group": "No Camera Group",
597
+ "location": "No Location"
598
+ },
599
+ "level_settings": {
600
+ "low": 1,
601
+ "medium": 3,
602
+ "significant": 4,
603
+ "critical": 7
604
+ },
605
+ "alerts": alerts or [],
606
+ "alert_settings": alert_settings or []
607
+ }
608
+
609
+ def create_structured_business_analytics(self, analysis_name: str, statistics: Dict,
610
+ camera_info: Dict = None, alerts: List[Dict] = None,
611
+ alert_settings: List[Dict] = None) -> Dict:
612
+ """Create structured business analytics in the required format."""
613
+ from datetime import datetime, timezone
614
+
615
+ timestamp = datetime.now(timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC")
616
+
617
+ return {
618
+ "analysis_name": analysis_name,
619
+ "input_timestamp": timestamp,
620
+ "reset_timestamp": timestamp,
621
+ "camera_info": camera_info or {
622
+ "camera_name": "No Camera Name",
623
+ "camera_group": "No Camera Group",
624
+ "location": "No Location"
625
+ },
626
+ "statistics": statistics,
627
+ "alerts": alerts or [],
628
+ "alert_settings": alert_settings or [],
629
+ "reset_settings": [
630
+ {
631
+ "interval_type": "daily",
632
+ "reset_time": {
633
+ "value": 9,
634
+ "time_unit": "hour"
635
+ }
636
+ }
637
+ ],
638
+ "human_text": f"Analytics Statistics:\nCURRENT FRAME @ {timestamp}\n\t{analysis_name}: {statistics}\nTOTAL SINCE {timestamp}\n\t{analysis_name}: {statistics}\nAlerts: None"
639
+ }
640
+
641
+
642
+ class BaseUseCase(ABC):
643
+ """Base class for all use cases."""
644
+
645
+ def __init__(self, name: str, category: str):
646
+ """Initialize use case with name and category."""
647
+ self.name = name
648
+ self.category = category
649
+ self.logger = logging.getLogger(f"{__name__}.{name}")
650
+
651
+ @abstractmethod
652
+ def get_config_schema(self) -> Dict[str, Any]:
653
+ """Get JSON schema for configuration validation."""
654
+ pass
655
+
656
+ @abstractmethod
657
+ def create_default_config(self, **overrides) -> ConfigProtocol:
658
+ """Create default configuration with optional overrides."""
659
+ pass
660
+
661
+ def validate_config(self, config: ConfigProtocol) -> List[str]:
662
+ """Validate configuration for this use case."""
663
+ return config.validate()
664
+
665
+
666
+ class ProcessorRegistry:
667
+ """Registry for processors and use cases."""
668
+
669
+ def __init__(self):
670
+ """Initialize registry."""
671
+ self._processors: Dict[str, Type[BaseProcessor]] = {}
672
+ self._use_cases: Dict[str, Dict[str, Type[BaseUseCase]]] = {}
673
+
674
+ def register_processor(self, name: str, processor_class: Type[BaseProcessor]) -> None:
675
+ """Register a processor class."""
676
+ self._processors[name] = processor_class
677
+ logger.debug(f"Registered processor: {name}")
678
+
679
+ def register_use_case(self, category: str, name: str, use_case_class: Type[BaseUseCase]) -> None:
680
+ """Register a use case class."""
681
+ if category not in self._use_cases:
682
+ self._use_cases[category] = {}
683
+ self._use_cases[category][name] = use_case_class
684
+ logger.debug(f"Registered use case: {category}/{name}")
685
+
686
+ def get_processor(self, name: str) -> Optional[Type[BaseProcessor]]:
687
+ """Get processor class by name."""
688
+ return self._processors.get(name)
689
+
690
+ def get_use_case(self, category: str, name: str) -> Optional[Type[BaseUseCase]]:
691
+ """Get use case class by category and name."""
692
+ return self._use_cases.get(category, {}).get(name)
693
+
694
+ def list_processors(self) -> List[str]:
695
+ """List all registered processors."""
696
+ return list(self._processors.keys())
697
+
698
+ def list_use_cases(self) -> Dict[str, List[str]]:
699
+ """List all registered use cases by category."""
700
+ return {category: list(use_cases.keys()) for category, use_cases in self._use_cases.items()}
701
+
702
+
703
+ # Global registry instance
704
+ registry = ProcessorRegistry()