matrice-analytics 0.1.3__py3-none-any.whl → 0.1.32__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of matrice-analytics might be problematic. Click here for more details.
- matrice_analytics/post_processing/advanced_tracker/matching.py +3 -3
- matrice_analytics/post_processing/advanced_tracker/strack.py +1 -1
- matrice_analytics/post_processing/config.py +4 -0
- matrice_analytics/post_processing/core/config.py +115 -12
- matrice_analytics/post_processing/face_reg/compare_similarity.py +5 -5
- matrice_analytics/post_processing/face_reg/embedding_manager.py +109 -8
- matrice_analytics/post_processing/face_reg/face_recognition.py +157 -61
- matrice_analytics/post_processing/face_reg/face_recognition_client.py +339 -88
- matrice_analytics/post_processing/face_reg/people_activity_logging.py +67 -29
- 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 +0 -1
- matrice_analytics/post_processing/post_processor.py +32 -11
- matrice_analytics/post_processing/usecases/color/clip.py +42 -8
- matrice_analytics/post_processing/usecases/color/color_mapper.py +2 -2
- matrice_analytics/post_processing/usecases/color_detection.py +50 -129
- matrice_analytics/post_processing/usecases/drone_traffic_monitoring.py +41 -386
- matrice_analytics/post_processing/usecases/flare_analysis.py +1 -56
- matrice_analytics/post_processing/usecases/license_plate_detection.py +476 -202
- matrice_analytics/post_processing/usecases/license_plate_monitoring.py +351 -26
- matrice_analytics/post_processing/usecases/people_counting.py +408 -1431
- matrice_analytics/post_processing/usecases/people_counting_bckp.py +1683 -0
- matrice_analytics/post_processing/usecases/vehicle_monitoring.py +39 -10
- matrice_analytics/post_processing/utils/__init__.py +8 -8
- {matrice_analytics-0.1.3.dist-info → matrice_analytics-0.1.32.dist-info}/METADATA +1 -1
- {matrice_analytics-0.1.3.dist-info → matrice_analytics-0.1.32.dist-info}/RECORD +61 -26
- {matrice_analytics-0.1.3.dist-info → matrice_analytics-0.1.32.dist-info}/WHEEL +0 -0
- {matrice_analytics-0.1.3.dist-info → matrice_analytics-0.1.32.dist-info}/licenses/LICENSE.txt +0 -0
- {matrice_analytics-0.1.3.dist-info → matrice_analytics-0.1.32.dist-info}/top_level.txt +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import Any, Dict, List, Optional
|
|
1
|
+
from typing import Any, Dict, List, Optional
|
|
2
2
|
from dataclasses import asdict
|
|
3
3
|
import time
|
|
4
4
|
from datetime import datetime, timezone
|
|
@@ -18,7 +18,6 @@ from ..utils import (
|
|
|
18
18
|
)
|
|
19
19
|
from dataclasses import dataclass, field
|
|
20
20
|
from ..core.config import BaseConfig, AlertConfig, ZoneConfig
|
|
21
|
-
from ..utils.geometry_utils import get_bbox_center, point_in_polygon, get_bbox_bottom25_center
|
|
22
21
|
|
|
23
22
|
@dataclass
|
|
24
23
|
class VehiclePeopleDroneMonitoringConfig(BaseConfig):
|
|
@@ -29,15 +28,6 @@ class VehiclePeopleDroneMonitoringConfig(BaseConfig):
|
|
|
29
28
|
smoothing_cooldown_frames: int = 5
|
|
30
29
|
smoothing_confidence_range_factor: float = 0.5
|
|
31
30
|
confidence_threshold: float = 0.6
|
|
32
|
-
|
|
33
|
-
#JBK_720_GATE POLYGON = [[86, 328], [844, 317], [1277, 520], [1273, 707], [125, 713]]
|
|
34
|
-
zone_config: Optional[Dict[str, Dict[str, List[List[float]]]]] = None #field(
|
|
35
|
-
# default_factory=lambda: {
|
|
36
|
-
# "zones": {
|
|
37
|
-
# "Interest_Region": [[86, 328], [844, 317], [1277, 520], [1273, 707], [125, 713]],
|
|
38
|
-
# }
|
|
39
|
-
# }
|
|
40
|
-
# )
|
|
41
31
|
usecase_categories: List[str] = field(
|
|
42
32
|
default_factory=lambda: [
|
|
43
33
|
"pedestrian", "people", "bicycle", "car", "van", "truck", "tricycle", "awning-tricycle", "bus", "motor"
|
|
@@ -57,8 +47,7 @@ class VehiclePeopleDroneMonitoringConfig(BaseConfig):
|
|
|
57
47
|
|
|
58
48
|
class DroneTrafficMonitoringUsecase(BaseProcessor):
|
|
59
49
|
CATEGORY_DISPLAY = {
|
|
60
|
-
|
|
61
|
-
"pedestrian": "Pesestrian",
|
|
50
|
+
"pedestrian": "Pedestrian",
|
|
62
51
|
"people": "People",
|
|
63
52
|
"bicycle": "Bicycle",
|
|
64
53
|
"car": "Car",
|
|
@@ -87,54 +76,29 @@ class DroneTrafficMonitoringUsecase(BaseProcessor):
|
|
|
87
76
|
self._track_merge_time_window: float = 7.0
|
|
88
77
|
self._ascending_alert_list: List[int] = []
|
|
89
78
|
self.current_incident_end_timestamp: str = "N/A"
|
|
90
|
-
self.start_timer = None
|
|
91
|
-
|
|
92
|
-
# Track ID storage for total count calculation
|
|
93
|
-
self._total_track_ids = set() # Store all unique track IDs seen across calls
|
|
94
|
-
self._current_frame_track_ids = set() # Store track IDs from current frame
|
|
95
|
-
self._total_count = 0 # Cached total count
|
|
96
|
-
self._last_update_time = time.time() # Track when last updated
|
|
97
|
-
self._total_count_list = []
|
|
98
|
-
|
|
99
|
-
# Zone-based tracking storage
|
|
100
|
-
self._zone_current_track_ids = {} # zone_name -> set of current track IDs in zone
|
|
101
|
-
self._zone_total_track_ids = {} # zone_name -> set of all track IDs that have been in zone
|
|
102
|
-
self._zone_current_counts = {} # zone_name -> current count in zone
|
|
103
|
-
self._zone_total_counts = {} # zone_name -> total count that have been in zone
|
|
104
79
|
|
|
105
80
|
def process(self, data: Any, config: ConfigProtocol, context: Optional[ProcessingContext] = None,
|
|
106
81
|
stream_info: Optional[Dict[str, Any]] = None) -> ProcessingResult:
|
|
107
|
-
|
|
82
|
+
start_time = time.time()
|
|
108
83
|
if not isinstance(config, VehiclePeopleDroneMonitoringConfig):
|
|
109
84
|
return self.create_error_result("Invalid config type", usecase=self.name, category=self.category, context=context)
|
|
110
85
|
if context is None:
|
|
111
86
|
context = ProcessingContext()
|
|
112
|
-
|
|
113
|
-
# Normalize typical YOLO outputs (COCO pretrained) to internal schema
|
|
114
|
-
data = self._normalize_yolo_results(data, getattr(config, 'index_to_category', None))
|
|
115
|
-
|
|
116
87
|
input_format = match_results_structure(data)
|
|
117
88
|
context.input_format = input_format
|
|
118
89
|
context.confidence_threshold = config.confidence_threshold
|
|
119
|
-
config.confidence_threshold = 0.25
|
|
120
|
-
|
|
121
90
|
if config.confidence_threshold is not None:
|
|
122
91
|
processed_data = filter_by_confidence(data, config.confidence_threshold)
|
|
123
92
|
self.logger.debug(f"Applied confidence filtering with threshold {config.confidence_threshold}")
|
|
124
93
|
else:
|
|
125
94
|
processed_data = data
|
|
126
95
|
self.logger.debug("Did not apply confidence filtering since no threshold provided")
|
|
127
|
-
|
|
128
96
|
if config.index_to_category:
|
|
129
97
|
processed_data = apply_category_mapping(processed_data, config.index_to_category)
|
|
130
98
|
self.logger.debug("Applied category mapping")
|
|
131
|
-
|
|
132
|
-
processed_data = [d for d in processed_data if d.get('category') in self.target_categories]
|
|
133
99
|
if config.target_categories:
|
|
134
100
|
processed_data = [d for d in processed_data if d.get('category') in self.target_categories]
|
|
135
101
|
self.logger.debug("Applied category filtering")
|
|
136
|
-
|
|
137
|
-
|
|
138
102
|
if config.enable_smoothing:
|
|
139
103
|
if self.smoothing_tracker is None:
|
|
140
104
|
smoothing_config = BBoxSmoothingConfig(
|
|
@@ -147,7 +111,6 @@ class DroneTrafficMonitoringUsecase(BaseProcessor):
|
|
|
147
111
|
)
|
|
148
112
|
self.smoothing_tracker = BBoxSmoothingTracker(smoothing_config)
|
|
149
113
|
processed_data = bbox_smoothing(processed_data, self.smoothing_tracker.config, self.smoothing_tracker)
|
|
150
|
-
|
|
151
114
|
try:
|
|
152
115
|
from ..advanced_tracker import AdvancedTracker
|
|
153
116
|
from ..advanced_tracker.config import TrackerConfig
|
|
@@ -158,10 +121,8 @@ class DroneTrafficMonitoringUsecase(BaseProcessor):
|
|
|
158
121
|
processed_data = self.tracker.update(processed_data)
|
|
159
122
|
except Exception as e:
|
|
160
123
|
self.logger.warning(f"AdvancedTracker failed: {e}")
|
|
161
|
-
|
|
162
124
|
self._update_tracking_state(processed_data)
|
|
163
125
|
self._total_frame_counter += 1
|
|
164
|
-
|
|
165
126
|
frame_number = None
|
|
166
127
|
if stream_info:
|
|
167
128
|
input_settings = stream_info.get("input_settings", {})
|
|
@@ -169,39 +130,16 @@ class DroneTrafficMonitoringUsecase(BaseProcessor):
|
|
|
169
130
|
end_frame = input_settings.get("end_frame")
|
|
170
131
|
if start_frame is not None and end_frame is not None and start_frame == end_frame:
|
|
171
132
|
frame_number = start_frame
|
|
172
|
-
|
|
173
133
|
general_counting_summary = calculate_counting_summary(data)
|
|
174
134
|
counting_summary = self._count_categories(processed_data, config)
|
|
175
135
|
total_counts = self.get_total_counts()
|
|
176
136
|
counting_summary['total_counts'] = total_counts
|
|
177
|
-
|
|
178
|
-
for detection in processed_data:
|
|
179
|
-
category = detection.get("category", "unknown")
|
|
180
|
-
counting_summary["categories"][category] = counting_summary["categories"].get(category, 0) + 1
|
|
181
|
-
|
|
182
|
-
zone_analysis = {}
|
|
183
|
-
if config.zone_config and config.zone_config['zones']:
|
|
184
|
-
# Convert single frame to format expected by count_objects_in_zones
|
|
185
|
-
frame_data = processed_data #[frame_detections]
|
|
186
|
-
zone_analysis = count_objects_in_zones(frame_data, config.zone_config['zones'], stream_info)
|
|
187
|
-
|
|
188
|
-
if zone_analysis:
|
|
189
|
-
enhanced_zone_analysis = self._update_zone_tracking(zone_analysis, processed_data, config)
|
|
190
|
-
# Merge enhanced zone analysis with original zone analysis
|
|
191
|
-
for zone_name, enhanced_data in enhanced_zone_analysis.items():
|
|
192
|
-
zone_analysis[zone_name] = enhanced_data
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
alerts = self._check_alerts(counting_summary,zone_analysis, frame_number, config)
|
|
137
|
+
alerts = self._check_alerts(counting_summary, frame_number, config)
|
|
196
138
|
predictions = self._extract_predictions(processed_data)
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
business_analytics_list = self._generate_business_analytics(counting_summary,zone_analysis, alerts, config, stream_info, is_empty=True)
|
|
203
|
-
summary_list = self._generate_summary(counting_summary,zone_analysis, incidents_list, tracking_stats_list, business_analytics_list, alerts)
|
|
204
|
-
|
|
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)
|
|
205
143
|
incidents = incidents_list[0] if incidents_list else {}
|
|
206
144
|
tracking_stats = tracking_stats_list[0] if tracking_stats_list else {}
|
|
207
145
|
business_analytics = business_analytics_list[0] if business_analytics_list else {}
|
|
@@ -211,10 +149,8 @@ class DroneTrafficMonitoringUsecase(BaseProcessor):
|
|
|
211
149
|
"tracking_stats": tracking_stats,
|
|
212
150
|
"business_analytics": business_analytics,
|
|
213
151
|
"alerts": alerts,
|
|
214
|
-
"zone_analysis": zone_analysis,
|
|
215
152
|
"human_text": summary}
|
|
216
153
|
}
|
|
217
|
-
|
|
218
154
|
context.mark_completed()
|
|
219
155
|
result = self.create_result(
|
|
220
156
|
data={"agg_summary": agg_summary},
|
|
@@ -222,167 +158,9 @@ class DroneTrafficMonitoringUsecase(BaseProcessor):
|
|
|
222
158
|
category=self.category,
|
|
223
159
|
context=context
|
|
224
160
|
)
|
|
225
|
-
proc_time = time.time() - processing_start
|
|
226
|
-
processing_latency_ms = proc_time * 1000.0
|
|
227
|
-
processing_fps = (1.0 / proc_time) if proc_time > 0 else None
|
|
228
|
-
# Log the performance metrics using the module-level logger
|
|
229
|
-
print("latency in ms:",processing_latency_ms,"| Throughput fps:",processing_fps,"| Frame_Number:",self._total_frame_counter)
|
|
230
161
|
return result
|
|
231
162
|
|
|
232
|
-
def
|
|
233
|
-
"""
|
|
234
|
-
Update zone tracking with current frame data.
|
|
235
|
-
|
|
236
|
-
Args:
|
|
237
|
-
zone_analysis: Current zone analysis results
|
|
238
|
-
detections: List of detections with track IDs
|
|
239
|
-
|
|
240
|
-
Returns:
|
|
241
|
-
Enhanced zone analysis with tracking information
|
|
242
|
-
"""
|
|
243
|
-
if not zone_analysis or not config.zone_config or not config.zone_config['zones']:
|
|
244
|
-
return {}
|
|
245
|
-
|
|
246
|
-
enhanced_zone_analysis = {}
|
|
247
|
-
zones = config.zone_config['zones']
|
|
248
|
-
|
|
249
|
-
# Get current frame track IDs in each zone
|
|
250
|
-
current_frame_zone_tracks = {}
|
|
251
|
-
|
|
252
|
-
# Initialize zone tracking for all zones
|
|
253
|
-
for zone_name in zones.keys():
|
|
254
|
-
current_frame_zone_tracks[zone_name] = set()
|
|
255
|
-
if zone_name not in self._zone_current_track_ids:
|
|
256
|
-
self._zone_current_track_ids[zone_name] = set()
|
|
257
|
-
if zone_name not in self._zone_total_track_ids:
|
|
258
|
-
self._zone_total_track_ids[zone_name] = set()
|
|
259
|
-
|
|
260
|
-
# Check each detection against each zone
|
|
261
|
-
for detection in detections:
|
|
262
|
-
track_id = detection.get("track_id")
|
|
263
|
-
if track_id is None:
|
|
264
|
-
continue
|
|
265
|
-
|
|
266
|
-
# Get detection bbox
|
|
267
|
-
bbox = detection.get("bounding_box", detection.get("bbox"))
|
|
268
|
-
if not bbox:
|
|
269
|
-
continue
|
|
270
|
-
|
|
271
|
-
# Get detection center point
|
|
272
|
-
center_point = get_bbox_bottom25_center(bbox) #get_bbox_center(bbox)
|
|
273
|
-
|
|
274
|
-
# Check which zone this detection is in using actual zone polygons
|
|
275
|
-
for zone_name, zone_polygon in zones.items():
|
|
276
|
-
# Convert polygon points to tuples for point_in_polygon function
|
|
277
|
-
# zone_polygon format: [[x1, y1], [x2, y2], [x3, y3], ...]
|
|
278
|
-
polygon_points = [(point[0], point[1]) for point in zone_polygon]
|
|
279
|
-
|
|
280
|
-
# Check if detection center is inside the zone polygon using ray casting algorithm
|
|
281
|
-
if point_in_polygon(center_point, polygon_points):
|
|
282
|
-
current_frame_zone_tracks[zone_name].add(track_id)
|
|
283
|
-
if track_id not in self._total_count_list:
|
|
284
|
-
self._total_count_list.append(track_id)
|
|
285
|
-
|
|
286
|
-
# Update zone tracking for each zone
|
|
287
|
-
for zone_name, zone_counts in zone_analysis.items():
|
|
288
|
-
# Get current frame tracks for this zone
|
|
289
|
-
current_tracks = current_frame_zone_tracks.get(zone_name, set())
|
|
290
|
-
|
|
291
|
-
# Update current zone tracks
|
|
292
|
-
self._zone_current_track_ids[zone_name] = current_tracks
|
|
293
|
-
|
|
294
|
-
# Update total zone tracks (accumulate all track IDs that have been in this zone)
|
|
295
|
-
self._zone_total_track_ids[zone_name].update(current_tracks)
|
|
296
|
-
|
|
297
|
-
# Update counts
|
|
298
|
-
self._zone_current_counts[zone_name] = len(current_tracks)
|
|
299
|
-
self._zone_total_counts[zone_name] = len(self._zone_total_track_ids[zone_name])
|
|
300
|
-
|
|
301
|
-
# Create enhanced zone analysis
|
|
302
|
-
enhanced_zone_analysis[zone_name] = {
|
|
303
|
-
"current_count": self._zone_current_counts[zone_name],
|
|
304
|
-
"total_count": self._zone_total_counts[zone_name],
|
|
305
|
-
"current_track_ids": list(current_tracks),
|
|
306
|
-
"total_track_ids": list(self._zone_total_track_ids[zone_name]),
|
|
307
|
-
"original_counts": zone_counts # Preserve original zone counts
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
return enhanced_zone_analysis
|
|
311
|
-
|
|
312
|
-
def _normalize_yolo_results(self, data: Any, index_to_category: Optional[Dict[int, str]] = None) -> Any:
|
|
313
|
-
"""
|
|
314
|
-
Normalize YOLO-style outputs to internal detection schema:
|
|
315
|
-
- category/category_id: prefer string label using COCO mapping if available
|
|
316
|
-
- confidence: map from 'conf'/'score' to 'confidence'
|
|
317
|
-
- bounding_box: ensure dict with keys (x1,y1,x2,y2) or (xmin,ymin,xmax,ymax)
|
|
318
|
-
- supports list of detections and frame_id -> detections dict
|
|
319
|
-
"""
|
|
320
|
-
def to_bbox_dict(d: Dict[str, Any]) -> Dict[str, Any]:
|
|
321
|
-
if "bounding_box" in d and isinstance(d["bounding_box"], dict):
|
|
322
|
-
return d["bounding_box"]
|
|
323
|
-
if "bbox" in d:
|
|
324
|
-
bbox = d["bbox"]
|
|
325
|
-
if isinstance(bbox, dict):
|
|
326
|
-
return bbox
|
|
327
|
-
if isinstance(bbox, (list, tuple)) and len(bbox) >= 4:
|
|
328
|
-
x1, y1, x2, y2 = bbox[0], bbox[1], bbox[2], bbox[3]
|
|
329
|
-
return {"x1": x1, "y1": y1, "x2": x2, "y2": y2}
|
|
330
|
-
if "xyxy" in d and isinstance(d["xyxy"], (list, tuple)) and len(d["xyxy"]) >= 4:
|
|
331
|
-
x1, y1, x2, y2 = d["xyxy"][0], d["xyxy"][1], d["xyxy"][2], d["xyxy"][3]
|
|
332
|
-
return {"x1": x1, "y1": y1, "x2": x2, "y2": y2}
|
|
333
|
-
if "xywh" in d and isinstance(d["xywh"], (list, tuple)) and len(d["xywh"]) >= 4:
|
|
334
|
-
cx, cy, w, h = d["xywh"][0], d["xywh"][1], d["xywh"][2], d["xywh"][3]
|
|
335
|
-
x1, y1, x2, y2 = cx - w / 2, cy - h / 2, cx + w / 2, cy + h / 2
|
|
336
|
-
return {"x1": x1, "y1": y1, "x2": x2, "y2": y2}
|
|
337
|
-
return {}
|
|
338
|
-
|
|
339
|
-
def resolve_category(d: Dict[str, Any]) -> Tuple[str, Optional[int]]:
|
|
340
|
-
raw_cls = d.get("category", d.get("category_id", d.get("class", d.get("cls"))))
|
|
341
|
-
label_name = d.get("name")
|
|
342
|
-
if isinstance(raw_cls, int):
|
|
343
|
-
if index_to_category and raw_cls in index_to_category:
|
|
344
|
-
return index_to_category[raw_cls], raw_cls
|
|
345
|
-
return str(raw_cls), raw_cls
|
|
346
|
-
if isinstance(raw_cls, str):
|
|
347
|
-
# Some YOLO exports provide string labels directly
|
|
348
|
-
return raw_cls, None
|
|
349
|
-
if label_name:
|
|
350
|
-
return str(label_name), None
|
|
351
|
-
return "unknown", None
|
|
352
|
-
|
|
353
|
-
def normalize_det(det: Dict[str, Any]) -> Dict[str, Any]:
|
|
354
|
-
category_name, category_id = resolve_category(det)
|
|
355
|
-
confidence = det.get("confidence", det.get("conf", det.get("score", 0.0)))
|
|
356
|
-
bbox = to_bbox_dict(det)
|
|
357
|
-
normalized = {
|
|
358
|
-
"category": category_name,
|
|
359
|
-
"confidence": confidence,
|
|
360
|
-
"bounding_box": bbox,
|
|
361
|
-
}
|
|
362
|
-
if category_id is not None:
|
|
363
|
-
normalized["category_id"] = category_id
|
|
364
|
-
# Preserve optional fields
|
|
365
|
-
for key in ("track_id", "frame_id", "masks", "segmentation"):
|
|
366
|
-
if key in det:
|
|
367
|
-
normalized[key] = det[key]
|
|
368
|
-
return normalized
|
|
369
|
-
|
|
370
|
-
if isinstance(data, list):
|
|
371
|
-
return [normalize_det(d) if isinstance(d, dict) else d for d in data]
|
|
372
|
-
if isinstance(data, dict):
|
|
373
|
-
# Detect tracking style dict: frame_id -> list of detections
|
|
374
|
-
normalized_dict: Dict[str, Any] = {}
|
|
375
|
-
for k, v in data.items():
|
|
376
|
-
if isinstance(v, list):
|
|
377
|
-
normalized_dict[k] = [normalize_det(d) if isinstance(d, dict) else d for d in v]
|
|
378
|
-
elif isinstance(v, dict):
|
|
379
|
-
normalized_dict[k] = normalize_det(v)
|
|
380
|
-
else:
|
|
381
|
-
normalized_dict[k] = v
|
|
382
|
-
return normalized_dict
|
|
383
|
-
return data
|
|
384
|
-
|
|
385
|
-
def _check_alerts(self, summary: dict, zone_analysis: Dict, frame_number: Any, config: VehiclePeopleDroneMonitoringConfig) -> List[Dict]:
|
|
163
|
+
def _check_alerts(self, summary: dict, frame_number: Any, config: VehiclePeopleDroneMonitoringConfig) -> List[Dict]:
|
|
386
164
|
def get_trend(data, lookback=900, threshold=0.6):
|
|
387
165
|
window = data[-lookback:] if len(data) >= lookback else data
|
|
388
166
|
if len(window) < 2:
|
|
@@ -395,16 +173,13 @@ class DroneTrafficMonitoringUsecase(BaseProcessor):
|
|
|
395
173
|
total += 1
|
|
396
174
|
ratio = increasing / total
|
|
397
175
|
return ratio >= threshold
|
|
398
|
-
|
|
399
176
|
frame_key = str(frame_number) if frame_number is not None else "current_frame"
|
|
400
177
|
alerts = []
|
|
401
178
|
total_detections = summary.get("total_count", 0)
|
|
402
179
|
total_counts_dict = summary.get("total_counts", {})
|
|
403
180
|
per_category_count = summary.get("per_category_count", {})
|
|
404
|
-
|
|
405
181
|
if not config.alert_config:
|
|
406
182
|
return alerts
|
|
407
|
-
|
|
408
183
|
if hasattr(config.alert_config, 'count_thresholds') and config.alert_config.count_thresholds:
|
|
409
184
|
for category, threshold in config.alert_config.count_thresholds.items():
|
|
410
185
|
if category == "all" and total_detections > threshold:
|
|
@@ -415,7 +190,7 @@ class DroneTrafficMonitoringUsecase(BaseProcessor):
|
|
|
415
190
|
"threshold_level": threshold,
|
|
416
191
|
"ascending": get_trend(self._ascending_alert_list, lookback=900, threshold=0.8),
|
|
417
192
|
"settings": {t: v for t, v in zip(getattr(config.alert_config, 'alert_type', ['Default']),
|
|
418
|
-
|
|
193
|
+
getattr(config.alert_config, 'alert_value', ['JSON']))}
|
|
419
194
|
})
|
|
420
195
|
elif category in per_category_count and per_category_count[category] > threshold:
|
|
421
196
|
alerts.append({
|
|
@@ -425,19 +200,17 @@ class DroneTrafficMonitoringUsecase(BaseProcessor):
|
|
|
425
200
|
"threshold_level": threshold,
|
|
426
201
|
"ascending": get_trend(self._ascending_alert_list, lookback=900, threshold=0.8),
|
|
427
202
|
"settings": {t: v for t, v in zip(getattr(config.alert_config, 'alert_type', ['Default']),
|
|
428
|
-
|
|
203
|
+
getattr(config.alert_config, 'alert_value', ['JSON']))}
|
|
429
204
|
})
|
|
430
205
|
return alerts
|
|
431
206
|
|
|
432
|
-
def _generate_incidents(self, counting_summary: Dict,
|
|
433
|
-
|
|
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]:
|
|
434
209
|
incidents = []
|
|
435
210
|
total_detections = counting_summary.get("total_count", 0)
|
|
436
211
|
current_timestamp = self._get_current_timestamp_str(stream_info)
|
|
437
212
|
camera_info = self.get_camera_info_from_stream(stream_info)
|
|
438
|
-
|
|
439
213
|
self._ascending_alert_list = self._ascending_alert_list[-900:] if len(self._ascending_alert_list) > 900 else self._ascending_alert_list
|
|
440
|
-
|
|
441
214
|
if total_detections > 0:
|
|
442
215
|
level = "low"
|
|
443
216
|
intensity = 5.0
|
|
@@ -449,8 +222,7 @@ class DroneTrafficMonitoringUsecase(BaseProcessor):
|
|
|
449
222
|
self.current_incident_end_timestamp = current_timestamp
|
|
450
223
|
elif self.current_incident_end_timestamp != 'Incident still active' and self.current_incident_end_timestamp != 'N/A':
|
|
451
224
|
self.current_incident_end_timestamp = 'N/A'
|
|
452
|
-
|
|
453
|
-
if config.alert_config and hasattr(config.alert_config, 'count_thresholds') and config.alert_config.count_thresholds:
|
|
225
|
+
if config.alert_config and config.alert_config.count_thresholds:
|
|
454
226
|
threshold = config.alert_config.count_thresholds.get("all", 15)
|
|
455
227
|
intensity = min(10.0, (total_detections / threshold) * 10)
|
|
456
228
|
if intensity >= 9:
|
|
@@ -482,11 +254,9 @@ class DroneTrafficMonitoringUsecase(BaseProcessor):
|
|
|
482
254
|
level = "low"
|
|
483
255
|
intensity = min(10.0, total_detections / 3.0)
|
|
484
256
|
self._ascending_alert_list.append(0)
|
|
485
|
-
|
|
486
257
|
human_text_lines = [f"VEHICLE INCIDENTS DETECTED @ {current_timestamp}:"]
|
|
487
258
|
human_text_lines.append(f"\tSeverity Level: {(self.CASE_TYPE, level)}")
|
|
488
259
|
human_text = "\n".join(human_text_lines)
|
|
489
|
-
|
|
490
260
|
alert_settings = []
|
|
491
261
|
if config.alert_config and hasattr(config.alert_config, 'alert_type'):
|
|
492
262
|
alert_settings.append({
|
|
@@ -497,7 +267,6 @@ class DroneTrafficMonitoringUsecase(BaseProcessor):
|
|
|
497
267
|
"settings": {t: v for t, v in zip(getattr(config.alert_config, 'alert_type', ['Default']),
|
|
498
268
|
getattr(config.alert_config, 'alert_value', ['JSON']))}
|
|
499
269
|
})
|
|
500
|
-
|
|
501
270
|
event = self.create_incident(
|
|
502
271
|
incident_id=f"{self.CASE_TYPE}_{frame_number}",
|
|
503
272
|
incident_type=self.CASE_TYPE,
|
|
@@ -516,7 +285,7 @@ class DroneTrafficMonitoringUsecase(BaseProcessor):
|
|
|
516
285
|
incidents.append({})
|
|
517
286
|
return incidents
|
|
518
287
|
|
|
519
|
-
def _generate_tracking_stats(self, counting_summary: Dict,
|
|
288
|
+
def _generate_tracking_stats(self, counting_summary: Dict, alerts: List, config: VehiclePeopleDroneMonitoringConfig,
|
|
520
289
|
frame_number: Optional[int] = None, stream_info: Optional[Dict[str, Any]] = None) -> List[Dict]:
|
|
521
290
|
camera_info = self.get_camera_info_from_stream(stream_info)
|
|
522
291
|
tracking_stats = []
|
|
@@ -527,10 +296,8 @@ class DroneTrafficMonitoringUsecase(BaseProcessor):
|
|
|
527
296
|
start_timestamp = self._get_start_timestamp_str(stream_info, precision=False)
|
|
528
297
|
high_precision_start_timestamp = self._get_current_timestamp_str(stream_info, precision=True)
|
|
529
298
|
high_precision_reset_timestamp = self._get_start_timestamp_str(stream_info, precision=True)
|
|
530
|
-
|
|
531
299
|
total_counts = [{"category": cat, "count": count} for cat, count in total_counts_dict.items() if count > 0]
|
|
532
300
|
current_counts = [{"category": cat, "count": count} for cat, count in per_category_count.items() if count > 0 or total_detections > 0]
|
|
533
|
-
|
|
534
301
|
detections = []
|
|
535
302
|
for detection in counting_summary.get("detections", []):
|
|
536
303
|
bbox = detection.get("bounding_box", {})
|
|
@@ -547,7 +314,6 @@ class DroneTrafficMonitoringUsecase(BaseProcessor):
|
|
|
547
314
|
else:
|
|
548
315
|
detection_obj = self.create_detection_object(category, bbox)
|
|
549
316
|
detections.append(detection_obj)
|
|
550
|
-
|
|
551
317
|
alert_settings = []
|
|
552
318
|
if config.alert_config and hasattr(config.alert_config, 'alert_type'):
|
|
553
319
|
alert_settings.append({
|
|
@@ -556,58 +322,22 @@ class DroneTrafficMonitoringUsecase(BaseProcessor):
|
|
|
556
322
|
"threshold_level": config.alert_config.count_thresholds if hasattr(config.alert_config, 'count_thresholds') else {},
|
|
557
323
|
"ascending": True,
|
|
558
324
|
"settings": {t: v for t, v in zip(getattr(config.alert_config, 'alert_type', ['Default']),
|
|
559
|
-
|
|
325
|
+
getattr(config.alert_config, 'alert_value', ['JSON']))}
|
|
560
326
|
})
|
|
561
|
-
|
|
562
327
|
human_text_lines = [f"Tracking Statistics:"]
|
|
563
328
|
human_text_lines.append(f"CURRENT FRAME @ {current_timestamp}")
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
human_text_lines.append("\tZones (current):")
|
|
567
|
-
for zone_name, zone_data in zone_analysis.items():
|
|
568
|
-
current_count = 0
|
|
569
|
-
if isinstance(zone_data, dict):
|
|
570
|
-
if "current_count" in zone_data:
|
|
571
|
-
current_count = zone_data.get("current_count", 0)
|
|
572
|
-
else:
|
|
573
|
-
counts_dict = zone_data.get("original_counts") if isinstance(zone_data.get("original_counts"), dict) else zone_data
|
|
574
|
-
current_count = counts_dict.get(
|
|
575
|
-
"total",
|
|
576
|
-
sum(v for v in counts_dict.values() if isinstance(v, (int, float)))
|
|
577
|
-
)
|
|
578
|
-
human_text_lines.append(f"\t{zone_name}: {int(current_count)}")
|
|
579
|
-
else:
|
|
580
|
-
for cat, count in per_category_count.items():
|
|
581
|
-
human_text_lines.append(f"\t{cat}: {count}")
|
|
329
|
+
for cat, count in per_category_count.items():
|
|
330
|
+
human_text_lines.append(f"\t{cat}: {count}")
|
|
582
331
|
human_text_lines.append(f"TOTAL SINCE {start_timestamp}")
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
for zone_name, zone_data in zone_analysis.items():
|
|
587
|
-
total_count = 0
|
|
588
|
-
if isinstance(zone_data, dict):
|
|
589
|
-
# Prefer the numeric cumulative total if available
|
|
590
|
-
if "total_count" in zone_data and isinstance(zone_data.get("total_count"), (int, float)):
|
|
591
|
-
total_count = zone_data.get("total_count", 0)
|
|
592
|
-
# Fallback: compute from list of total_track_ids if present
|
|
593
|
-
elif "total_track_ids" in zone_data and isinstance(zone_data.get("total_track_ids"), list):
|
|
594
|
-
total_count = len(zone_data.get("total_track_ids", []))
|
|
595
|
-
else:
|
|
596
|
-
# Last resort: try to sum numeric values present
|
|
597
|
-
counts_dict = zone_data if isinstance(zone_data, dict) else {}
|
|
598
|
-
total_count = sum(v for v in counts_dict.values() if isinstance(v, (int, float)))
|
|
599
|
-
human_text_lines.append(f"\t{zone_name}: {int(total_count)}")
|
|
600
|
-
else:
|
|
601
|
-
for cat, count in total_counts_dict.items():
|
|
602
|
-
if count > 0:
|
|
603
|
-
human_text_lines.append(f"\t{cat}: {count}")
|
|
332
|
+
for cat, count in total_counts_dict.items():
|
|
333
|
+
if count > 0:
|
|
334
|
+
human_text_lines.append(f"\t{cat}: {count}")
|
|
604
335
|
if alerts:
|
|
605
336
|
for alert in alerts:
|
|
606
337
|
human_text_lines.append(f"Alerts: {alert.get('settings', {})} sent @ {current_timestamp}")
|
|
607
338
|
else:
|
|
608
339
|
human_text_lines.append("Alerts: None")
|
|
609
340
|
human_text = "\n".join(human_text_lines)
|
|
610
|
-
|
|
611
341
|
reset_settings = [{"interval_type": "daily", "reset_time": {"value": 9, "time_unit": "hour"}}]
|
|
612
342
|
tracking_stat = self.create_tracking_stats(
|
|
613
343
|
total_counts=total_counts,
|
|
@@ -624,29 +354,24 @@ class DroneTrafficMonitoringUsecase(BaseProcessor):
|
|
|
624
354
|
tracking_stats.append(tracking_stat)
|
|
625
355
|
return tracking_stats
|
|
626
356
|
|
|
627
|
-
def _generate_business_analytics(self, counting_summary: Dict,
|
|
357
|
+
def _generate_business_analytics(self, counting_summary: Dict, alerts: Any, config: VehiclePeopleDroneMonitoringConfig,
|
|
628
358
|
stream_info: Optional[Dict[str, Any]] = None, is_empty=False) -> List[Dict]:
|
|
629
359
|
if is_empty:
|
|
630
360
|
return []
|
|
631
361
|
|
|
632
|
-
def _generate_summary(self, summary: dict,
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
""
|
|
636
|
-
lines = []
|
|
637
|
-
lines.append("Application Name: "+self.CASE_TYPE)
|
|
638
|
-
lines.append("Application Version: "+self.CASE_VERSION)
|
|
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
|
|
639
366
|
if len(incidents) > 0:
|
|
640
|
-
lines
|
|
367
|
+
lines["Incidents:"] = f"\n\t{incidents[0].get('human_text', 'No incidents detected')}\n"
|
|
641
368
|
if len(tracking_stats) > 0:
|
|
642
|
-
lines
|
|
369
|
+
lines["Tracking Statistics:"] = f"\t{tracking_stats[0].get('human_text', 'No tracking statistics detected')}\n"
|
|
643
370
|
if len(business_analytics) > 0:
|
|
644
|
-
lines
|
|
645
|
-
|
|
371
|
+
lines["Business Analytics:"] = f"\t{business_analytics[0].get('human_text', 'No business analytics detected')}\n"
|
|
646
372
|
if len(incidents) == 0 and len(tracking_stats) == 0 and len(business_analytics) == 0:
|
|
647
|
-
lines
|
|
648
|
-
|
|
649
|
-
return ["\n".join(lines)]
|
|
373
|
+
lines["Summary"] = "No Summary Data"
|
|
374
|
+
return [lines]
|
|
650
375
|
|
|
651
376
|
def _get_track_ids_info(self, detections: list) -> Dict[str, Any]:
|
|
652
377
|
frame_track_ids = set()
|
|
@@ -670,7 +395,6 @@ class DroneTrafficMonitoringUsecase(BaseProcessor):
|
|
|
670
395
|
if not hasattr(self, "_per_category_total_track_ids"):
|
|
671
396
|
self._per_category_total_track_ids = {cat: set() for cat in self.target_categories}
|
|
672
397
|
self._current_frame_track_ids = {cat: set() for cat in self.target_categories}
|
|
673
|
-
|
|
674
398
|
for det in detections:
|
|
675
399
|
cat = det.get("category")
|
|
676
400
|
raw_track_id = det.get("track_id")
|
|
@@ -696,32 +420,23 @@ class DroneTrafficMonitoringUsecase(BaseProcessor):
|
|
|
696
420
|
return f"{hours:02d}:{minutes:02d}:{seconds:.1f}"
|
|
697
421
|
|
|
698
422
|
def _get_current_timestamp_str(self, stream_info: Optional[Dict[str, Any]], precision=False, frame_id: Optional[str]=None) -> str:
|
|
699
|
-
"""Get formatted current timestamp based on stream type."""
|
|
700
423
|
if not stream_info:
|
|
701
424
|
return "00:00:00.00"
|
|
702
|
-
|
|
703
425
|
if precision:
|
|
704
426
|
if stream_info.get("input_settings", {}).get("start_frame", "na") != "na":
|
|
705
427
|
if frame_id:
|
|
706
428
|
start_time = int(frame_id)/stream_info.get("input_settings", {}).get("original_fps", 30)
|
|
707
429
|
else:
|
|
708
430
|
start_time = stream_info.get("input_settings", {}).get("start_frame", 30)/stream_info.get("input_settings", {}).get("original_fps", 30)
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
return self._format_timestamp(stream_info.get("input_settings", {}).get("stream_time", "NA"))
|
|
431
|
+
return self._format_timestamp_for_video(start_time)
|
|
713
432
|
else:
|
|
714
433
|
return datetime.now(timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC")
|
|
715
|
-
|
|
716
434
|
if stream_info.get("input_settings", {}).get("start_frame", "na") != "na":
|
|
717
435
|
if frame_id:
|
|
718
436
|
start_time = int(frame_id)/stream_info.get("input_settings", {}).get("original_fps", 30)
|
|
719
437
|
else:
|
|
720
438
|
start_time = stream_info.get("input_settings", {}).get("start_frame", 30)/stream_info.get("input_settings", {}).get("original_fps", 30)
|
|
721
|
-
|
|
722
|
-
stream_time_str = self._format_timestamp_for_video(start_time)
|
|
723
|
-
|
|
724
|
-
return self._format_timestamp(stream_info.get("input_settings", {}).get("stream_time", "NA"))
|
|
439
|
+
return self._format_timestamp_for_video(start_time)
|
|
725
440
|
else:
|
|
726
441
|
stream_time_str = stream_info.get("input_settings", {}).get("stream_info", {}).get("stream_time", "")
|
|
727
442
|
if stream_time_str:
|
|
@@ -736,31 +451,16 @@ class DroneTrafficMonitoringUsecase(BaseProcessor):
|
|
|
736
451
|
return self._format_timestamp_for_stream(time.time())
|
|
737
452
|
|
|
738
453
|
def _get_start_timestamp_str(self, stream_info: Optional[Dict[str, Any]], precision=False) -> str:
|
|
739
|
-
"""Get formatted start timestamp for 'TOTAL SINCE' based on stream type."""
|
|
740
454
|
if not stream_info:
|
|
741
455
|
return "00:00:00"
|
|
742
|
-
|
|
743
456
|
if precision:
|
|
744
|
-
if
|
|
745
|
-
|
|
746
|
-
return self._format_timestamp(self.start_timer)
|
|
747
|
-
elif stream_info.get("input_settings", {}).get("start_frame", "na") == 1:
|
|
748
|
-
self.start_timer = stream_info.get("input_settings", {}).get("stream_time", "NA")
|
|
749
|
-
return self._format_timestamp(self.start_timer)
|
|
457
|
+
if stream_info.get("input_settings", {}).get("start_frame", "na") != "na":
|
|
458
|
+
return "00:00:00"
|
|
750
459
|
else:
|
|
751
|
-
return
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
self.start_timer = stream_info.get("input_settings", {}).get("stream_time", "NA")
|
|
755
|
-
return self._format_timestamp(self.start_timer)
|
|
756
|
-
elif stream_info.get("input_settings", {}).get("start_frame", "na") == 1:
|
|
757
|
-
self.start_timer = stream_info.get("input_settings", {}).get("stream_time", "NA")
|
|
758
|
-
return self._format_timestamp(self.start_timer)
|
|
759
|
-
|
|
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"
|
|
760
463
|
else:
|
|
761
|
-
if self.start_timer is not None:
|
|
762
|
-
return self._format_timestamp(self.start_timer)
|
|
763
|
-
|
|
764
464
|
if self._tracking_start_time is None:
|
|
765
465
|
stream_time_str = stream_info.get("input_settings", {}).get("stream_info", {}).get("stream_time", "")
|
|
766
466
|
if stream_time_str:
|
|
@@ -772,57 +472,10 @@ class DroneTrafficMonitoringUsecase(BaseProcessor):
|
|
|
772
472
|
self._tracking_start_time = time.time()
|
|
773
473
|
else:
|
|
774
474
|
self._tracking_start_time = time.time()
|
|
775
|
-
|
|
776
475
|
dt = datetime.fromtimestamp(self._tracking_start_time, tz=timezone.utc)
|
|
777
476
|
dt = dt.replace(minute=0, second=0, microsecond=0)
|
|
778
477
|
return dt.strftime('%Y:%m:%d %H:%M:%S')
|
|
779
478
|
|
|
780
|
-
def _format_timestamp(self, timestamp: Any) -> str:
|
|
781
|
-
"""Format a timestamp so that exactly two digits follow the decimal point (milliseconds).
|
|
782
|
-
|
|
783
|
-
The input can be either:
|
|
784
|
-
1. A numeric Unix timestamp (``float`` / ``int``) – it will first be converted to a
|
|
785
|
-
string in the format ``YYYY-MM-DD-HH:MM:SS.ffffff UTC``.
|
|
786
|
-
2. A string already following the same layout.
|
|
787
|
-
|
|
788
|
-
The returned value preserves the overall format of the input but truncates or pads
|
|
789
|
-
the fractional seconds portion to **exactly two digits**.
|
|
790
|
-
|
|
791
|
-
Example
|
|
792
|
-
-------
|
|
793
|
-
>>> self._format_timestamp("2025-08-19-04:22:47.187574 UTC")
|
|
794
|
-
'2025-08-19-04:22:47.18 UTC'
|
|
795
|
-
"""
|
|
796
|
-
|
|
797
|
-
# Convert numeric timestamps to the expected string representation first
|
|
798
|
-
if isinstance(timestamp, (int, float)):
|
|
799
|
-
timestamp = datetime.fromtimestamp(timestamp, timezone.utc).strftime(
|
|
800
|
-
'%Y-%m-%d-%H:%M:%S.%f UTC'
|
|
801
|
-
)
|
|
802
|
-
|
|
803
|
-
# Ensure we are working with a string from here on
|
|
804
|
-
if not isinstance(timestamp, str):
|
|
805
|
-
return str(timestamp)
|
|
806
|
-
|
|
807
|
-
# If there is no fractional component, simply return the original string
|
|
808
|
-
if '.' not in timestamp:
|
|
809
|
-
return timestamp
|
|
810
|
-
|
|
811
|
-
# Split out the main portion (up to the decimal point)
|
|
812
|
-
main_part, fractional_and_suffix = timestamp.split('.', 1)
|
|
813
|
-
|
|
814
|
-
# Separate fractional digits from the suffix (typically ' UTC')
|
|
815
|
-
if ' ' in fractional_and_suffix:
|
|
816
|
-
fractional_part, suffix = fractional_and_suffix.split(' ', 1)
|
|
817
|
-
suffix = ' ' + suffix # Re-attach the space removed by split
|
|
818
|
-
else:
|
|
819
|
-
fractional_part, suffix = fractional_and_suffix, ''
|
|
820
|
-
|
|
821
|
-
# Guarantee exactly two digits for the fractional part
|
|
822
|
-
fractional_part = (fractional_part + '00')[:2]
|
|
823
|
-
|
|
824
|
-
return f"{main_part}.{fractional_part}{suffix}"
|
|
825
|
-
|
|
826
479
|
def _count_categories(self, detections: list, config: VehiclePeopleDroneMonitoringConfig) -> dict:
|
|
827
480
|
counts = {}
|
|
828
481
|
for det in detections:
|
|
@@ -867,7 +520,6 @@ class DroneTrafficMonitoringUsecase(BaseProcessor):
|
|
|
867
520
|
values = [v for v in bbox.values() if isinstance(v, (int, float))]
|
|
868
521
|
return values[:4] if len(values) >= 4 else []
|
|
869
522
|
return []
|
|
870
|
-
|
|
871
523
|
l1 = _bbox_to_list(box1)
|
|
872
524
|
l2 = _bbox_to_list(box2)
|
|
873
525
|
if len(l1) < 4 or len(l2) < 4:
|
|
@@ -921,6 +573,9 @@ class DroneTrafficMonitoringUsecase(BaseProcessor):
|
|
|
921
573
|
}
|
|
922
574
|
return canonical_id
|
|
923
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
|
+
|
|
924
579
|
def _get_tracking_start_time(self) -> str:
|
|
925
580
|
if self._tracking_start_time is None:
|
|
926
581
|
return "N/A"
|