matrice-analytics 0.1.60__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- matrice_analytics/__init__.py +28 -0
- matrice_analytics/boundary_drawing_internal/README.md +305 -0
- matrice_analytics/boundary_drawing_internal/__init__.py +45 -0
- matrice_analytics/boundary_drawing_internal/boundary_drawing_internal.py +1207 -0
- matrice_analytics/boundary_drawing_internal/boundary_drawing_tool.py +429 -0
- matrice_analytics/boundary_drawing_internal/boundary_tool_template.html +1036 -0
- matrice_analytics/boundary_drawing_internal/data/.gitignore +12 -0
- matrice_analytics/boundary_drawing_internal/example_usage.py +206 -0
- matrice_analytics/boundary_drawing_internal/usage/README.md +110 -0
- matrice_analytics/boundary_drawing_internal/usage/boundary_drawer_launcher.py +102 -0
- matrice_analytics/boundary_drawing_internal/usage/simple_boundary_launcher.py +107 -0
- matrice_analytics/post_processing/README.md +455 -0
- matrice_analytics/post_processing/__init__.py +732 -0
- matrice_analytics/post_processing/advanced_tracker/README.md +650 -0
- matrice_analytics/post_processing/advanced_tracker/__init__.py +17 -0
- matrice_analytics/post_processing/advanced_tracker/base.py +99 -0
- matrice_analytics/post_processing/advanced_tracker/config.py +77 -0
- matrice_analytics/post_processing/advanced_tracker/kalman_filter.py +370 -0
- matrice_analytics/post_processing/advanced_tracker/matching.py +195 -0
- matrice_analytics/post_processing/advanced_tracker/strack.py +230 -0
- matrice_analytics/post_processing/advanced_tracker/tracker.py +367 -0
- matrice_analytics/post_processing/config.py +146 -0
- matrice_analytics/post_processing/core/__init__.py +63 -0
- matrice_analytics/post_processing/core/base.py +704 -0
- matrice_analytics/post_processing/core/config.py +3291 -0
- matrice_analytics/post_processing/core/config_utils.py +925 -0
- matrice_analytics/post_processing/face_reg/__init__.py +43 -0
- matrice_analytics/post_processing/face_reg/compare_similarity.py +556 -0
- matrice_analytics/post_processing/face_reg/embedding_manager.py +950 -0
- matrice_analytics/post_processing/face_reg/face_recognition.py +2234 -0
- matrice_analytics/post_processing/face_reg/face_recognition_client.py +606 -0
- matrice_analytics/post_processing/face_reg/people_activity_logging.py +321 -0
- matrice_analytics/post_processing/ocr/__init__.py +0 -0
- matrice_analytics/post_processing/ocr/easyocr_extractor.py +250 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/__init__.py +9 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/__init__.py +4 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/cli.py +33 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/dataset_stats.py +139 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/export.py +398 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/train.py +447 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/utils.py +129 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/valid.py +93 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/validate_dataset.py +240 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/visualize_augmentation.py +176 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/visualize_predictions.py +96 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/core/__init__.py +3 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/core/process.py +246 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/core/types.py +60 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/core/utils.py +87 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/inference/__init__.py +3 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/inference/config.py +82 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/inference/hub.py +141 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/inference/plate_recognizer.py +323 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/py.typed +0 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/__init__.py +0 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/data/__init__.py +0 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/data/augmentation.py +101 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/data/dataset.py +97 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/__init__.py +0 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/config.py +114 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/layers.py +553 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/loss.py +55 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/metric.py +86 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/model_builders.py +95 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/model_schema.py +395 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/utilities/__init__.py +0 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/utilities/backend_utils.py +38 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/utilities/utils.py +214 -0
- matrice_analytics/post_processing/ocr/postprocessing.py +270 -0
- matrice_analytics/post_processing/ocr/preprocessing.py +52 -0
- matrice_analytics/post_processing/post_processor.py +1175 -0
- matrice_analytics/post_processing/test_cases/__init__.py +1 -0
- matrice_analytics/post_processing/test_cases/run_tests.py +143 -0
- matrice_analytics/post_processing/test_cases/test_advanced_customer_service.py +841 -0
- matrice_analytics/post_processing/test_cases/test_basic_counting_tracking.py +523 -0
- matrice_analytics/post_processing/test_cases/test_comprehensive.py +531 -0
- matrice_analytics/post_processing/test_cases/test_config.py +852 -0
- matrice_analytics/post_processing/test_cases/test_customer_service.py +585 -0
- matrice_analytics/post_processing/test_cases/test_data_generators.py +583 -0
- matrice_analytics/post_processing/test_cases/test_people_counting.py +510 -0
- matrice_analytics/post_processing/test_cases/test_processor.py +524 -0
- matrice_analytics/post_processing/test_cases/test_usecases.py +165 -0
- matrice_analytics/post_processing/test_cases/test_utilities.py +356 -0
- matrice_analytics/post_processing/test_cases/test_utils.py +743 -0
- matrice_analytics/post_processing/usecases/Histopathological_Cancer_Detection_img.py +604 -0
- matrice_analytics/post_processing/usecases/__init__.py +267 -0
- matrice_analytics/post_processing/usecases/abandoned_object_detection.py +797 -0
- matrice_analytics/post_processing/usecases/advanced_customer_service.py +1601 -0
- matrice_analytics/post_processing/usecases/age_detection.py +842 -0
- matrice_analytics/post_processing/usecases/age_gender_detection.py +1085 -0
- matrice_analytics/post_processing/usecases/anti_spoofing_detection.py +656 -0
- matrice_analytics/post_processing/usecases/assembly_line_detection.py +841 -0
- matrice_analytics/post_processing/usecases/banana_defect_detection.py +624 -0
- matrice_analytics/post_processing/usecases/basic_counting_tracking.py +667 -0
- matrice_analytics/post_processing/usecases/blood_cancer_detection_img.py +881 -0
- matrice_analytics/post_processing/usecases/car_damage_detection.py +834 -0
- matrice_analytics/post_processing/usecases/car_part_segmentation.py +946 -0
- matrice_analytics/post_processing/usecases/car_service.py +1601 -0
- matrice_analytics/post_processing/usecases/cardiomegaly_classification.py +864 -0
- matrice_analytics/post_processing/usecases/cell_microscopy_segmentation.py +897 -0
- matrice_analytics/post_processing/usecases/chicken_pose_detection.py +648 -0
- matrice_analytics/post_processing/usecases/child_monitoring.py +814 -0
- matrice_analytics/post_processing/usecases/color/clip.py +660 -0
- matrice_analytics/post_processing/usecases/color/clip_processor/merges.txt +48895 -0
- matrice_analytics/post_processing/usecases/color/clip_processor/preprocessor_config.json +28 -0
- matrice_analytics/post_processing/usecases/color/clip_processor/special_tokens_map.json +30 -0
- matrice_analytics/post_processing/usecases/color/clip_processor/tokenizer.json +245079 -0
- matrice_analytics/post_processing/usecases/color/clip_processor/tokenizer_config.json +32 -0
- matrice_analytics/post_processing/usecases/color/clip_processor/vocab.json +1 -0
- matrice_analytics/post_processing/usecases/color/color_map_utils.py +70 -0
- matrice_analytics/post_processing/usecases/color/color_mapper.py +468 -0
- matrice_analytics/post_processing/usecases/color_detection.py +1936 -0
- matrice_analytics/post_processing/usecases/color_map_utils.py +70 -0
- matrice_analytics/post_processing/usecases/concrete_crack_detection.py +827 -0
- matrice_analytics/post_processing/usecases/crop_weed_detection.py +781 -0
- matrice_analytics/post_processing/usecases/customer_service.py +1008 -0
- matrice_analytics/post_processing/usecases/defect_detection_products.py +936 -0
- matrice_analytics/post_processing/usecases/distracted_driver_detection.py +822 -0
- matrice_analytics/post_processing/usecases/drone_traffic_monitoring.py +585 -0
- matrice_analytics/post_processing/usecases/drowsy_driver_detection.py +829 -0
- matrice_analytics/post_processing/usecases/dwell_detection.py +829 -0
- matrice_analytics/post_processing/usecases/emergency_vehicle_detection.py +827 -0
- matrice_analytics/post_processing/usecases/face_emotion.py +813 -0
- matrice_analytics/post_processing/usecases/face_recognition.py +827 -0
- matrice_analytics/post_processing/usecases/fashion_detection.py +835 -0
- matrice_analytics/post_processing/usecases/field_mapping.py +902 -0
- matrice_analytics/post_processing/usecases/fire_detection.py +1146 -0
- matrice_analytics/post_processing/usecases/flare_analysis.py +836 -0
- matrice_analytics/post_processing/usecases/flower_segmentation.py +1006 -0
- matrice_analytics/post_processing/usecases/gas_leak_detection.py +837 -0
- matrice_analytics/post_processing/usecases/gender_detection.py +832 -0
- matrice_analytics/post_processing/usecases/human_activity_recognition.py +871 -0
- matrice_analytics/post_processing/usecases/intrusion_detection.py +1672 -0
- matrice_analytics/post_processing/usecases/leaf.py +821 -0
- matrice_analytics/post_processing/usecases/leaf_disease.py +840 -0
- matrice_analytics/post_processing/usecases/leak_detection.py +837 -0
- matrice_analytics/post_processing/usecases/license_plate_detection.py +1188 -0
- matrice_analytics/post_processing/usecases/license_plate_monitoring.py +1781 -0
- matrice_analytics/post_processing/usecases/litter_monitoring.py +717 -0
- matrice_analytics/post_processing/usecases/mask_detection.py +869 -0
- matrice_analytics/post_processing/usecases/natural_disaster.py +907 -0
- matrice_analytics/post_processing/usecases/parking.py +787 -0
- matrice_analytics/post_processing/usecases/parking_space_detection.py +822 -0
- matrice_analytics/post_processing/usecases/pcb_defect_detection.py +888 -0
- matrice_analytics/post_processing/usecases/pedestrian_detection.py +808 -0
- matrice_analytics/post_processing/usecases/people_counting.py +706 -0
- matrice_analytics/post_processing/usecases/people_counting_bckp.py +1683 -0
- matrice_analytics/post_processing/usecases/people_tracking.py +1842 -0
- matrice_analytics/post_processing/usecases/pipeline_detection.py +605 -0
- matrice_analytics/post_processing/usecases/plaque_segmentation_img.py +874 -0
- matrice_analytics/post_processing/usecases/pothole_segmentation.py +915 -0
- matrice_analytics/post_processing/usecases/ppe_compliance.py +645 -0
- matrice_analytics/post_processing/usecases/price_tag_detection.py +822 -0
- matrice_analytics/post_processing/usecases/proximity_detection.py +1901 -0
- matrice_analytics/post_processing/usecases/road_lane_detection.py +623 -0
- matrice_analytics/post_processing/usecases/road_traffic_density.py +832 -0
- matrice_analytics/post_processing/usecases/road_view_segmentation.py +915 -0
- matrice_analytics/post_processing/usecases/shelf_inventory_detection.py +583 -0
- matrice_analytics/post_processing/usecases/shoplifting_detection.py +822 -0
- matrice_analytics/post_processing/usecases/shopping_cart_analysis.py +899 -0
- matrice_analytics/post_processing/usecases/skin_cancer_classification_img.py +864 -0
- matrice_analytics/post_processing/usecases/smoker_detection.py +833 -0
- matrice_analytics/post_processing/usecases/solar_panel.py +810 -0
- matrice_analytics/post_processing/usecases/suspicious_activity_detection.py +1030 -0
- matrice_analytics/post_processing/usecases/template_usecase.py +380 -0
- matrice_analytics/post_processing/usecases/theft_detection.py +648 -0
- matrice_analytics/post_processing/usecases/traffic_sign_monitoring.py +724 -0
- matrice_analytics/post_processing/usecases/underground_pipeline_defect_detection.py +775 -0
- matrice_analytics/post_processing/usecases/underwater_pollution_detection.py +842 -0
- matrice_analytics/post_processing/usecases/vehicle_monitoring.py +1029 -0
- matrice_analytics/post_processing/usecases/warehouse_object_segmentation.py +899 -0
- matrice_analytics/post_processing/usecases/waterbody_segmentation.py +923 -0
- matrice_analytics/post_processing/usecases/weapon_detection.py +771 -0
- matrice_analytics/post_processing/usecases/weld_defect_detection.py +615 -0
- matrice_analytics/post_processing/usecases/wildlife_monitoring.py +898 -0
- matrice_analytics/post_processing/usecases/windmill_maintenance.py +834 -0
- matrice_analytics/post_processing/usecases/wound_segmentation.py +856 -0
- matrice_analytics/post_processing/utils/__init__.py +150 -0
- matrice_analytics/post_processing/utils/advanced_counting_utils.py +400 -0
- matrice_analytics/post_processing/utils/advanced_helper_utils.py +317 -0
- matrice_analytics/post_processing/utils/advanced_tracking_utils.py +461 -0
- matrice_analytics/post_processing/utils/alerting_utils.py +213 -0
- matrice_analytics/post_processing/utils/category_mapping_utils.py +94 -0
- matrice_analytics/post_processing/utils/color_utils.py +592 -0
- matrice_analytics/post_processing/utils/counting_utils.py +182 -0
- matrice_analytics/post_processing/utils/filter_utils.py +261 -0
- matrice_analytics/post_processing/utils/format_utils.py +293 -0
- matrice_analytics/post_processing/utils/geometry_utils.py +300 -0
- matrice_analytics/post_processing/utils/smoothing_utils.py +358 -0
- matrice_analytics/post_processing/utils/tracking_utils.py +234 -0
- matrice_analytics/py.typed +0 -0
- matrice_analytics-0.1.60.dist-info/METADATA +481 -0
- matrice_analytics-0.1.60.dist-info/RECORD +196 -0
- matrice_analytics-0.1.60.dist-info/WHEEL +5 -0
- matrice_analytics-0.1.60.dist-info/licenses/LICENSE.txt +21 -0
- matrice_analytics-0.1.60.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,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()
|