matrice 1.0.99180__py3-none-any.whl → 1.0.99181__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/license_plate_detection.py +18 -6
- matrice/deploy/utils/post_processing/usecases/pothole_segmentation.py +207 -109
- {matrice-1.0.99180.dist-info → matrice-1.0.99181.dist-info}/METADATA +1 -1
- {matrice-1.0.99180.dist-info → matrice-1.0.99181.dist-info}/RECORD +7 -7
- {matrice-1.0.99180.dist-info → matrice-1.0.99181.dist-info}/WHEEL +0 -0
- {matrice-1.0.99180.dist-info → matrice-1.0.99181.dist-info}/licenses/LICENSE.txt +0 -0
- {matrice-1.0.99180.dist-info → matrice-1.0.99181.dist-info}/top_level.txt +0 -0
@@ -149,14 +149,26 @@ class LicensePlateUseCase(BaseProcessor):
|
|
149
149
|
|
150
150
|
# Advanced tracking (BYTETracker-like)
|
151
151
|
try:
|
152
|
-
from ..advanced_tracker import AdvancedTracker
|
153
|
-
from ..advanced_tracker.config import TrackerConfig
|
154
|
-
|
155
|
-
# Create tracker instance if it doesn't exist (preserves state across frames)
|
156
152
|
if self.tracker is None:
|
157
|
-
|
153
|
+
# Configure tracker thresholds based on the use-case confidence threshold so that
|
154
|
+
# low-confidence detections (e.g. < 0.7) can still be initialised as tracks when
|
155
|
+
# the user passes a lower `confidence_threshold` in the post-processing config.
|
156
|
+
if config.confidence_threshold is not None:
|
157
|
+
tracker_config = TrackerConfig(
|
158
|
+
track_high_thresh=float(config.confidence_threshold),
|
159
|
+
# Allow even lower detections to participate in secondary association
|
160
|
+
track_low_thresh=max(0.05, float(config.confidence_threshold) / 2),
|
161
|
+
new_track_thresh=float(config.confidence_threshold)
|
162
|
+
)
|
163
|
+
else:
|
164
|
+
tracker_config = TrackerConfig()
|
158
165
|
self.tracker = AdvancedTracker(tracker_config)
|
159
|
-
self.logger.info(
|
166
|
+
self.logger.info(
|
167
|
+
"Initialized AdvancedTracker for Monitoring and tracking with thresholds: "
|
168
|
+
f"high={tracker_config.track_high_thresh}, "
|
169
|
+
f"low={tracker_config.track_low_thresh}, "
|
170
|
+
f"new={tracker_config.new_track_thresh}"
|
171
|
+
)
|
160
172
|
|
161
173
|
# The tracker expects the data in the same format as input
|
162
174
|
# It will add track_id and frame_id to each detection
|
@@ -6,10 +6,12 @@ zone analysis, and alert generation.
|
|
6
6
|
|
7
7
|
"""
|
8
8
|
|
9
|
+
|
9
10
|
from typing import Any, Dict, List, Optional
|
10
11
|
from dataclasses import asdict
|
11
12
|
import time
|
12
13
|
from datetime import datetime, timezone
|
14
|
+
import copy # Added for deep copying detections to preserve original masks
|
13
15
|
|
14
16
|
from ..core.base import BaseProcessor, ProcessingContext, ProcessingResult, ConfigProtocol, ResultFormat
|
15
17
|
from ..utils import (
|
@@ -59,7 +61,8 @@ class PotholeConfig(BaseConfig):
|
|
59
61
|
|
60
62
|
|
61
63
|
class PotholeSegmentationUseCase(BaseProcessor):
|
62
|
-
|
64
|
+
|
65
|
+
# Human-friendly display names for categories
|
63
66
|
CATEGORY_DISPLAY = {
|
64
67
|
"pothole": "pothole"
|
65
68
|
}
|
@@ -68,12 +71,12 @@ class PotholeSegmentationUseCase(BaseProcessor):
|
|
68
71
|
super().__init__("pothole_segmentation")
|
69
72
|
self.category = "infrastructure"
|
70
73
|
|
74
|
+
# List of categories to track
|
75
|
+
self.target_categories = ['pothole']
|
76
|
+
|
71
77
|
self.CASE_TYPE: Optional[str] = 'pothole_segmentation'
|
72
78
|
self.CASE_VERSION: Optional[str] = '1.3'
|
73
79
|
|
74
|
-
# List of categories to track
|
75
|
-
self.target_categories = ["pothole"]
|
76
|
-
|
77
80
|
# Initialize smoothing tracker
|
78
81
|
self.smoothing_tracker = None
|
79
82
|
|
@@ -87,6 +90,13 @@ class PotholeSegmentationUseCase(BaseProcessor):
|
|
87
90
|
# Track start time for "TOTAL SINCE" calculation
|
88
91
|
self._tracking_start_time = None
|
89
92
|
|
93
|
+
# ------------------------------------------------------------------ #
|
94
|
+
# Canonical tracking aliasing to avoid duplicate counts #
|
95
|
+
# ------------------------------------------------------------------ #
|
96
|
+
# Maps raw tracker-generated IDs to stable canonical IDs that persist
|
97
|
+
# even if the underlying tracker re-assigns a new ID after a short
|
98
|
+
# interruption. This mirrors the logic used in people_counting to
|
99
|
+
# provide accurate unique counting.
|
90
100
|
self._track_aliases: Dict[Any, Any] = {}
|
91
101
|
self._canonical_tracks: Dict[Any, Dict[str, Any]] = {}
|
92
102
|
# Tunable parameters – adjust if necessary for specific scenarios
|
@@ -99,7 +109,7 @@ class PotholeSegmentationUseCase(BaseProcessor):
|
|
99
109
|
def process(self, data: Any, config: ConfigProtocol, context: Optional[ProcessingContext] = None,
|
100
110
|
stream_info: Optional[Dict[str, Any]] = None) -> ProcessingResult:
|
101
111
|
"""
|
102
|
-
Main entry point for
|
112
|
+
Main entry point for post-processing.
|
103
113
|
Applies category mapping, smoothing, counting, alerting, and summary generation.
|
104
114
|
Returns a ProcessingResult with all relevant outputs.
|
105
115
|
"""
|
@@ -115,60 +125,80 @@ class PotholeSegmentationUseCase(BaseProcessor):
|
|
115
125
|
input_format = match_results_structure(data)
|
116
126
|
context.input_format = input_format
|
117
127
|
context.confidence_threshold = config.confidence_threshold
|
118
|
-
|
128
|
+
|
129
|
+
# Step 1: Confidence filtering
|
119
130
|
if config.confidence_threshold is not None:
|
120
131
|
processed_data = filter_by_confidence(data, config.confidence_threshold)
|
121
|
-
self.logger.debug(f"Applied confidence filtering with threshold {config.confidence_threshold}")
|
122
132
|
else:
|
123
133
|
processed_data = data
|
124
|
-
|
125
134
|
self.logger.debug(f"Did not apply confidence filtering with threshold since nothing was provided")
|
126
135
|
|
127
136
|
# Step 2: Apply category mapping if provided
|
128
137
|
if config.index_to_category:
|
129
138
|
processed_data = apply_category_mapping(processed_data, config.index_to_category)
|
130
|
-
self.logger.debug("Applied category mapping")
|
131
139
|
|
140
|
+
# Step 3: Category filtering
|
132
141
|
if config.target_categories:
|
133
142
|
processed_data = [d for d in processed_data if d.get('category') in self.target_categories]
|
134
|
-
self.logger.debug(f"Applied category filtering")
|
135
143
|
|
136
|
-
# Apply bbox smoothing if enabled
|
144
|
+
# Step 4: Apply bbox smoothing if enabled
|
145
|
+
# Deep-copy detections so that we preserve the original masks before any
|
146
|
+
# smoothing/tracking logic potentially removes them.
|
147
|
+
raw_processed_data = [copy.deepcopy(det) for det in processed_data]
|
137
148
|
if config.enable_smoothing:
|
138
149
|
if self.smoothing_tracker is None:
|
139
150
|
smoothing_config = BBoxSmoothingConfig(
|
140
151
|
smoothing_algorithm=config.smoothing_algorithm,
|
141
152
|
window_size=config.smoothing_window_size,
|
142
153
|
cooldown_frames=config.smoothing_cooldown_frames,
|
143
|
-
confidence_threshold=config.confidence_threshold,
|
154
|
+
confidence_threshold=config.confidence_threshold,
|
144
155
|
confidence_range_factor=config.smoothing_confidence_range_factor,
|
145
156
|
enable_smoothing=True
|
146
157
|
)
|
147
158
|
self.smoothing_tracker = BBoxSmoothingTracker(smoothing_config)
|
159
|
+
|
148
160
|
processed_data = bbox_smoothing(processed_data, self.smoothing_tracker.config, self.smoothing_tracker)
|
161
|
+
# Restore masks after smoothing
|
149
162
|
|
150
|
-
# Advanced tracking (BYTETracker-like)
|
163
|
+
# Step 5: Advanced tracking (BYTETracker-like)
|
151
164
|
try:
|
152
|
-
from ..advanced_tracker import AdvancedTracker
|
153
|
-
from ..advanced_tracker.config import TrackerConfig
|
154
|
-
|
155
|
-
# Create tracker instance if it doesn't exist (preserves state across frames)
|
156
165
|
if self.tracker is None:
|
157
|
-
|
166
|
+
# Configure tracker thresholds based on the use-case confidence threshold so that
|
167
|
+
# low-confidence detections (e.g. < 0.7) can still be initialised as tracks when
|
168
|
+
# the user passes a lower `confidence_threshold` in the post-processing config.
|
169
|
+
if config.confidence_threshold is not None:
|
170
|
+
tracker_config = TrackerConfig(
|
171
|
+
track_high_thresh=float(config.confidence_threshold),
|
172
|
+
# Allow even lower detections to participate in secondary association
|
173
|
+
track_low_thresh=max(0.05, float(config.confidence_threshold) / 2),
|
174
|
+
new_track_thresh=float(config.confidence_threshold)
|
175
|
+
)
|
176
|
+
else:
|
177
|
+
tracker_config = TrackerConfig()
|
158
178
|
self.tracker = AdvancedTracker(tracker_config)
|
159
|
-
self.logger.info(
|
160
|
-
|
161
|
-
|
162
|
-
|
179
|
+
self.logger.info(
|
180
|
+
"Initialized AdvancedTracker for Monitoring and tracking with thresholds: "
|
181
|
+
f"high={tracker_config.track_high_thresh}, "
|
182
|
+
f"low={tracker_config.track_low_thresh}, "
|
183
|
+
f"new={tracker_config.new_track_thresh}"
|
184
|
+
)
|
185
|
+
|
163
186
|
processed_data = self.tracker.update(processed_data)
|
164
|
-
|
165
187
|
except Exception as e:
|
166
188
|
# If advanced tracker fails, fallback to unsmoothed detections
|
167
189
|
self.logger.warning(f"AdvancedTracker failed: {e}")
|
168
190
|
|
169
|
-
# Update
|
191
|
+
# Update tracking state for total count per label
|
170
192
|
self._update_tracking_state(processed_data)
|
171
193
|
|
194
|
+
# ------------------------------------------------------------------ #
|
195
|
+
# Re-attach segmentation masks that were present in the original input
|
196
|
+
# but may have been stripped during smoothing/tracking. We match each
|
197
|
+
# processed detection back to the raw detection with the highest IoU
|
198
|
+
# and copy over its "masks" field (if available).
|
199
|
+
# ------------------------------------------------------------------ #
|
200
|
+
processed_data = self._attach_masks_to_detections(processed_data, raw_processed_data)
|
201
|
+
|
172
202
|
# Update frame counter
|
173
203
|
self._total_frame_counter += 1
|
174
204
|
|
@@ -183,19 +213,20 @@ class PotholeSegmentationUseCase(BaseProcessor):
|
|
183
213
|
frame_number = start_frame
|
184
214
|
|
185
215
|
# Compute summaries and alerts
|
186
|
-
general_counting_summary = calculate_counting_summary(data)
|
187
|
-
counting_summary = self._count_categories(processed_data, config)
|
188
|
-
# Add total unique
|
189
|
-
total_counts = self.get_total_counts()
|
190
|
-
counting_summary['total_counts'] = total_counts
|
191
|
-
|
216
|
+
general_counting_summary = calculate_counting_summary(data)
|
217
|
+
counting_summary = self._count_categories(processed_data, config)
|
218
|
+
# Add total unique counts after tracking using only local state
|
219
|
+
total_counts = self.get_total_counts()
|
220
|
+
counting_summary['total_counts'] = total_counts
|
221
|
+
|
192
222
|
alerts = self._check_alerts(counting_summary, frame_number, config)
|
193
223
|
predictions = self._extract_predictions(processed_data)
|
194
224
|
|
195
|
-
# Step: Generate structured
|
225
|
+
# Step: Generate structured events and tracking stats with frame-based keys
|
196
226
|
incidents_list = self._generate_incidents(counting_summary, alerts, config, frame_number, stream_info)
|
197
|
-
tracking_stats_list = self._generate_tracking_stats(counting_summary, alerts, config, frame_number,
|
198
|
-
business_analytics_list = self._generate_business_analytics(counting_summary, alerts, config, stream_info, is_empty=
|
227
|
+
tracking_stats_list = self._generate_tracking_stats(counting_summary, alerts, config, frame_number,stream_info)
|
228
|
+
# business_analytics_list = self._generate_business_analytics(counting_summary, alerts, config, frame_number, stream_info, is_empty=False)
|
229
|
+
business_analytics_list = []
|
199
230
|
summary_list = self._generate_summary(counting_summary, incidents_list, tracking_stats_list, business_analytics_list, alerts)
|
200
231
|
|
201
232
|
# Extract frame-based dictionaries from the lists
|
@@ -210,8 +241,7 @@ class PotholeSegmentationUseCase(BaseProcessor):
|
|
210
241
|
"alerts": alerts,
|
211
242
|
"human_text": summary}
|
212
243
|
}
|
213
|
-
|
214
|
-
|
244
|
+
|
215
245
|
context.mark_completed()
|
216
246
|
|
217
247
|
# Build result object following the new pattern
|
@@ -273,8 +303,8 @@ class PotholeSegmentationUseCase(BaseProcessor):
|
|
273
303
|
"threshold_level": threshold,
|
274
304
|
"ascending": get_trend(self._ascending_alert_list, lookback=900, threshold=0.8),
|
275
305
|
"settings": {t: v for t, v in zip(getattr(config.alert_config, 'alert_type', ['Default']) if hasattr(config.alert_config, 'alert_type') else ['Default'],
|
276
|
-
|
277
|
-
|
306
|
+
getattr(config.alert_config, 'alert_value', ['JSON']) if hasattr(config.alert_config, 'alert_value') else ['JSON'])
|
307
|
+
}
|
278
308
|
})
|
279
309
|
elif category in summary.get("per_category_count", {}):
|
280
310
|
count = summary.get("per_category_count", {})[category]
|
@@ -286,25 +316,27 @@ class PotholeSegmentationUseCase(BaseProcessor):
|
|
286
316
|
"threshold_level": threshold,
|
287
317
|
"ascending": get_trend(self._ascending_alert_list, lookback=900, threshold=0.8),
|
288
318
|
"settings": {t: v for t, v in zip(getattr(config.alert_config, 'alert_type', ['Default']) if hasattr(config.alert_config, 'alert_type') else ['Default'],
|
289
|
-
|
290
|
-
|
319
|
+
getattr(config.alert_config, 'alert_value', ['JSON']) if hasattr(config.alert_config, 'alert_value') else ['JSON'])
|
320
|
+
}
|
291
321
|
})
|
292
322
|
else:
|
293
323
|
pass
|
294
324
|
return alerts
|
295
325
|
|
296
326
|
def _generate_incidents(self, counting_summary: Dict, alerts: List, config: PotholeConfig,
|
297
|
-
|
327
|
+
frame_number: Optional[int] = None, stream_info: Optional[Dict[str, Any]] = None) -> List[
|
298
328
|
Dict]:
|
299
|
-
"""Generate structured
|
300
|
-
|
301
|
-
|
329
|
+
"""Generate structured events for the output format with frame-based keys."""
|
330
|
+
|
331
|
+
# Use frame number as key, fallback to 'current_frame' if not available
|
332
|
+
frame_key = str(frame_number) if frame_number is not None else "current_frame"
|
333
|
+
incidents=[]
|
302
334
|
total_detections = counting_summary.get("total_count", 0)
|
303
335
|
current_timestamp = self._get_current_timestamp_str(stream_info)
|
304
336
|
camera_info = self.get_camera_info_from_stream(stream_info)
|
305
337
|
|
306
338
|
self._ascending_alert_list = self._ascending_alert_list[-900:] if len(self._ascending_alert_list) > 900 else self._ascending_alert_list
|
307
|
-
|
339
|
+
|
308
340
|
if total_detections > 0:
|
309
341
|
# Determine event level based on thresholds
|
310
342
|
level = "low"
|
@@ -352,7 +384,7 @@ class PotholeSegmentationUseCase(BaseProcessor):
|
|
352
384
|
intensity = min(10.0, total_detections / 3.0)
|
353
385
|
self._ascending_alert_list.append(0)
|
354
386
|
|
355
|
-
|
387
|
+
# Generate human text in new format
|
356
388
|
human_text_lines = [f"INCIDENTS DETECTED @ {current_timestamp}:"]
|
357
389
|
human_text_lines.append(f"\tSeverity Level: {(self.CASE_TYPE,level)}")
|
358
390
|
human_text = "\n".join(human_text_lines)
|
@@ -384,23 +416,23 @@ class PotholeSegmentationUseCase(BaseProcessor):
|
|
384
416
|
def _generate_tracking_stats(
|
385
417
|
self,
|
386
418
|
counting_summary: Dict,
|
387
|
-
alerts:
|
419
|
+
alerts: Any,
|
388
420
|
config: PotholeConfig,
|
389
421
|
frame_number: Optional[int] = None,
|
390
422
|
stream_info: Optional[Dict[str, Any]] = None
|
391
423
|
) -> List[Dict]:
|
392
|
-
"""Generate structured tracking stats
|
393
|
-
camera_info = self.get_camera_info_from_stream(stream_info)
|
394
|
-
|
424
|
+
"""Generate structured tracking stats for the output format with frame-based keys, including track_ids_info and detections with masks."""
|
395
425
|
# frame_key = str(frame_number) if frame_number is not None else "current_frame"
|
396
426
|
# tracking_stats = [{frame_key: []}]
|
397
427
|
# frame_tracking_stats = tracking_stats[0][frame_key]
|
398
428
|
tracking_stats = []
|
399
|
-
|
400
|
-
total_detections = counting_summary.get("total_count", 0)
|
401
|
-
|
402
|
-
cumulative_total = sum(
|
403
|
-
per_category_count = counting_summary.get("per_category_count", {})
|
429
|
+
|
430
|
+
total_detections = counting_summary.get("total_count", 0)
|
431
|
+
total_counts = counting_summary.get("total_counts", {})
|
432
|
+
cumulative_total = sum(total_counts.values()) if total_counts else 0
|
433
|
+
per_category_count = counting_summary.get("per_category_count", {})
|
434
|
+
|
435
|
+
track_ids_info = self._get_track_ids_info(counting_summary.get("detections", []))
|
404
436
|
|
405
437
|
current_timestamp = self._get_current_timestamp_str(stream_info, precision=False)
|
406
438
|
start_timestamp = self._get_start_timestamp_str(stream_info, precision=False)
|
@@ -409,16 +441,33 @@ class PotholeSegmentationUseCase(BaseProcessor):
|
|
409
441
|
high_precision_start_timestamp = self._get_current_timestamp_str(stream_info, precision=True)
|
410
442
|
high_precision_reset_timestamp = self._get_start_timestamp_str(stream_info, precision=True)
|
411
443
|
|
412
|
-
|
413
|
-
|
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
|
-
})
|
444
|
+
camera_info = self.get_camera_info_from_stream(stream_info)
|
445
|
+
human_text_lines = []
|
421
446
|
|
447
|
+
# CURRENT FRAME section
|
448
|
+
human_text_lines.append(f"CURRENT FRAME @ {current_timestamp}:")
|
449
|
+
if total_detections > 0:
|
450
|
+
category_counts = [f"{count} {cat}" for cat, count in per_category_count.items()]
|
451
|
+
if len(category_counts) == 1:
|
452
|
+
detection_text = category_counts[0] + " detected"
|
453
|
+
elif len(category_counts) == 2:
|
454
|
+
detection_text = f"{category_counts[0]} and {category_counts[1]} detected"
|
455
|
+
else:
|
456
|
+
detection_text = f"{', '.join(category_counts[:-1])}, and {category_counts[-1]} detected"
|
457
|
+
human_text_lines.append(f"\t- {detection_text}")
|
458
|
+
else:
|
459
|
+
human_text_lines.append(f"\t- No detections")
|
460
|
+
|
461
|
+
human_text_lines.append("") # spacing
|
462
|
+
|
463
|
+
# TOTAL SINCE section
|
464
|
+
human_text_lines.append(f"TOTAL SINCE {start_timestamp}:")
|
465
|
+
human_text_lines.append(f"\t- Total Detected: {cumulative_total}")
|
466
|
+
# Add category-wise counts
|
467
|
+
if total_counts:
|
468
|
+
for cat, count in total_counts.items():
|
469
|
+
if count > 0: # Only include categories with non-zero counts
|
470
|
+
human_text_lines.append(f"\t- {cat}: {count}")
|
422
471
|
# Build current_counts array in expected format
|
423
472
|
current_counts = []
|
424
473
|
for cat, count in per_category_count.items():
|
@@ -428,6 +477,9 @@ class PotholeSegmentationUseCase(BaseProcessor):
|
|
428
477
|
"count": count
|
429
478
|
})
|
430
479
|
|
480
|
+
human_text = "\n".join(human_text_lines)
|
481
|
+
|
482
|
+
# Include detections with masks from counting_summary
|
431
483
|
# Prepare detections without confidence scores (as per eg.json)
|
432
484
|
detections = []
|
433
485
|
for detection in counting_summary.get("detections", []):
|
@@ -460,20 +512,6 @@ class PotholeSegmentationUseCase(BaseProcessor):
|
|
460
512
|
}
|
461
513
|
})
|
462
514
|
|
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
|
-
|
477
515
|
if alerts:
|
478
516
|
for alert in alerts:
|
479
517
|
human_text_lines.append(f"Alerts: {alert.get('settings', {})} sent @ {current_timestamp}")
|
@@ -481,7 +519,7 @@ class PotholeSegmentationUseCase(BaseProcessor):
|
|
481
519
|
human_text_lines.append("Alerts: None")
|
482
520
|
|
483
521
|
human_text = "\n".join(human_text_lines)
|
484
|
-
reset_settings=[
|
522
|
+
reset_settings = [
|
485
523
|
{
|
486
524
|
"interval_type": "daily",
|
487
525
|
"reset_time": {
|
@@ -499,7 +537,7 @@ class PotholeSegmentationUseCase(BaseProcessor):
|
|
499
537
|
tracking_stats.append(tracking_stat)
|
500
538
|
return tracking_stats
|
501
539
|
|
502
|
-
def _generate_business_analytics(self, counting_summary: Dict,
|
540
|
+
def _generate_business_analytics(self, counting_summary: Dict, zone_analysis: Dict, config: PotholeConfig, stream_info: Optional[Dict[str, Any]] = None, is_empty=False) -> List[Dict]:
|
503
541
|
"""Generate standardized business analytics for the agg_summary structure."""
|
504
542
|
if is_empty:
|
505
543
|
return []
|
@@ -530,6 +568,36 @@ class PotholeSegmentationUseCase(BaseProcessor):
|
|
530
568
|
|
531
569
|
return [lines]
|
532
570
|
|
571
|
+
|
572
|
+
def _count_categories(self, detections: list, config: PotholeConfig) -> dict:
|
573
|
+
"""
|
574
|
+
Count the number of detections per category and return a summary dict.
|
575
|
+
The detections list is expected to have 'track_id' (from tracker), 'category', 'bounding_box', 'masks', etc.
|
576
|
+
Output structure will include 'track_id' and 'masks' for each detection as per AdvancedTracker output.
|
577
|
+
"""
|
578
|
+
counts = {}
|
579
|
+
valid_detections = []
|
580
|
+
for det in detections:
|
581
|
+
cat = det.get('category', 'unknown')
|
582
|
+
if not all(k in det for k in ['category', 'confidence', 'bounding_box']): # Validate required fields
|
583
|
+
self.logger.warning(f"Skipping invalid detection: {det}")
|
584
|
+
continue
|
585
|
+
counts[cat] = counts.get(cat, 0) + 1
|
586
|
+
valid_detections.append({
|
587
|
+
"bounding_box": det.get("bounding_box"),
|
588
|
+
"category": det.get("category"),
|
589
|
+
"confidence": det.get("confidence"),
|
590
|
+
"track_id": det.get("track_id"),
|
591
|
+
"frame_id": det.get("frame_id"),
|
592
|
+
"masks": det.get("masks", det.get("mask", [])) # Include masks, fallback to empty list
|
593
|
+
})
|
594
|
+
self.logger.debug(f"Valid detections after filtering: {len(valid_detections)}")
|
595
|
+
return {
|
596
|
+
"total_count": sum(counts.values()),
|
597
|
+
"per_category_count": counts,
|
598
|
+
"detections": valid_detections
|
599
|
+
}
|
600
|
+
|
533
601
|
def _get_track_ids_info(self, detections: list) -> Dict[str, Any]:
|
534
602
|
"""
|
535
603
|
Get detailed information about track IDs (per frame).
|
@@ -583,12 +651,6 @@ class PotholeSegmentationUseCase(BaseProcessor):
|
|
583
651
|
"""
|
584
652
|
return {cat: len(ids) for cat, ids in getattr(self, '_per_category_total_track_ids', {}).items()}
|
585
653
|
|
586
|
-
|
587
|
-
def _format_timestamp_for_stream(self, timestamp: float) -> str:
|
588
|
-
"""Format timestamp for streams (YYYY:MM:DD HH:MM:SS format)."""
|
589
|
-
dt = datetime.fromtimestamp(timestamp, tz=timezone.utc)
|
590
|
-
return dt.strftime('%Y:%m:%d %H:%M:%S')
|
591
|
-
|
592
654
|
def _format_timestamp_for_video(self, timestamp: float) -> str:
|
593
655
|
"""Format timestamp for video chunks (HH:MM:SS.ms format)."""
|
594
656
|
hours = int(timestamp // 3600)
|
@@ -596,6 +658,11 @@ class PotholeSegmentationUseCase(BaseProcessor):
|
|
596
658
|
seconds = round(float(timestamp % 60),2)
|
597
659
|
return f"{hours:02d}:{minutes:02d}:{seconds:.1f}"
|
598
660
|
|
661
|
+
def _format_timestamp_for_stream(self, timestamp: float) -> str:
|
662
|
+
"""Format timestamp for streams (YYYY:MM:DD HH:MM:SS format)."""
|
663
|
+
dt = datetime.fromtimestamp(timestamp, tz=timezone.utc)
|
664
|
+
return dt.strftime('%Y:%m:%d %H:%M:%S')
|
665
|
+
|
599
666
|
def _get_current_timestamp_str(self, stream_info: Optional[Dict[str, Any]], precision=False, frame_id: Optional[str]=None) -> str:
|
600
667
|
"""Get formatted current timestamp based on stream type."""
|
601
668
|
if not stream_info:
|
@@ -671,31 +738,60 @@ class PotholeSegmentationUseCase(BaseProcessor):
|
|
671
738
|
dt = dt.replace(minute=0, second=0, microsecond=0)
|
672
739
|
return dt.strftime('%Y:%m:%d %H:%M:%S')
|
673
740
|
|
674
|
-
|
741
|
+
# ------------------------------------------------------------------ #
|
742
|
+
# Helper to merge masks back into detections #
|
743
|
+
# ------------------------------------------------------------------ #
|
744
|
+
def _attach_masks_to_detections(
|
745
|
+
self,
|
746
|
+
processed_detections: List[Dict[str, Any]],
|
747
|
+
raw_detections: List[Dict[str, Any]],
|
748
|
+
iou_threshold: float = 0.5,
|
749
|
+
) -> List[Dict[str, Any]]:
|
675
750
|
"""
|
676
|
-
|
677
|
-
|
678
|
-
|
751
|
+
Attach segmentation masks from the original `raw_detections` list to the
|
752
|
+
`processed_detections` list returned after smoothing/tracking.
|
753
|
+
|
754
|
+
Matching between detections is performed using Intersection-over-Union
|
755
|
+
(IoU) of the bounding boxes. For each processed detection we select the
|
756
|
+
raw detection with the highest IoU above `iou_threshold` and copy its
|
757
|
+
`masks` (or `mask`) field. If no suitable match is found, the detection
|
758
|
+
keeps an empty list for `masks` to maintain a consistent schema.
|
679
759
|
"""
|
680
|
-
|
681
|
-
|
682
|
-
|
683
|
-
|
684
|
-
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
|
690
|
-
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
|
695
|
-
|
696
|
-
|
697
|
-
|
698
|
-
|
760
|
+
|
761
|
+
if not processed_detections or not raw_detections:
|
762
|
+
# Nothing to do – ensure masks key exists for downstream logic.
|
763
|
+
for det in processed_detections:
|
764
|
+
det.setdefault("masks", [])
|
765
|
+
return processed_detections
|
766
|
+
|
767
|
+
# Track which raw detections have already been matched to avoid
|
768
|
+
# assigning the same mask to multiple processed detections.
|
769
|
+
used_raw_indices = set()
|
770
|
+
|
771
|
+
for det in processed_detections:
|
772
|
+
best_iou = 0.0
|
773
|
+
best_idx = None
|
774
|
+
|
775
|
+
for idx, raw_det in enumerate(raw_detections):
|
776
|
+
if idx in used_raw_indices:
|
777
|
+
continue
|
778
|
+
|
779
|
+
iou = self._compute_iou(det.get("bounding_box"), raw_det.get("bounding_box"))
|
780
|
+
if iou > best_iou:
|
781
|
+
best_iou = iou
|
782
|
+
best_idx = idx
|
783
|
+
|
784
|
+
if best_idx is not None and best_iou >= iou_threshold:
|
785
|
+
raw_det = raw_detections[best_idx]
|
786
|
+
masks = raw_det.get("masks", raw_det.get("mask"))
|
787
|
+
if masks is not None:
|
788
|
+
det["masks"] = masks
|
789
|
+
used_raw_indices.add(best_idx)
|
790
|
+
else:
|
791
|
+
# No adequate match – default to empty list to keep schema consistent.
|
792
|
+
det.setdefault("masks", ["EMPTY"])
|
793
|
+
|
794
|
+
return processed_detections
|
699
795
|
|
700
796
|
def _extract_predictions(self, detections: list) -> List[Dict[str, Any]]:
|
701
797
|
"""
|
@@ -705,11 +801,13 @@ class PotholeSegmentationUseCase(BaseProcessor):
|
|
705
801
|
{
|
706
802
|
"category": det.get("category", "unknown"),
|
707
803
|
"confidence": det.get("confidence", 0.0),
|
708
|
-
"bounding_box": det.get("bounding_box", {})
|
804
|
+
"bounding_box": det.get("bounding_box", {}),
|
805
|
+
"mask": det.get("mask", det.get("masks", None)) # Accept either key
|
709
806
|
}
|
710
807
|
for det in detections
|
711
808
|
]
|
712
809
|
|
810
|
+
|
713
811
|
# ------------------------------------------------------------------ #
|
714
812
|
# Canonical ID helpers #
|
715
813
|
# ------------------------------------------------------------------ #
|
@@ -181,7 +181,7 @@ matrice/deploy/utils/post_processing/usecases/flower_segmentation.py,sha256=4I7q
|
|
181
181
|
matrice/deploy/utils/post_processing/usecases/gender_detection.py,sha256=DEnCTRew6B7DtPcBQVCTtpd_IQMvMusBcu6nadUg2oM,40107
|
182
182
|
matrice/deploy/utils/post_processing/usecases/leaf.py,sha256=cwgB1ZNxkQFtkk-thSJrkXOGou1ghJr1kqtopb3sLD4,37036
|
183
183
|
matrice/deploy/utils/post_processing/usecases/leaf_disease.py,sha256=bkiLccTdf4KUq3he4eCpBlKXb5exr-WBhQ_oWQ7os68,36225
|
184
|
-
matrice/deploy/utils/post_processing/usecases/license_plate_detection.py,sha256=
|
184
|
+
matrice/deploy/utils/post_processing/usecases/license_plate_detection.py,sha256=Zl2y1goPWdCMGlKdj7mIwoNmwMkYtz6-oGqGjSCYco4,40387
|
185
185
|
matrice/deploy/utils/post_processing/usecases/mask_detection.py,sha256=MNpCcuefOdW7C8g_x_mNuWYA4mbyg8UNwomwBPoKtr0,39684
|
186
186
|
matrice/deploy/utils/post_processing/usecases/parking.py,sha256=lqTGqcjUZZPFw3tu11Ha8BSsZ311K5--wEZnlVsXakU,34534
|
187
187
|
matrice/deploy/utils/post_processing/usecases/parking_space_detection.py,sha256=xwhkJjGGKcT827URbasi3olYqhd95Sh0zsEIphwzcgY,39561
|
@@ -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=DLi6NARPkg11r0ZeodJEevwK0XxTupH7Bg-iPWcT5Sc,44773
|
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.99181.dist-info/licenses/LICENSE.txt,sha256=2bm9uFabQZ3Ykb_SaSU_uUbAj2-htc6WJQmS_65qD00,1073
|
231
|
+
matrice-1.0.99181.dist-info/METADATA,sha256=kf-h2K1CEsq50QKTS3nPXWMKEWVfEaIeUIap6mv_lkg,14624
|
232
|
+
matrice-1.0.99181.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
233
|
+
matrice-1.0.99181.dist-info/top_level.txt,sha256=P97js8ur6o5ClRqMH3Cjoab_NqbJ6sOQ3rJmVzKBvMc,8
|
234
|
+
matrice-1.0.99181.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|