matrice 1.0.99178__py3-none-any.whl → 1.0.99180__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/deploy/utils/post_processing/usecases/pothole_segmentation.py +141 -220
- {matrice-1.0.99178.dist-info → matrice-1.0.99180.dist-info}/METADATA +1 -1
- {matrice-1.0.99178.dist-info → matrice-1.0.99180.dist-info}/RECORD +6 -6
- {matrice-1.0.99178.dist-info → matrice-1.0.99180.dist-info}/WHEEL +0 -0
- {matrice-1.0.99178.dist-info → matrice-1.0.99180.dist-info}/licenses/LICENSE.txt +0 -0
- {matrice-1.0.99178.dist-info → matrice-1.0.99180.dist-info}/top_level.txt +0 -0
@@ -10,7 +10,6 @@ from typing import Any, Dict, List, Optional
|
|
10
10
|
from dataclasses import asdict
|
11
11
|
import time
|
12
12
|
from datetime import datetime, timezone
|
13
|
-
import copy # Added for deep copying detections to preserve original masks
|
14
13
|
|
15
14
|
from ..core.base import BaseProcessor, ProcessingContext, ProcessingResult, ConfigProtocol, ResultFormat
|
16
15
|
from ..utils import (
|
@@ -40,7 +39,7 @@ class PotholeConfig(BaseConfig):
|
|
40
39
|
smoothing_confidence_range_factor: float = 0.5
|
41
40
|
|
42
41
|
#confidence thresholds
|
43
|
-
confidence_threshold: float = 0.
|
42
|
+
confidence_threshold: float = 0.3
|
44
43
|
|
45
44
|
usecase_categories: List[str] = field(
|
46
45
|
default_factory=lambda: ['pothole']
|
@@ -54,28 +53,27 @@ class PotholeConfig(BaseConfig):
|
|
54
53
|
|
55
54
|
index_to_category: Optional[Dict[int, str]] = field(
|
56
55
|
default_factory=lambda: {
|
57
|
-
|
58
|
-
|
56
|
+
0: 'pothole'
|
59
57
|
}
|
60
58
|
)
|
61
59
|
|
62
60
|
|
63
61
|
class PotholeSegmentationUseCase(BaseProcessor):
|
64
|
-
|
65
|
-
# Human-friendly display names for categories
|
62
|
+
# Human-friendly display names for categories
|
66
63
|
CATEGORY_DISPLAY = {
|
67
64
|
"pothole": "pothole"
|
68
65
|
}
|
66
|
+
|
69
67
|
def __init__(self):
|
70
68
|
super().__init__("pothole_segmentation")
|
71
69
|
self.category = "infrastructure"
|
72
70
|
|
71
|
+
self.CASE_TYPE: Optional[str] = 'pothole_segmentation'
|
72
|
+
self.CASE_VERSION: Optional[str] = '1.3'
|
73
|
+
|
73
74
|
# List of categories to track
|
74
75
|
self.target_categories = ["pothole"]
|
75
76
|
|
76
|
-
self.CASE_TYPE: Optional[str] = 'Pothole_detection'
|
77
|
-
self.CASE_VERSION: Optional[str] = '1.3'
|
78
|
-
|
79
77
|
# Initialize smoothing tracker
|
80
78
|
self.smoothing_tracker = None
|
81
79
|
|
@@ -89,13 +87,6 @@ class PotholeSegmentationUseCase(BaseProcessor):
|
|
89
87
|
# Track start time for "TOTAL SINCE" calculation
|
90
88
|
self._tracking_start_time = None
|
91
89
|
|
92
|
-
# ------------------------------------------------------------------ #
|
93
|
-
# Canonical tracking aliasing to avoid duplicate counts #
|
94
|
-
# ------------------------------------------------------------------ #
|
95
|
-
# Maps raw tracker-generated IDs to stable canonical IDs that persist
|
96
|
-
# even if the underlying tracker re-assigns a new ID after a short
|
97
|
-
# interruption. This mirrors the logic used in people_counting to
|
98
|
-
# provide accurate unique counting.
|
99
90
|
self._track_aliases: Dict[Any, Any] = {}
|
100
91
|
self._canonical_tracks: Dict[Any, Dict[str, Any]] = {}
|
101
92
|
# Tunable parameters – adjust if necessary for specific scenarios
|
@@ -108,7 +99,7 @@ class PotholeSegmentationUseCase(BaseProcessor):
|
|
108
99
|
def process(self, data: Any, config: ConfigProtocol, context: Optional[ProcessingContext] = None,
|
109
100
|
stream_info: Optional[Dict[str, Any]] = None) -> ProcessingResult:
|
110
101
|
"""
|
111
|
-
Main entry point for
|
102
|
+
Main entry point for post-processing.
|
112
103
|
Applies category mapping, smoothing, counting, alerting, and summary generation.
|
113
104
|
Returns a ProcessingResult with all relevant outputs.
|
114
105
|
"""
|
@@ -124,42 +115,39 @@ class PotholeSegmentationUseCase(BaseProcessor):
|
|
124
115
|
input_format = match_results_structure(data)
|
125
116
|
context.input_format = input_format
|
126
117
|
context.confidence_threshold = config.confidence_threshold
|
127
|
-
|
128
|
-
# Step 1: Confidence filtering
|
118
|
+
|
129
119
|
if config.confidence_threshold is not None:
|
130
120
|
processed_data = filter_by_confidence(data, config.confidence_threshold)
|
121
|
+
self.logger.debug(f"Applied confidence filtering with threshold {config.confidence_threshold}")
|
131
122
|
else:
|
132
123
|
processed_data = data
|
124
|
+
|
133
125
|
self.logger.debug(f"Did not apply confidence filtering with threshold since nothing was provided")
|
134
126
|
|
135
127
|
# Step 2: Apply category mapping if provided
|
136
128
|
if config.index_to_category:
|
137
129
|
processed_data = apply_category_mapping(processed_data, config.index_to_category)
|
130
|
+
self.logger.debug("Applied category mapping")
|
138
131
|
|
139
|
-
# Step 3: Category filtering
|
140
132
|
if config.target_categories:
|
141
133
|
processed_data = [d for d in processed_data if d.get('category') in self.target_categories]
|
134
|
+
self.logger.debug(f"Applied category filtering")
|
142
135
|
|
143
|
-
#
|
144
|
-
# Deep-copy detections so that we preserve the original masks before any
|
145
|
-
# smoothing/tracking logic potentially removes them.
|
146
|
-
raw_processed_data = [copy.deepcopy(det) for det in processed_data]
|
136
|
+
# Apply bbox smoothing if enabled
|
147
137
|
if config.enable_smoothing:
|
148
138
|
if self.smoothing_tracker is None:
|
149
139
|
smoothing_config = BBoxSmoothingConfig(
|
150
140
|
smoothing_algorithm=config.smoothing_algorithm,
|
151
141
|
window_size=config.smoothing_window_size,
|
152
142
|
cooldown_frames=config.smoothing_cooldown_frames,
|
153
|
-
confidence_threshold=config.confidence_threshold,
|
143
|
+
confidence_threshold=config.confidence_threshold, # Use mask threshold as default
|
154
144
|
confidence_range_factor=config.smoothing_confidence_range_factor,
|
155
145
|
enable_smoothing=True
|
156
146
|
)
|
157
147
|
self.smoothing_tracker = BBoxSmoothingTracker(smoothing_config)
|
158
|
-
|
159
148
|
processed_data = bbox_smoothing(processed_data, self.smoothing_tracker.config, self.smoothing_tracker)
|
160
|
-
# Restore masks after smoothing
|
161
149
|
|
162
|
-
#
|
150
|
+
# Advanced tracking (BYTETracker-like)
|
163
151
|
try:
|
164
152
|
from ..advanced_tracker import AdvancedTracker
|
165
153
|
from ..advanced_tracker.config import TrackerConfig
|
@@ -168,24 +156,19 @@ class PotholeSegmentationUseCase(BaseProcessor):
|
|
168
156
|
if self.tracker is None:
|
169
157
|
tracker_config = TrackerConfig()
|
170
158
|
self.tracker = AdvancedTracker(tracker_config)
|
171
|
-
self.logger.info("Initialized AdvancedTracker for
|
172
|
-
|
159
|
+
self.logger.info("Initialized AdvancedTracker for Monitoring and tracking")
|
160
|
+
|
161
|
+
# The tracker expects the data in the same format as input
|
162
|
+
# It will add track_id and frame_id to each detection
|
173
163
|
processed_data = self.tracker.update(processed_data)
|
164
|
+
|
174
165
|
except Exception as e:
|
175
166
|
# If advanced tracker fails, fallback to unsmoothed detections
|
176
167
|
self.logger.warning(f"AdvancedTracker failed: {e}")
|
177
168
|
|
178
|
-
# Update
|
169
|
+
# Update tracking state for total count per label
|
179
170
|
self._update_tracking_state(processed_data)
|
180
171
|
|
181
|
-
# ------------------------------------------------------------------ #
|
182
|
-
# Re-attach segmentation masks that were present in the original input
|
183
|
-
# but may have been stripped during smoothing/tracking. We match each
|
184
|
-
# processed detection back to the raw detection with the highest IoU
|
185
|
-
# and copy over its "masks" field (if available).
|
186
|
-
# ------------------------------------------------------------------ #
|
187
|
-
processed_data = self._attach_masks_to_detections(processed_data, raw_processed_data)
|
188
|
-
|
189
172
|
# Update frame counter
|
190
173
|
self._total_frame_counter += 1
|
191
174
|
|
@@ -200,19 +183,19 @@ class PotholeSegmentationUseCase(BaseProcessor):
|
|
200
183
|
frame_number = start_frame
|
201
184
|
|
202
185
|
# Compute summaries and alerts
|
203
|
-
general_counting_summary = calculate_counting_summary(data)
|
204
|
-
counting_summary = self._count_categories(processed_data, config)
|
205
|
-
# Add total unique
|
206
|
-
total_counts = self.get_total_counts()
|
207
|
-
counting_summary['total_counts'] = total_counts
|
208
|
-
|
186
|
+
general_counting_summary = calculate_counting_summary(data)
|
187
|
+
counting_summary = self._count_categories(processed_data, config)
|
188
|
+
# Add total unique counts after tracking using only local state
|
189
|
+
total_counts = self.get_total_counts()
|
190
|
+
counting_summary['total_counts'] = total_counts
|
191
|
+
|
209
192
|
alerts = self._check_alerts(counting_summary, frame_number, config)
|
210
193
|
predictions = self._extract_predictions(processed_data)
|
211
194
|
|
212
|
-
# Step: Generate structured
|
195
|
+
# Step: Generate structured incidents, tracking stats and business analytics with frame-based keys
|
213
196
|
incidents_list = self._generate_incidents(counting_summary, alerts, config, frame_number, stream_info)
|
214
|
-
tracking_stats_list = self._generate_tracking_stats(counting_summary, alerts, config, frame_number,stream_info)
|
215
|
-
business_analytics_list = self._generate_business_analytics(counting_summary, alerts, config,
|
197
|
+
tracking_stats_list = self._generate_tracking_stats(counting_summary, alerts, config, frame_number, stream_info)
|
198
|
+
business_analytics_list = self._generate_business_analytics(counting_summary, alerts, config, stream_info, is_empty=True)
|
216
199
|
summary_list = self._generate_summary(counting_summary, incidents_list, tracking_stats_list, business_analytics_list, alerts)
|
217
200
|
|
218
201
|
# Extract frame-based dictionaries from the lists
|
@@ -226,8 +209,9 @@ class PotholeSegmentationUseCase(BaseProcessor):
|
|
226
209
|
"business_analytics": business_analytics,
|
227
210
|
"alerts": alerts,
|
228
211
|
"human_text": summary}
|
229
|
-
|
230
|
-
|
212
|
+
}
|
213
|
+
|
214
|
+
|
231
215
|
context.mark_completed()
|
232
216
|
|
233
217
|
# Build result object following the new pattern
|
@@ -289,8 +273,8 @@ class PotholeSegmentationUseCase(BaseProcessor):
|
|
289
273
|
"threshold_level": threshold,
|
290
274
|
"ascending": get_trend(self._ascending_alert_list, lookback=900, threshold=0.8),
|
291
275
|
"settings": {t: v for t, v in zip(getattr(config.alert_config, 'alert_type', ['Default']) if hasattr(config.alert_config, 'alert_type') else ['Default'],
|
292
|
-
|
293
|
-
|
276
|
+
getattr(config.alert_config, 'alert_value', ['JSON']) if hasattr(config.alert_config, 'alert_value') else ['JSON'])
|
277
|
+
}
|
294
278
|
})
|
295
279
|
elif category in summary.get("per_category_count", {}):
|
296
280
|
count = summary.get("per_category_count", {})[category]
|
@@ -302,27 +286,25 @@ class PotholeSegmentationUseCase(BaseProcessor):
|
|
302
286
|
"threshold_level": threshold,
|
303
287
|
"ascending": get_trend(self._ascending_alert_list, lookback=900, threshold=0.8),
|
304
288
|
"settings": {t: v for t, v in zip(getattr(config.alert_config, 'alert_type', ['Default']) if hasattr(config.alert_config, 'alert_type') else ['Default'],
|
305
|
-
|
306
|
-
|
289
|
+
getattr(config.alert_config, 'alert_value', ['JSON']) if hasattr(config.alert_config, 'alert_value') else ['JSON'])
|
290
|
+
}
|
307
291
|
})
|
308
292
|
else:
|
309
293
|
pass
|
310
294
|
return alerts
|
311
295
|
|
312
296
|
def _generate_incidents(self, counting_summary: Dict, alerts: List, config: PotholeConfig,
|
313
|
-
|
297
|
+
frame_number: Optional[int] = None, stream_info: Optional[Dict[str, Any]] = None) -> List[
|
314
298
|
Dict]:
|
315
|
-
"""Generate structured
|
316
|
-
|
317
|
-
|
318
|
-
frame_key = str(frame_number) if frame_number is not None else "current_frame"
|
319
|
-
incidents=[]
|
299
|
+
"""Generate structured incidents for the output format with frame-based keys."""
|
300
|
+
|
301
|
+
incidents = []
|
320
302
|
total_detections = counting_summary.get("total_count", 0)
|
321
303
|
current_timestamp = self._get_current_timestamp_str(stream_info)
|
322
304
|
camera_info = self.get_camera_info_from_stream(stream_info)
|
323
305
|
|
324
306
|
self._ascending_alert_list = self._ascending_alert_list[-900:] if len(self._ascending_alert_list) > 900 else self._ascending_alert_list
|
325
|
-
|
307
|
+
|
326
308
|
if total_detections > 0:
|
327
309
|
# Determine event level based on thresholds
|
328
310
|
level = "low"
|
@@ -370,7 +352,7 @@ class PotholeSegmentationUseCase(BaseProcessor):
|
|
370
352
|
intensity = min(10.0, total_detections / 3.0)
|
371
353
|
self._ascending_alert_list.append(0)
|
372
354
|
|
373
|
-
|
355
|
+
# Generate human text in new format
|
374
356
|
human_text_lines = [f"INCIDENTS DETECTED @ {current_timestamp}:"]
|
375
357
|
human_text_lines.append(f"\tSeverity Level: {(self.CASE_TYPE,level)}")
|
376
358
|
human_text = "\n".join(human_text_lines)
|
@@ -388,9 +370,9 @@ class PotholeSegmentationUseCase(BaseProcessor):
|
|
388
370
|
})
|
389
371
|
|
390
372
|
event= self.create_incident(incident_id=self.CASE_TYPE+'_'+str(frame_number), incident_type=self.CASE_TYPE,
|
391
|
-
|
392
|
-
|
393
|
-
|
373
|
+
severity_level=level, human_text=human_text, camera_info=camera_info, alerts=alerts, alert_settings=alert_settings,
|
374
|
+
start_time=start_timestamp, end_time=self.current_incident_end_timestamp,
|
375
|
+
level_settings= {"low": 1, "medium": 3, "significant":4, "critical": 7})
|
394
376
|
incidents.append(event)
|
395
377
|
|
396
378
|
else:
|
@@ -402,22 +384,23 @@ class PotholeSegmentationUseCase(BaseProcessor):
|
|
402
384
|
def _generate_tracking_stats(
|
403
385
|
self,
|
404
386
|
counting_summary: Dict,
|
405
|
-
alerts:
|
387
|
+
alerts: List,
|
406
388
|
config: PotholeConfig,
|
407
389
|
frame_number: Optional[int] = None,
|
408
390
|
stream_info: Optional[Dict[str, Any]] = None
|
409
391
|
) -> List[Dict]:
|
410
|
-
"""Generate structured tracking stats
|
411
|
-
|
412
|
-
|
413
|
-
#
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
392
|
+
"""Generate structured tracking stats matching eg.json format."""
|
393
|
+
camera_info = self.get_camera_info_from_stream(stream_info)
|
394
|
+
|
395
|
+
# frame_key = str(frame_number) if frame_number is not None else "current_frame"
|
396
|
+
# tracking_stats = [{frame_key: []}]
|
397
|
+
# frame_tracking_stats = tracking_stats[0][frame_key]
|
398
|
+
tracking_stats = []
|
399
|
+
|
400
|
+
total_detections = counting_summary.get("total_count", 0) #CURRENT total count of all classes
|
401
|
+
total_counts_dict = counting_summary.get("total_counts", {}) #TOTAL cumulative counts per class
|
402
|
+
cumulative_total = sum(total_counts_dict.values()) if total_counts_dict else 0 #TOTAL combined cumulative count
|
403
|
+
per_category_count = counting_summary.get("per_category_count", {}) #CURRENT count per class
|
421
404
|
|
422
405
|
current_timestamp = self._get_current_timestamp_str(stream_info, precision=False)
|
423
406
|
start_timestamp = self._get_start_timestamp_str(stream_info, precision=False)
|
@@ -426,33 +409,16 @@ class PotholeSegmentationUseCase(BaseProcessor):
|
|
426
409
|
high_precision_start_timestamp = self._get_current_timestamp_str(stream_info, precision=True)
|
427
410
|
high_precision_reset_timestamp = self._get_start_timestamp_str(stream_info, precision=True)
|
428
411
|
|
429
|
-
|
430
|
-
|
412
|
+
|
413
|
+
# Build total_counts array in expected format
|
414
|
+
total_counts = []
|
415
|
+
for cat, count in total_counts_dict.items():
|
416
|
+
if count > 0:
|
417
|
+
total_counts.append({
|
418
|
+
"category": cat,
|
419
|
+
"count": count
|
420
|
+
})
|
431
421
|
|
432
|
-
# CURRENT FRAME section
|
433
|
-
human_text_lines.append(f"CURRENT FRAME @ {current_timestamp}:")
|
434
|
-
if total_detections > 0:
|
435
|
-
category_counts = [f"{count} {cat}" for cat, count in per_category_count.items()]
|
436
|
-
if len(category_counts) == 1:
|
437
|
-
detection_text = category_counts[0] + " detected"
|
438
|
-
elif len(category_counts) == 2:
|
439
|
-
detection_text = f"{category_counts[0]} and {category_counts[1]} detected"
|
440
|
-
else:
|
441
|
-
detection_text = f"{', '.join(category_counts[:-1])}, and {category_counts[-1]} detected"
|
442
|
-
human_text_lines.append(f"\t- {detection_text}")
|
443
|
-
else:
|
444
|
-
human_text_lines.append(f"\t- No detections")
|
445
|
-
|
446
|
-
human_text_lines.append("") # spacing
|
447
|
-
|
448
|
-
# TOTAL SINCE section
|
449
|
-
human_text_lines.append(f"TOTAL SINCE {start_timestamp}:")
|
450
|
-
human_text_lines.append(f"\t- Total Detected: {cumulative_total}")
|
451
|
-
# Add category-wise counts
|
452
|
-
if total_counts:
|
453
|
-
for cat, count in total_counts.items():
|
454
|
-
if count > 0: # Only include categories with non-zero counts
|
455
|
-
human_text_lines.append(f"\t- {cat}: {count}")
|
456
422
|
# Build current_counts array in expected format
|
457
423
|
current_counts = []
|
458
424
|
for cat, count in per_category_count.items():
|
@@ -462,9 +428,6 @@ class PotholeSegmentationUseCase(BaseProcessor):
|
|
462
428
|
"count": count
|
463
429
|
})
|
464
430
|
|
465
|
-
human_text = "\n".join(human_text_lines)
|
466
|
-
|
467
|
-
# Include detections with masks from counting_summary
|
468
431
|
# Prepare detections without confidence scores (as per eg.json)
|
469
432
|
detections = []
|
470
433
|
for detection in counting_summary.get("detections", []):
|
@@ -493,10 +456,24 @@ class PotholeSegmentationUseCase(BaseProcessor):
|
|
493
456
|
"threshold_level": config.alert_config.count_thresholds if hasattr(config.alert_config, 'count_thresholds') else {},
|
494
457
|
"ascending": True,
|
495
458
|
"settings": {t: v for t, v in zip(getattr(config.alert_config, 'alert_type', ['Default']) if hasattr(config.alert_config, 'alert_type') else ['Default'],
|
496
|
-
|
497
|
-
|
459
|
+
getattr(config.alert_config, 'alert_value', ['JSON']) if hasattr(config.alert_config, 'alert_value') else ['JSON'])
|
460
|
+
}
|
498
461
|
})
|
499
462
|
|
463
|
+
# Generate human_text in expected format
|
464
|
+
human_text_lines = [f"Tracking Statistics:"]
|
465
|
+
human_text_lines.append(f"CURRENT FRAME @ {current_timestamp}")
|
466
|
+
human_text_lines.append(f"Potholes Detected - ")
|
467
|
+
|
468
|
+
for cat, count in per_category_count.items():
|
469
|
+
human_text_lines.append(f"\t{cat}: {count}")
|
470
|
+
|
471
|
+
human_text_lines.append(f"TOTAL SINCE {start_timestamp}")
|
472
|
+
human_text_lines.append(f"Total Potholes Detected - ")
|
473
|
+
for cat, count in total_counts_dict.items():
|
474
|
+
if count > 0:
|
475
|
+
human_text_lines.append(f"\t{cat}: {count}")
|
476
|
+
|
500
477
|
if alerts:
|
501
478
|
for alert in alerts:
|
502
479
|
human_text_lines.append(f"Alerts: {alert.get('settings', {})} sent @ {current_timestamp}")
|
@@ -504,7 +481,7 @@ class PotholeSegmentationUseCase(BaseProcessor):
|
|
504
481
|
human_text_lines.append("Alerts: None")
|
505
482
|
|
506
483
|
human_text = "\n".join(human_text_lines)
|
507
|
-
reset_settings
|
484
|
+
reset_settings=[
|
508
485
|
{
|
509
486
|
"interval_type": "daily",
|
510
487
|
"reset_time": {
|
@@ -515,14 +492,14 @@ class PotholeSegmentationUseCase(BaseProcessor):
|
|
515
492
|
]
|
516
493
|
|
517
494
|
tracking_stat=self.create_tracking_stats(total_counts=total_counts, current_counts=current_counts,
|
518
|
-
|
519
|
-
|
520
|
-
|
495
|
+
detections=detections, human_text=human_text, camera_info=camera_info, alerts=alerts, alert_settings=alert_settings,
|
496
|
+
reset_settings=reset_settings, start_time=high_precision_start_timestamp ,
|
497
|
+
reset_time=high_precision_reset_timestamp)
|
521
498
|
|
522
499
|
tracking_stats.append(tracking_stat)
|
523
500
|
return tracking_stats
|
524
501
|
|
525
|
-
def _generate_business_analytics(self, counting_summary: Dict,
|
502
|
+
def _generate_business_analytics(self, counting_summary: Dict, alerts: Any, config: PotholeConfig, stream_info: Optional[Dict[str, Any]] = None, is_empty=False) -> List[Dict]:
|
526
503
|
"""Generate standardized business analytics for the agg_summary structure."""
|
527
504
|
if is_empty:
|
528
505
|
return []
|
@@ -553,36 +530,6 @@ class PotholeSegmentationUseCase(BaseProcessor):
|
|
553
530
|
|
554
531
|
return [lines]
|
555
532
|
|
556
|
-
|
557
|
-
def _count_categories(self, detections: list, config: PotholeConfig) -> dict:
|
558
|
-
"""
|
559
|
-
Count the number of detections per category and return a summary dict.
|
560
|
-
The detections list is expected to have 'track_id' (from tracker), 'category', 'bounding_box', 'masks', etc.
|
561
|
-
Output structure will include 'track_id' and 'masks' for each detection as per AdvancedTracker output.
|
562
|
-
"""
|
563
|
-
counts = {}
|
564
|
-
valid_detections = []
|
565
|
-
for det in detections:
|
566
|
-
cat = det.get('category', 'unknown')
|
567
|
-
if not all(k in det for k in ['category', 'confidence', 'bounding_box']): # Validate required fields
|
568
|
-
self.logger.warning(f"Skipping invalid detection: {det}")
|
569
|
-
continue
|
570
|
-
counts[cat] = counts.get(cat, 0) + 1
|
571
|
-
valid_detections.append({
|
572
|
-
"bounding_box": det.get("bounding_box"),
|
573
|
-
"category": det.get("category"),
|
574
|
-
"confidence": det.get("confidence"),
|
575
|
-
"track_id": det.get("track_id"),
|
576
|
-
"frame_id": det.get("frame_id"),
|
577
|
-
"masks": det.get("masks", det.get("mask", [])) # Include masks, fallback to empty list
|
578
|
-
})
|
579
|
-
self.logger.debug(f"Valid detections after filtering: {len(valid_detections)}")
|
580
|
-
return {
|
581
|
-
"total_count": sum(counts.values()),
|
582
|
-
"per_category_count": counts,
|
583
|
-
"detections": valid_detections
|
584
|
-
}
|
585
|
-
|
586
533
|
def _get_track_ids_info(self, detections: list) -> Dict[str, Any]:
|
587
534
|
"""
|
588
535
|
Get detailed information about track IDs (per frame).
|
@@ -636,37 +583,45 @@ class PotholeSegmentationUseCase(BaseProcessor):
|
|
636
583
|
"""
|
637
584
|
return {cat: len(ids) for cat, ids in getattr(self, '_per_category_total_track_ids', {}).items()}
|
638
585
|
|
639
|
-
def _format_timestamp_for_video(self, timestamp: float) -> str:
|
640
|
-
"""Format timestamp for video chunks (HH:MM:SS.ms format)."""
|
641
|
-
hours = int(timestamp // 3600)
|
642
|
-
minutes = int((timestamp % 3600) // 60)
|
643
|
-
seconds = timestamp % 60
|
644
|
-
return f"{hours:02d}:{minutes:02d}:{seconds:06.2f}"
|
645
586
|
|
646
587
|
def _format_timestamp_for_stream(self, timestamp: float) -> str:
|
647
588
|
"""Format timestamp for streams (YYYY:MM:DD HH:MM:SS format)."""
|
648
589
|
dt = datetime.fromtimestamp(timestamp, tz=timezone.utc)
|
649
590
|
return dt.strftime('%Y:%m:%d %H:%M:%S')
|
650
591
|
|
651
|
-
def
|
592
|
+
def _format_timestamp_for_video(self, timestamp: float) -> str:
|
593
|
+
"""Format timestamp for video chunks (HH:MM:SS.ms format)."""
|
594
|
+
hours = int(timestamp // 3600)
|
595
|
+
minutes = int((timestamp % 3600) // 60)
|
596
|
+
seconds = round(float(timestamp % 60),2)
|
597
|
+
return f"{hours:02d}:{minutes:02d}:{seconds:.1f}"
|
598
|
+
|
599
|
+
def _get_current_timestamp_str(self, stream_info: Optional[Dict[str, Any]], precision=False, frame_id: Optional[str]=None) -> str:
|
652
600
|
"""Get formatted current timestamp based on stream type."""
|
653
601
|
if not stream_info:
|
654
602
|
return "00:00:00.00"
|
655
|
-
|
603
|
+
# is_video_chunk = stream_info.get("input_settings", {}).get("is_video_chunk", False)
|
656
604
|
if precision:
|
657
|
-
if stream_info.get("input_settings", {}).get("
|
658
|
-
|
659
|
-
|
605
|
+
if stream_info.get("input_settings", {}).get("start_frame", "na") != "na":
|
606
|
+
if frame_id:
|
607
|
+
start_time = int(frame_id)/stream_info.get("input_settings", {}).get("original_fps", 30)
|
608
|
+
else:
|
609
|
+
start_time = stream_info.get("input_settings", {}).get("start_frame", 30)/stream_info.get("input_settings", {}).get("original_fps", 30)
|
610
|
+
stream_time_str = self._format_timestamp_for_video(start_time)
|
611
|
+
return stream_time_str
|
660
612
|
else:
|
661
613
|
return datetime.now(timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC")
|
662
614
|
|
663
|
-
if stream_info.get("input_settings", {}).get("
|
664
|
-
|
665
|
-
|
666
|
-
|
615
|
+
if stream_info.get("input_settings", {}).get("start_frame", "na") != "na":
|
616
|
+
if frame_id:
|
617
|
+
start_time = int(frame_id)/stream_info.get("input_settings", {}).get("original_fps", 30)
|
618
|
+
else:
|
619
|
+
start_time = stream_info.get("input_settings", {}).get("start_frame", 30)/stream_info.get("input_settings", {}).get("original_fps", 30)
|
620
|
+
stream_time_str = self._format_timestamp_for_video(start_time)
|
621
|
+
return stream_time_str
|
667
622
|
else:
|
668
623
|
# For streams, use stream_time from stream_info
|
669
|
-
stream_time_str = stream_info.get("stream_time", "")
|
624
|
+
stream_time_str = stream_info.get("input_settings", {}).get("stream_info", {}).get("stream_time", "")
|
670
625
|
if stream_time_str:
|
671
626
|
# Parse the high precision timestamp string to get timestamp
|
672
627
|
try:
|
@@ -685,23 +640,20 @@ class PotholeSegmentationUseCase(BaseProcessor):
|
|
685
640
|
"""Get formatted start timestamp for 'TOTAL SINCE' based on stream type."""
|
686
641
|
if not stream_info:
|
687
642
|
return "00:00:00"
|
688
|
-
|
689
|
-
is_video_chunk = stream_info.get("input_settings", {}).get("is_video_chunk", False)
|
690
643
|
if precision:
|
691
|
-
if stream_info.get("input_settings", {}).get("
|
644
|
+
if stream_info.get("input_settings", {}).get("start_frame", "na") != "na":
|
692
645
|
return "00:00:00"
|
693
646
|
else:
|
694
647
|
return datetime.now(timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC")
|
695
648
|
|
696
|
-
|
697
|
-
if stream_info.get("input_settings", {}).get("stream_type", "video_file") == "video_file":
|
649
|
+
if stream_info.get("input_settings", {}).get("start_frame", "na") != "na":
|
698
650
|
# If video format, start from 00:00:00
|
699
651
|
return "00:00:00"
|
700
652
|
else:
|
701
653
|
# For streams, use tracking start time or current time with minutes/seconds reset
|
702
654
|
if self._tracking_start_time is None:
|
703
655
|
# Try to extract timestamp from stream_time string
|
704
|
-
stream_time_str = stream_info.get("stream_time", "")
|
656
|
+
stream_time_str = stream_info.get("input_settings", {}).get("stream_info", {}).get("stream_time", "")
|
705
657
|
if stream_time_str:
|
706
658
|
try:
|
707
659
|
# Remove " UTC" suffix and parse
|
@@ -719,60 +671,31 @@ class PotholeSegmentationUseCase(BaseProcessor):
|
|
719
671
|
dt = dt.replace(minute=0, second=0, microsecond=0)
|
720
672
|
return dt.strftime('%Y:%m:%d %H:%M:%S')
|
721
673
|
|
722
|
-
|
723
|
-
# Helper to merge masks back into detections #
|
724
|
-
# ------------------------------------------------------------------ #
|
725
|
-
def _attach_masks_to_detections(
|
726
|
-
self,
|
727
|
-
processed_detections: List[Dict[str, Any]],
|
728
|
-
raw_detections: List[Dict[str, Any]],
|
729
|
-
iou_threshold: float = 0.5,
|
730
|
-
) -> List[Dict[str, Any]]:
|
674
|
+
def _count_categories(self, detections: list, config: PotholeConfig) -> dict:
|
731
675
|
"""
|
732
|
-
|
733
|
-
|
734
|
-
|
735
|
-
Matching between detections is performed using Intersection-over-Union
|
736
|
-
(IoU) of the bounding boxes. For each processed detection we select the
|
737
|
-
raw detection with the highest IoU above `iou_threshold` and copy its
|
738
|
-
`masks` (or `mask`) field. If no suitable match is found, the detection
|
739
|
-
keeps an empty list for `masks` to maintain a consistent schema.
|
676
|
+
Count the number of detections per category and return a summary dict.
|
677
|
+
The detections list is expected to have 'track_id' (from tracker), 'category', 'bounding_box', etc.
|
678
|
+
Output structure will include 'track_id' for each detection as per AdvancedTracker output.
|
740
679
|
"""
|
741
|
-
|
742
|
-
|
743
|
-
|
744
|
-
|
745
|
-
|
746
|
-
|
747
|
-
|
748
|
-
|
749
|
-
|
750
|
-
|
751
|
-
|
752
|
-
|
753
|
-
|
754
|
-
|
755
|
-
|
756
|
-
|
757
|
-
|
758
|
-
|
759
|
-
|
760
|
-
iou = self._compute_iou(det.get("bounding_box"), raw_det.get("bounding_box"))
|
761
|
-
if iou > best_iou:
|
762
|
-
best_iou = iou
|
763
|
-
best_idx = idx
|
764
|
-
|
765
|
-
if best_idx is not None and best_iou >= iou_threshold:
|
766
|
-
raw_det = raw_detections[best_idx]
|
767
|
-
masks = raw_det.get("masks", raw_det.get("mask"))
|
768
|
-
if masks is not None:
|
769
|
-
det["masks"] = masks
|
770
|
-
used_raw_indices.add(best_idx)
|
771
|
-
else:
|
772
|
-
# No adequate match – default to empty list to keep schema consistent.
|
773
|
-
det.setdefault("masks", ["EMPTY"])
|
774
|
-
|
775
|
-
return processed_detections
|
680
|
+
counts = {}
|
681
|
+
for det in detections:
|
682
|
+
cat = det.get('category', 'unknown')
|
683
|
+
counts[cat] = counts.get(cat, 0) + 1
|
684
|
+
# Each detection dict will now include 'track_id' (and possibly 'frame_id')
|
685
|
+
return {
|
686
|
+
"total_count": sum(counts.values()),
|
687
|
+
"per_category_count": counts,
|
688
|
+
"detections": [
|
689
|
+
{
|
690
|
+
"bounding_box": det.get("bounding_box"),
|
691
|
+
"category": det.get("category"),
|
692
|
+
"confidence": det.get("confidence"),
|
693
|
+
"track_id": det.get("track_id"),
|
694
|
+
"frame_id": det.get("frame_id")
|
695
|
+
}
|
696
|
+
for det in detections
|
697
|
+
]
|
698
|
+
}
|
776
699
|
|
777
700
|
def _extract_predictions(self, detections: list) -> List[Dict[str, Any]]:
|
778
701
|
"""
|
@@ -782,13 +705,11 @@ class PotholeSegmentationUseCase(BaseProcessor):
|
|
782
705
|
{
|
783
706
|
"category": det.get("category", "unknown"),
|
784
707
|
"confidence": det.get("confidence", 0.0),
|
785
|
-
"bounding_box": det.get("bounding_box", {})
|
786
|
-
"mask": det.get("mask", det.get("masks", None)) # Accept either key
|
708
|
+
"bounding_box": det.get("bounding_box", {})
|
787
709
|
}
|
788
710
|
for det in detections
|
789
711
|
]
|
790
712
|
|
791
|
-
|
792
713
|
# ------------------------------------------------------------------ #
|
793
714
|
# Canonical ID helpers #
|
794
715
|
# ------------------------------------------------------------------ #
|
@@ -189,7 +189,7 @@ matrice/deploy/utils/post_processing/usecases/pedestrian_detection.py,sha256=hPF
|
|
189
189
|
matrice/deploy/utils/post_processing/usecases/people_counting.py,sha256=mDJOwcrs9OO4jIbJVr_ItWvjjGP2mgGFYlrP3R-mH2E,76528
|
190
190
|
matrice/deploy/utils/post_processing/usecases/pipeline_detection.py,sha256=VsLTXMAqx0tRw7Olrxqx7SBLolZR7p2aFOrdSXLS-kE,30796
|
191
191
|
matrice/deploy/utils/post_processing/usecases/plaque_segmentation_img.py,sha256=d__a0PkkObYVoC-Q5-2bFVfeyKnQHtB5xVAKVOCeFyk,41925
|
192
|
-
matrice/deploy/utils/post_processing/usecases/pothole_segmentation.py,sha256=
|
192
|
+
matrice/deploy/utils/post_processing/usecases/pothole_segmentation.py,sha256=shxBDw9U59g2HPkNJTNPLBtXM10Zly_xC3QeB1oQrUE,39443
|
193
193
|
matrice/deploy/utils/post_processing/usecases/ppe_compliance.py,sha256=G9P9j9E9nfNJInHJxmK1Lb4daFBlG5hq0aqotTLvFFE,30146
|
194
194
|
matrice/deploy/utils/post_processing/usecases/price_tag_detection.py,sha256=09Tp6MGAHh95s-NSAp-4WC9iCc20sajWApuUBAvgXiQ,39880
|
195
195
|
matrice/deploy/utils/post_processing/usecases/road_lane_detection.py,sha256=V_KxwBtAHSNkyoH8sXw-U-P3J8ToXtX3ncc69gn6Tds,31591
|
@@ -227,8 +227,8 @@ matrice/deployment/camera_manager.py,sha256=ReBZqm1CNXRImKcbcZ4uWAT3TUWkof1D28oB
|
|
227
227
|
matrice/deployment/deployment.py,sha256=PLIUD-PxTaC2Zxb3Y12wUddsryV-OJetjCjLoSUh7S4,48103
|
228
228
|
matrice/deployment/inference_pipeline.py,sha256=bXLgd29ViA7o0c7YWLFJl1otBUQfTPb61jS6VawQB0Y,37918
|
229
229
|
matrice/deployment/streaming_gateway_manager.py,sha256=w5swGsuFVfZIdOm2ZuBHRHlRdYYJMLopLsf2gb91lQ8,20946
|
230
|
-
matrice-1.0.
|
231
|
-
matrice-1.0.
|
232
|
-
matrice-1.0.
|
233
|
-
matrice-1.0.
|
234
|
-
matrice-1.0.
|
230
|
+
matrice-1.0.99180.dist-info/licenses/LICENSE.txt,sha256=2bm9uFabQZ3Ykb_SaSU_uUbAj2-htc6WJQmS_65qD00,1073
|
231
|
+
matrice-1.0.99180.dist-info/METADATA,sha256=h-xuX7F-7q5n5QY1M_WYSAJrUGwjS96CSGO2dK-3iUA,14624
|
232
|
+
matrice-1.0.99180.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
233
|
+
matrice-1.0.99180.dist-info/top_level.txt,sha256=P97js8ur6o5ClRqMH3Cjoab_NqbJ6sOQ3rJmVzKBvMc,8
|
234
|
+
matrice-1.0.99180.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|