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,585 @@
1
+ from typing import Any, Dict, List, Optional
2
+ from dataclasses import asdict
3
+ import time
4
+ from datetime import datetime, timezone
5
+
6
+ from ..core.base import BaseProcessor, ProcessingContext, ProcessingResult, ConfigProtocol, ResultFormat
7
+ from ..utils import (
8
+ filter_by_confidence,
9
+ filter_by_categories,
10
+ apply_category_mapping,
11
+ count_objects_by_category,
12
+ count_objects_in_zones,
13
+ calculate_counting_summary,
14
+ match_results_structure,
15
+ bbox_smoothing,
16
+ BBoxSmoothingConfig,
17
+ BBoxSmoothingTracker
18
+ )
19
+ from dataclasses import dataclass, field
20
+ from ..core.config import BaseConfig, AlertConfig, ZoneConfig
21
+
22
+ @dataclass
23
+ class VehiclePeopleDroneMonitoringConfig(BaseConfig):
24
+ """Configuration for vehicle detection use case in vehicle monitoring."""
25
+ enable_smoothing: bool = True
26
+ smoothing_algorithm: str = "observability"
27
+ smoothing_window_size: int = 20
28
+ smoothing_cooldown_frames: int = 5
29
+ smoothing_confidence_range_factor: float = 0.5
30
+ confidence_threshold: float = 0.6
31
+ usecase_categories: List[str] = field(
32
+ default_factory=lambda: [
33
+ "pedestrian", "people", "bicycle", "car", "van", "truck", "tricycle", "awning-tricycle", "bus", "motor"
34
+ ]
35
+ )
36
+ target_categories: List[str] = field(
37
+ default_factory=lambda: [
38
+ "pedestrian", "people", "bicycle", "car", "van", "truck", "tricycle", "awning-tricycle", "bus", "motor"]
39
+ )
40
+ alert_config: Optional[AlertConfig] = None
41
+ index_to_category: Optional[Dict[int, str]] = field(
42
+ default_factory=lambda: {
43
+ 0: "pedestrian", 1: "people", 2: "bicycle", 3: "car", 4: "van", 5: "truck",
44
+ 6: "tricycle", 7: "awning-tricycle", 8: "bus", 9: "motor"
45
+ }
46
+ )
47
+
48
+ class DroneTrafficMonitoringUsecase(BaseProcessor):
49
+ CATEGORY_DISPLAY = {
50
+ "pedestrian": "Pedestrian",
51
+ "people": "People",
52
+ "bicycle": "Bicycle",
53
+ "car": "Car",
54
+ "van": "Van",
55
+ "truck": "Truck",
56
+ "tricycle": "Tricycle",
57
+ "awning-tricycle": "Awning-Tricycle",
58
+ "bus": "Bus",
59
+ "motor": "Motorcycle"
60
+ }
61
+
62
+ def __init__(self):
63
+ super().__init__("drone_traffic_monitoring")
64
+ self.category = "traffic"
65
+ self.CASE_TYPE: Optional[str] = 'drone_traffic_monitoring'
66
+ self.CASE_VERSION: Optional[str] = '1.0'
67
+ self.target_categories = ["pedestrian", "people", "bicycle", "car", "van", "truck", "tricycle", "awning-tricycle", "bus", "motor"]
68
+ self.smoothing_tracker = None
69
+ self.tracker = None
70
+ self._total_frame_counter = 0
71
+ self._global_frame_offset = 0
72
+ self._tracking_start_time = None
73
+ self._track_aliases: Dict[Any, Any] = {}
74
+ self._canonical_tracks: Dict[Any, Dict[str, Any]] = {}
75
+ self._track_merge_iou_threshold: float = 0.05
76
+ self._track_merge_time_window: float = 7.0
77
+ self._ascending_alert_list: List[int] = []
78
+ self.current_incident_end_timestamp: str = "N/A"
79
+
80
+ def process(self, data: Any, config: ConfigProtocol, context: Optional[ProcessingContext] = None,
81
+ stream_info: Optional[Dict[str, Any]] = None) -> ProcessingResult:
82
+ start_time = time.time()
83
+ if not isinstance(config, VehiclePeopleDroneMonitoringConfig):
84
+ return self.create_error_result("Invalid config type", usecase=self.name, category=self.category, context=context)
85
+ if context is None:
86
+ context = ProcessingContext()
87
+ input_format = match_results_structure(data)
88
+ context.input_format = input_format
89
+ context.confidence_threshold = config.confidence_threshold
90
+ if config.confidence_threshold is not None:
91
+ processed_data = filter_by_confidence(data, config.confidence_threshold)
92
+ self.logger.debug(f"Applied confidence filtering with threshold {config.confidence_threshold}")
93
+ else:
94
+ processed_data = data
95
+ self.logger.debug("Did not apply confidence filtering since no threshold provided")
96
+ if config.index_to_category:
97
+ processed_data = apply_category_mapping(processed_data, config.index_to_category)
98
+ self.logger.debug("Applied category mapping")
99
+ if config.target_categories:
100
+ processed_data = [d for d in processed_data if d.get('category') in self.target_categories]
101
+ self.logger.debug("Applied category filtering")
102
+ if config.enable_smoothing:
103
+ if self.smoothing_tracker is None:
104
+ smoothing_config = BBoxSmoothingConfig(
105
+ smoothing_algorithm=config.smoothing_algorithm,
106
+ window_size=config.smoothing_window_size,
107
+ cooldown_frames=config.smoothing_cooldown_frames,
108
+ confidence_threshold=config.confidence_threshold,
109
+ confidence_range_factor=config.smoothing_confidence_range_factor,
110
+ enable_smoothing=True
111
+ )
112
+ self.smoothing_tracker = BBoxSmoothingTracker(smoothing_config)
113
+ processed_data = bbox_smoothing(processed_data, self.smoothing_tracker.config, self.smoothing_tracker)
114
+ try:
115
+ from ..advanced_tracker import AdvancedTracker
116
+ from ..advanced_tracker.config import TrackerConfig
117
+ if self.tracker is None:
118
+ tracker_config = TrackerConfig()
119
+ self.tracker = AdvancedTracker(tracker_config)
120
+ self.logger.info("Initialized AdvancedTracker for Vehicle Monitoring")
121
+ processed_data = self.tracker.update(processed_data)
122
+ except Exception as e:
123
+ self.logger.warning(f"AdvancedTracker failed: {e}")
124
+ self._update_tracking_state(processed_data)
125
+ self._total_frame_counter += 1
126
+ frame_number = None
127
+ if stream_info:
128
+ input_settings = stream_info.get("input_settings", {})
129
+ start_frame = input_settings.get("start_frame")
130
+ end_frame = input_settings.get("end_frame")
131
+ if start_frame is not None and end_frame is not None and start_frame == end_frame:
132
+ frame_number = start_frame
133
+ general_counting_summary = calculate_counting_summary(data)
134
+ counting_summary = self._count_categories(processed_data, config)
135
+ total_counts = self.get_total_counts()
136
+ counting_summary['total_counts'] = total_counts
137
+ alerts = self._check_alerts(counting_summary, frame_number, config)
138
+ predictions = self._extract_predictions(processed_data)
139
+ incidents_list = self._generate_incidents(counting_summary, alerts, config, frame_number, stream_info)
140
+ tracking_stats_list = self._generate_tracking_stats(counting_summary, alerts, config, frame_number, stream_info)
141
+ business_analytics_list = self._generate_business_analytics(counting_summary, alerts, config, stream_info, is_empty=True)
142
+ summary_list = self._generate_summary(counting_summary, incidents_list, tracking_stats_list, business_analytics_list, alerts)
143
+ incidents = incidents_list[0] if incidents_list else {}
144
+ tracking_stats = tracking_stats_list[0] if tracking_stats_list else {}
145
+ business_analytics = business_analytics_list[0] if business_analytics_list else {}
146
+ summary = summary_list[0] if summary_list else {}
147
+ agg_summary = {str(frame_number): {
148
+ "incidents": incidents,
149
+ "tracking_stats": tracking_stats,
150
+ "business_analytics": business_analytics,
151
+ "alerts": alerts,
152
+ "human_text": summary}
153
+ }
154
+ context.mark_completed()
155
+ result = self.create_result(
156
+ data={"agg_summary": agg_summary},
157
+ usecase=self.name,
158
+ category=self.category,
159
+ context=context
160
+ )
161
+ return result
162
+
163
+ def _check_alerts(self, summary: dict, frame_number: Any, config: VehiclePeopleDroneMonitoringConfig) -> List[Dict]:
164
+ def get_trend(data, lookback=900, threshold=0.6):
165
+ window = data[-lookback:] if len(data) >= lookback else data
166
+ if len(window) < 2:
167
+ return True
168
+ increasing = 0
169
+ total = 0
170
+ for i in range(1, len(window)):
171
+ if window[i] >= window[i - 1]:
172
+ increasing += 1
173
+ total += 1
174
+ ratio = increasing / total
175
+ return ratio >= threshold
176
+ frame_key = str(frame_number) if frame_number is not None else "current_frame"
177
+ alerts = []
178
+ total_detections = summary.get("total_count", 0)
179
+ total_counts_dict = summary.get("total_counts", {})
180
+ per_category_count = summary.get("per_category_count", {})
181
+ if not config.alert_config:
182
+ return alerts
183
+ if hasattr(config.alert_config, 'count_thresholds') and config.alert_config.count_thresholds:
184
+ for category, threshold in config.alert_config.count_thresholds.items():
185
+ if category == "all" and total_detections > threshold:
186
+ alerts.append({
187
+ "alert_type": getattr(config.alert_config, 'alert_type', ['Default']),
188
+ "alert_id": f"alert_{category}_{frame_key}",
189
+ "incident_category": self.CASE_TYPE,
190
+ "threshold_level": threshold,
191
+ "ascending": get_trend(self._ascending_alert_list, lookback=900, threshold=0.8),
192
+ "settings": {t: v for t, v in zip(getattr(config.alert_config, 'alert_type', ['Default']),
193
+ getattr(config.alert_config, 'alert_value', ['JSON']))}
194
+ })
195
+ elif category in per_category_count and per_category_count[category] > threshold:
196
+ alerts.append({
197
+ "alert_type": getattr(config.alert_config, 'alert_type', ['Default']),
198
+ "alert_id": f"alert_{category}_{frame_key}",
199
+ "incident_category": self.CASE_TYPE,
200
+ "threshold_level": threshold,
201
+ "ascending": get_trend(self._ascending_alert_list, lookback=900, threshold=0.8),
202
+ "settings": {t: v for t, v in zip(getattr(config.alert_config, 'alert_type', ['Default']),
203
+ getattr(config.alert_config, 'alert_value', ['JSON']))}
204
+ })
205
+ return alerts
206
+
207
+ def _generate_incidents(self, counting_summary: Dict, alerts: List, config: VehiclePeopleDroneMonitoringConfig,
208
+ frame_number: Optional[int] = None, stream_info: Optional[Dict[str, Any]] = None) -> List[Dict]:
209
+ incidents = []
210
+ total_detections = counting_summary.get("total_count", 0)
211
+ current_timestamp = self._get_current_timestamp_str(stream_info)
212
+ camera_info = self.get_camera_info_from_stream(stream_info)
213
+ self._ascending_alert_list = self._ascending_alert_list[-900:] if len(self._ascending_alert_list) > 900 else self._ascending_alert_list
214
+ if total_detections > 0:
215
+ level = "low"
216
+ intensity = 5.0
217
+ start_timestamp = self._get_start_timestamp_str(stream_info)
218
+ if start_timestamp and self.current_incident_end_timestamp == 'N/A':
219
+ self.current_incident_end_timestamp = 'Incident still active'
220
+ elif start_timestamp and self.current_incident_end_timestamp == 'Incident still active':
221
+ if len(self._ascending_alert_list) >= 15 and sum(self._ascending_alert_list[-15:]) / 15 < 1.5:
222
+ self.current_incident_end_timestamp = current_timestamp
223
+ elif self.current_incident_end_timestamp != 'Incident still active' and self.current_incident_end_timestamp != 'N/A':
224
+ self.current_incident_end_timestamp = 'N/A'
225
+ if config.alert_config and config.alert_config.count_thresholds:
226
+ threshold = config.alert_config.count_thresholds.get("all", 15)
227
+ intensity = min(10.0, (total_detections / threshold) * 10)
228
+ if intensity >= 9:
229
+ level = "critical"
230
+ self._ascending_alert_list.append(3)
231
+ elif intensity >= 7:
232
+ level = "significant"
233
+ self._ascending_alert_list.append(2)
234
+ elif intensity >= 5:
235
+ level = "medium"
236
+ self._ascending_alert_list.append(1)
237
+ else:
238
+ level = "low"
239
+ self._ascending_alert_list.append(0)
240
+ else:
241
+ if total_detections > 30:
242
+ level = "critical"
243
+ intensity = 10.0
244
+ self._ascending_alert_list.append(3)
245
+ elif total_detections > 25:
246
+ level = "significant"
247
+ intensity = 9.0
248
+ self._ascending_alert_list.append(2)
249
+ elif total_detections > 15:
250
+ level = "medium"
251
+ intensity = 7.0
252
+ self._ascending_alert_list.append(1)
253
+ else:
254
+ level = "low"
255
+ intensity = min(10.0, total_detections / 3.0)
256
+ self._ascending_alert_list.append(0)
257
+ human_text_lines = [f"VEHICLE INCIDENTS DETECTED @ {current_timestamp}:"]
258
+ human_text_lines.append(f"\tSeverity Level: {(self.CASE_TYPE, level)}")
259
+ human_text = "\n".join(human_text_lines)
260
+ alert_settings = []
261
+ if config.alert_config and hasattr(config.alert_config, 'alert_type'):
262
+ alert_settings.append({
263
+ "alert_type": getattr(config.alert_config, 'alert_type', ['Default']),
264
+ "incident_category": self.CASE_TYPE,
265
+ "threshold_level": config.alert_config.count_thresholds if hasattr(config.alert_config, 'count_thresholds') else {},
266
+ "ascending": True,
267
+ "settings": {t: v for t, v in zip(getattr(config.alert_config, 'alert_type', ['Default']),
268
+ getattr(config.alert_config, 'alert_value', ['JSON']))}
269
+ })
270
+ event = self.create_incident(
271
+ incident_id=f"{self.CASE_TYPE}_{frame_number}",
272
+ incident_type=self.CASE_TYPE,
273
+ severity_level=level,
274
+ human_text=human_text,
275
+ camera_info=camera_info,
276
+ alerts=alerts,
277
+ alert_settings=alert_settings,
278
+ start_time=start_timestamp,
279
+ end_time=self.current_incident_end_timestamp,
280
+ level_settings={"low": 1, "medium": 3, "significant": 4, "critical": 7}
281
+ )
282
+ incidents.append(event)
283
+ else:
284
+ self._ascending_alert_list.append(0)
285
+ incidents.append({})
286
+ return incidents
287
+
288
+ def _generate_tracking_stats(self, counting_summary: Dict, alerts: List, config: VehiclePeopleDroneMonitoringConfig,
289
+ frame_number: Optional[int] = None, stream_info: Optional[Dict[str, Any]] = None) -> List[Dict]:
290
+ camera_info = self.get_camera_info_from_stream(stream_info)
291
+ tracking_stats = []
292
+ total_detections = counting_summary.get("total_count", 0)
293
+ total_counts_dict = counting_summary.get("total_counts", {})
294
+ per_category_count = counting_summary.get("per_category_count", {})
295
+ current_timestamp = self._get_current_timestamp_str(stream_info, precision=False)
296
+ start_timestamp = self._get_start_timestamp_str(stream_info, precision=False)
297
+ high_precision_start_timestamp = self._get_current_timestamp_str(stream_info, precision=True)
298
+ high_precision_reset_timestamp = self._get_start_timestamp_str(stream_info, precision=True)
299
+ total_counts = [{"category": cat, "count": count} for cat, count in total_counts_dict.items() if count > 0]
300
+ current_counts = [{"category": cat, "count": count} for cat, count in per_category_count.items() if count > 0 or total_detections > 0]
301
+ detections = []
302
+ for detection in counting_summary.get("detections", []):
303
+ bbox = detection.get("bounding_box", {})
304
+ category = detection.get("category", "vehicle")
305
+ if detection.get("masks"):
306
+ segmentation = detection.get("masks", [])
307
+ detection_obj = self.create_detection_object(category, bbox, segmentation=segmentation)
308
+ elif detection.get("segmentation"):
309
+ segmentation = detection.get("segmentation")
310
+ detection_obj = self.create_detection_object(category, bbox, segmentation=segmentation)
311
+ elif detection.get("mask"):
312
+ segmentation = detection.get("mask")
313
+ detection_obj = self.create_detection_object(category, bbox, segmentation=segmentation)
314
+ else:
315
+ detection_obj = self.create_detection_object(category, bbox)
316
+ detections.append(detection_obj)
317
+ alert_settings = []
318
+ if config.alert_config and hasattr(config.alert_config, 'alert_type'):
319
+ alert_settings.append({
320
+ "alert_type": getattr(config.alert_config, 'alert_type', ['Default']),
321
+ "incident_category": self.CASE_TYPE,
322
+ "threshold_level": config.alert_config.count_thresholds if hasattr(config.alert_config, 'count_thresholds') else {},
323
+ "ascending": True,
324
+ "settings": {t: v for t, v in zip(getattr(config.alert_config, 'alert_type', ['Default']),
325
+ getattr(config.alert_config, 'alert_value', ['JSON']))}
326
+ })
327
+ human_text_lines = [f"Tracking Statistics:"]
328
+ human_text_lines.append(f"CURRENT FRAME @ {current_timestamp}")
329
+ for cat, count in per_category_count.items():
330
+ human_text_lines.append(f"\t{cat}: {count}")
331
+ human_text_lines.append(f"TOTAL SINCE {start_timestamp}")
332
+ for cat, count in total_counts_dict.items():
333
+ if count > 0:
334
+ human_text_lines.append(f"\t{cat}: {count}")
335
+ if alerts:
336
+ for alert in alerts:
337
+ human_text_lines.append(f"Alerts: {alert.get('settings', {})} sent @ {current_timestamp}")
338
+ else:
339
+ human_text_lines.append("Alerts: None")
340
+ human_text = "\n".join(human_text_lines)
341
+ reset_settings = [{"interval_type": "daily", "reset_time": {"value": 9, "time_unit": "hour"}}]
342
+ tracking_stat = self.create_tracking_stats(
343
+ total_counts=total_counts,
344
+ current_counts=current_counts,
345
+ detections=detections,
346
+ human_text=human_text,
347
+ camera_info=camera_info,
348
+ alerts=alerts,
349
+ alert_settings=alert_settings,
350
+ reset_settings=reset_settings,
351
+ start_time=high_precision_start_timestamp,
352
+ reset_time=high_precision_reset_timestamp
353
+ )
354
+ tracking_stats.append(tracking_stat)
355
+ return tracking_stats
356
+
357
+ def _generate_business_analytics(self, counting_summary: Dict, alerts: Any, config: VehiclePeopleDroneMonitoringConfig,
358
+ stream_info: Optional[Dict[str, Any]] = None, is_empty=False) -> List[Dict]:
359
+ if is_empty:
360
+ return []
361
+
362
+ def _generate_summary(self, summary: dict, incidents: List, tracking_stats: List, business_analytics: List, alerts: List) -> List[str]:
363
+ lines = {}
364
+ lines["Application Name"] = self.CASE_TYPE
365
+ lines["Application Version"] = self.CASE_VERSION
366
+ if len(incidents) > 0:
367
+ lines["Incidents:"] = f"\n\t{incidents[0].get('human_text', 'No incidents detected')}\n"
368
+ if len(tracking_stats) > 0:
369
+ lines["Tracking Statistics:"] = f"\t{tracking_stats[0].get('human_text', 'No tracking statistics detected')}\n"
370
+ if len(business_analytics) > 0:
371
+ lines["Business Analytics:"] = f"\t{business_analytics[0].get('human_text', 'No business analytics detected')}\n"
372
+ if len(incidents) == 0 and len(tracking_stats) == 0 and len(business_analytics) == 0:
373
+ lines["Summary"] = "No Summary Data"
374
+ return [lines]
375
+
376
+ def _get_track_ids_info(self, detections: list) -> Dict[str, Any]:
377
+ frame_track_ids = set()
378
+ for det in detections:
379
+ tid = det.get('track_id')
380
+ if tid is not None:
381
+ frame_track_ids.add(tid)
382
+ total_track_ids = set()
383
+ for s in getattr(self, '_per_category_total_track_ids', {}).values():
384
+ total_track_ids.update(s)
385
+ return {
386
+ "total_count": len(total_track_ids),
387
+ "current_frame_count": len(frame_track_ids),
388
+ "total_unique_track_ids": len(total_track_ids),
389
+ "current_frame_track_ids": list(frame_track_ids),
390
+ "last_update_time": time.time(),
391
+ "total_frames_processed": getattr(self, '_total_frame_counter', 0)
392
+ }
393
+
394
+ def _update_tracking_state(self, detections: list):
395
+ if not hasattr(self, "_per_category_total_track_ids"):
396
+ self._per_category_total_track_ids = {cat: set() for cat in self.target_categories}
397
+ self._current_frame_track_ids = {cat: set() for cat in self.target_categories}
398
+ for det in detections:
399
+ cat = det.get("category")
400
+ raw_track_id = det.get("track_id")
401
+ if cat not in self.target_categories or raw_track_id is None:
402
+ continue
403
+ bbox = det.get("bounding_box", det.get("bbox"))
404
+ canonical_id = self._merge_or_register_track(raw_track_id, bbox)
405
+ det["track_id"] = canonical_id
406
+ self._per_category_total_track_ids.setdefault(cat, set()).add(canonical_id)
407
+ self._current_frame_track_ids[cat].add(canonical_id)
408
+
409
+ def get_total_counts(self):
410
+ return {cat: len(ids) for cat, ids in getattr(self, '_per_category_total_track_ids', {}).items()}
411
+
412
+ def _format_timestamp_for_stream(self, timestamp: float) -> str:
413
+ dt = datetime.fromtimestamp(timestamp, tz=timezone.utc)
414
+ return dt.strftime('%Y:%m:%d %H:%M:%S')
415
+
416
+ def _format_timestamp_for_video(self, timestamp: float) -> str:
417
+ hours = int(timestamp // 3600)
418
+ minutes = int((timestamp % 3600) // 60)
419
+ seconds = round(float(timestamp % 60), 2)
420
+ return f"{hours:02d}:{minutes:02d}:{seconds:.1f}"
421
+
422
+ def _get_current_timestamp_str(self, stream_info: Optional[Dict[str, Any]], precision=False, frame_id: Optional[str]=None) -> str:
423
+ if not stream_info:
424
+ return "00:00:00.00"
425
+ if precision:
426
+ if stream_info.get("input_settings", {}).get("start_frame", "na") != "na":
427
+ if frame_id:
428
+ start_time = int(frame_id)/stream_info.get("input_settings", {}).get("original_fps", 30)
429
+ else:
430
+ start_time = stream_info.get("input_settings", {}).get("start_frame", 30)/stream_info.get("input_settings", {}).get("original_fps", 30)
431
+ return self._format_timestamp_for_video(start_time)
432
+ else:
433
+ return datetime.now(timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC")
434
+ if stream_info.get("input_settings", {}).get("start_frame", "na") != "na":
435
+ if frame_id:
436
+ start_time = int(frame_id)/stream_info.get("input_settings", {}).get("original_fps", 30)
437
+ else:
438
+ start_time = stream_info.get("input_settings", {}).get("start_frame", 30)/stream_info.get("input_settings", {}).get("original_fps", 30)
439
+ return self._format_timestamp_for_video(start_time)
440
+ else:
441
+ stream_time_str = stream_info.get("input_settings", {}).get("stream_info", {}).get("stream_time", "")
442
+ if stream_time_str:
443
+ try:
444
+ timestamp_str = stream_time_str.replace(" UTC", "")
445
+ dt = datetime.strptime(timestamp_str, "%Y-%m-%d-%H:%M:%S.%f")
446
+ timestamp = dt.replace(tzinfo=timezone.utc).timestamp()
447
+ return self._format_timestamp_for_stream(timestamp)
448
+ except:
449
+ return self._format_timestamp_for_stream(time.time())
450
+ else:
451
+ return self._format_timestamp_for_stream(time.time())
452
+
453
+ def _get_start_timestamp_str(self, stream_info: Optional[Dict[str, Any]], precision=False) -> str:
454
+ if not stream_info:
455
+ return "00:00:00"
456
+ if precision:
457
+ if stream_info.get("input_settings", {}).get("start_frame", "na") != "na":
458
+ return "00:00:00"
459
+ else:
460
+ return datetime.now(timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC")
461
+ if stream_info.get("input_settings", {}).get("start_frame", "na") != "na":
462
+ return "00:00:00"
463
+ else:
464
+ if self._tracking_start_time is None:
465
+ stream_time_str = stream_info.get("input_settings", {}).get("stream_info", {}).get("stream_time", "")
466
+ if stream_time_str:
467
+ try:
468
+ timestamp_str = stream_time_str.replace(" UTC", "")
469
+ dt = datetime.strptime(timestamp_str, "%Y-%m-%d-%H:%M:%S.%f")
470
+ self._tracking_start_time = dt.replace(tzinfo=timezone.utc).timestamp()
471
+ except:
472
+ self._tracking_start_time = time.time()
473
+ else:
474
+ self._tracking_start_time = time.time()
475
+ dt = datetime.fromtimestamp(self._tracking_start_time, tz=timezone.utc)
476
+ dt = dt.replace(minute=0, second=0, microsecond=0)
477
+ return dt.strftime('%Y:%m:%d %H:%M:%S')
478
+
479
+ def _count_categories(self, detections: list, config: VehiclePeopleDroneMonitoringConfig) -> dict:
480
+ counts = {}
481
+ for det in detections:
482
+ cat = det.get('category', 'unknown')
483
+ counts[cat] = counts.get(cat, 0) + 1
484
+ return {
485
+ "total_count": sum(counts.values()),
486
+ "per_category_count": counts,
487
+ "detections": [
488
+ {
489
+ "bounding_box": det.get("bounding_box"),
490
+ "category": det.get("category"),
491
+ "confidence": det.get("confidence"),
492
+ "track_id": det.get("track_id"),
493
+ "frame_id": det.get("frame_id")
494
+ }
495
+ for det in detections
496
+ ]
497
+ }
498
+
499
+ def _extract_predictions(self, detections: list) -> List[Dict[str, Any]]:
500
+ return [
501
+ {
502
+ "category": det.get("category", "unknown"),
503
+ "confidence": det.get("confidence", 0.0),
504
+ "bounding_box": det.get("bounding_box", {})
505
+ }
506
+ for det in detections
507
+ ]
508
+
509
+ def _compute_iou(self, box1: Any, box2: Any) -> float:
510
+ def _bbox_to_list(bbox):
511
+ if bbox is None:
512
+ return []
513
+ if isinstance(bbox, list):
514
+ return bbox[:4] if len(bbox) >= 4 else []
515
+ if isinstance(bbox, dict):
516
+ if "xmin" in bbox:
517
+ return [bbox["xmin"], bbox["ymin"], bbox["xmax"], bbox["ymax"]]
518
+ if "x1" in bbox:
519
+ return [bbox["x1"], bbox["y1"], bbox["x2"], bbox["y2"]]
520
+ values = [v for v in bbox.values() if isinstance(v, (int, float))]
521
+ return values[:4] if len(values) >= 4 else []
522
+ return []
523
+ l1 = _bbox_to_list(box1)
524
+ l2 = _bbox_to_list(box2)
525
+ if len(l1) < 4 or len(l2) < 4:
526
+ return 0.0
527
+ x1_min, y1_min, x1_max, y1_max = l1
528
+ x2_min, y2_min, x2_max, y2_max = l2
529
+ x1_min, x1_max = min(x1_min, x1_max), max(x1_min, x1_max)
530
+ y1_min, y1_max = min(y1_min, y1_max), max(y1_min, y1_max)
531
+ x2_min, x2_max = min(x2_min, x2_max), max(x2_min, x2_max)
532
+ y2_min, y2_max = min(y2_min, y2_max), max(y2_min, y2_max)
533
+ inter_x_min = max(x1_min, x2_min)
534
+ inter_y_min = max(y1_min, y2_min)
535
+ inter_x_max = min(x1_max, x2_max)
536
+ inter_y_max = min(y1_max, y2_max)
537
+ inter_w = max(0.0, inter_x_max - inter_x_min)
538
+ inter_h = max(0.0, inter_y_max - inter_y_min)
539
+ inter_area = inter_w * inter_h
540
+ area1 = (x1_max - x1_min) * (y1_max - y1_min)
541
+ area2 = (x2_max - x2_min) * (y2_max - y2_min)
542
+ union_area = area1 + area2 - inter_area
543
+ return (inter_area / union_area) if union_area > 0 else 0.0
544
+
545
+ def _merge_or_register_track(self, raw_id: Any, bbox: Any) -> Any:
546
+ if raw_id is None or bbox is None:
547
+ return raw_id
548
+ now = time.time()
549
+ if raw_id in self._track_aliases:
550
+ canonical_id = self._track_aliases[raw_id]
551
+ track_info = self._canonical_tracks.get(canonical_id)
552
+ if track_info is not None:
553
+ track_info["last_bbox"] = bbox
554
+ track_info["last_update"] = now
555
+ track_info["raw_ids"].add(raw_id)
556
+ return canonical_id
557
+ for canonical_id, info in self._canonical_tracks.items():
558
+ if now - info["last_update"] > self._track_merge_time_window:
559
+ continue
560
+ iou = self._compute_iou(bbox, info["last_bbox"])
561
+ if iou >= self._track_merge_iou_threshold:
562
+ self._track_aliases[raw_id] = canonical_id
563
+ info["last_bbox"] = bbox
564
+ info["last_update"] = now
565
+ info["raw_ids"].add(raw_id)
566
+ return canonical_id
567
+ canonical_id = raw_id
568
+ self._track_aliases[raw_id] = canonical_id
569
+ self._canonical_tracks[canonical_id] = {
570
+ "last_bbox": bbox,
571
+ "last_update": now,
572
+ "raw_ids": {raw_id},
573
+ }
574
+ return canonical_id
575
+
576
+ def _format_timestamp(self, timestamp: float) -> str:
577
+ return datetime.fromtimestamp(timestamp, timezone.utc).strftime('%Y-%m-%d %H:%M:%S UTC')
578
+
579
+ def _get_tracking_start_time(self) -> str:
580
+ if self._tracking_start_time is None:
581
+ return "N/A"
582
+ return self._format_timestamp(self._tracking_start_time)
583
+
584
+ def _set_tracking_start_time(self) -> None:
585
+ self._tracking_start_time = time.time()