matrice-analytics 0.1.2__py3-none-any.whl → 0.1.3__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/usecases/color/clip.py +254 -128
- matrice_analytics/post_processing/usecases/color_detection.py +412 -261
- {matrice_analytics-0.1.2.dist-info → matrice_analytics-0.1.3.dist-info}/METADATA +1 -1
- {matrice_analytics-0.1.2.dist-info → matrice_analytics-0.1.3.dist-info}/RECORD +7 -7
- {matrice_analytics-0.1.2.dist-info → matrice_analytics-0.1.3.dist-info}/WHEEL +0 -0
- {matrice_analytics-0.1.2.dist-info → matrice_analytics-0.1.3.dist-info}/licenses/LICENSE.txt +0 -0
- {matrice_analytics-0.1.2.dist-info → matrice_analytics-0.1.3.dist-info}/top_level.txt +0 -0
|
@@ -78,11 +78,12 @@ class ColorDetectionConfig(BaseConfig):
|
|
|
78
78
|
alert_config: Optional[AlertConfig] = None
|
|
79
79
|
time_window_minutes: int = 60
|
|
80
80
|
enable_unique_counting: bool = True
|
|
81
|
-
enable_smoothing: bool =
|
|
81
|
+
enable_smoothing: bool = False
|
|
82
82
|
smoothing_algorithm: str = "observability"
|
|
83
83
|
smoothing_window_size: int = 20
|
|
84
84
|
smoothing_cooldown_frames: int = 5
|
|
85
85
|
smoothing_confidence_range_factor: float = 0.5
|
|
86
|
+
detector = True
|
|
86
87
|
|
|
87
88
|
#JBK_720_GATE POLYGON = [[86, 328], [844, 317], [1277, 520], [1273, 707], [125, 713]]
|
|
88
89
|
zone_config: Optional[Dict[str, List[List[float]]]] = field(
|
|
@@ -92,7 +93,7 @@ class ColorDetectionConfig(BaseConfig):
|
|
|
92
93
|
}
|
|
93
94
|
}
|
|
94
95
|
)
|
|
95
|
-
true_import: bool = False
|
|
96
|
+
# true_import: bool = False
|
|
96
97
|
|
|
97
98
|
def validate(self) -> List[str]:
|
|
98
99
|
errors = super().validate()
|
|
@@ -112,6 +113,16 @@ class ColorDetectionConfig(BaseConfig):
|
|
|
112
113
|
errors.append("smoothing_confidence_range_factor must be positive")
|
|
113
114
|
return errors
|
|
114
115
|
|
|
116
|
+
def __post_init__(self):
|
|
117
|
+
# Lazy initialization: the ClipProcessor will be created once by the use case
|
|
118
|
+
# to avoid repeated model downloads and to ensure GPU session reuse.
|
|
119
|
+
if self.detector:
|
|
120
|
+
self.detector = ClipProcessor()
|
|
121
|
+
print("ClipProcessor Loaded Successfully!!")
|
|
122
|
+
else:
|
|
123
|
+
print("Clip color detector disabled by config")
|
|
124
|
+
self.detector = None
|
|
125
|
+
|
|
115
126
|
|
|
116
127
|
|
|
117
128
|
class ColorDetectionUseCase(BaseProcessor):
|
|
@@ -124,32 +135,32 @@ class ColorDetectionUseCase(BaseProcessor):
|
|
|
124
135
|
"three wheelers -CNG-": "Three Wheelers (CNG)", "human hauler": "Human Hauler",
|
|
125
136
|
"van": "Van", "wheelbarrow": "Wheelbarrow"
|
|
126
137
|
}
|
|
127
|
-
|
|
138
|
+
|
|
128
139
|
def __init__(self):
|
|
129
140
|
super().__init__("color_detection")
|
|
130
141
|
self.category = "visual_appearance"
|
|
131
|
-
|
|
142
|
+
|
|
132
143
|
self.target_categories = ["car", "bicycle", "bus", "motorcycle"]
|
|
133
|
-
|
|
144
|
+
|
|
134
145
|
self.CASE_TYPE: Optional[str] = 'color_detection'
|
|
135
146
|
self.CASE_VERSION: Optional[str] = '1.3'
|
|
136
|
-
|
|
147
|
+
|
|
137
148
|
self.tracker = None # AdvancedTracker instance
|
|
138
149
|
self.smoothing_tracker = None # BBoxSmoothingTracker instance
|
|
139
150
|
self._total_frame_counter = 0 # Total frames processed
|
|
140
151
|
self._global_frame_offset = 0 # Frame offset for new sessions
|
|
141
152
|
self._color_total_track_ids = defaultdict(set) # Cumulative track IDs per category-color
|
|
142
153
|
self._color_current_frame_track_ids = defaultdict(set) # Per-frame track IDs per category-color
|
|
143
|
-
|
|
154
|
+
|
|
144
155
|
self._tracking_start_time = None
|
|
145
|
-
|
|
156
|
+
|
|
146
157
|
self._track_aliases: Dict[Any, Any] = {}
|
|
147
158
|
self._canonical_tracks: Dict[Any, Dict[str, Any]] = {}
|
|
148
159
|
# Tunable parameters – adjust if necessary for specific scenarios
|
|
149
160
|
self._track_merge_iou_threshold: float = 0.05 # IoU ≥ 0.05 →
|
|
150
161
|
self._track_merge_time_window: float = 7.0 # seconds within which to merge
|
|
151
162
|
|
|
152
|
-
self._ascending_alert_list: List[int] = []
|
|
163
|
+
self._ascending_alert_list: List[int] = []
|
|
153
164
|
self.current_incident_end_timestamp: str = "N/A"
|
|
154
165
|
self.color_det_dict = {}
|
|
155
166
|
self.start_timer = None
|
|
@@ -158,12 +169,17 @@ class ColorDetectionUseCase(BaseProcessor):
|
|
|
158
169
|
self._zone_total_track_ids = {} # zone_name -> set of all track IDs that have been in zone
|
|
159
170
|
self._zone_current_counts = {} # zone_name -> current count in zone
|
|
160
171
|
self._zone_total_counts = {} # zone_name -> total count that have been in zone
|
|
161
|
-
self.logger.info("Initialized ColorDetectionUseCase with
|
|
162
|
-
self.detector = None
|
|
172
|
+
self.logger.info("Initialized ColorDetectionUseCase with tracking")
|
|
173
|
+
#self.detector = None
|
|
163
174
|
self.all_color_data = {}
|
|
164
175
|
self.all_color_counts = {}
|
|
176
|
+
self.total_category_count = {}
|
|
177
|
+
self.category_color = {}
|
|
178
|
+
self.vehicle_tracks = {}
|
|
179
|
+
self.vehicle_stats = defaultdict(lambda: defaultdict(int))
|
|
180
|
+
self.zone_vehicle_stats = defaultdict(lambda: defaultdict(lambda: defaultdict(int)))
|
|
165
181
|
#self.jpeg = TurboJPEG()
|
|
166
|
-
|
|
182
|
+
# data, config, ProcessingContext(), stream_info,input_bytes
|
|
167
183
|
def process(
|
|
168
184
|
self,
|
|
169
185
|
data: Any,
|
|
@@ -173,8 +189,10 @@ class ColorDetectionUseCase(BaseProcessor):
|
|
|
173
189
|
stream_info: Optional[Dict[str, Any]] = None
|
|
174
190
|
) -> ProcessingResult:
|
|
175
191
|
processing_start = time.time()
|
|
176
|
-
|
|
192
|
+
|
|
177
193
|
try:
|
|
194
|
+
cwd = os.getcwd()
|
|
195
|
+
print("Current working directory:", cwd)
|
|
178
196
|
if not isinstance(config, ColorDetectionConfig):
|
|
179
197
|
return self.create_error_result(
|
|
180
198
|
"Invalid configuration type for color detection",
|
|
@@ -182,17 +200,17 @@ class ColorDetectionUseCase(BaseProcessor):
|
|
|
182
200
|
category=self.category,
|
|
183
201
|
context=context
|
|
184
202
|
)
|
|
185
|
-
|
|
186
|
-
if config.true_import and self.detector is None:
|
|
187
|
-
|
|
188
|
-
|
|
203
|
+
|
|
204
|
+
# if config.true_import and self.detector is None:
|
|
205
|
+
# self.detector = ClipProcessor()
|
|
206
|
+
# self.logger.info("Initialized ClipProcessor for color detection")
|
|
189
207
|
|
|
190
208
|
if context is None:
|
|
191
209
|
context = ProcessingContext()
|
|
192
|
-
|
|
210
|
+
|
|
193
211
|
if not input_bytes:
|
|
194
212
|
print("input_bytes is required for color detection")
|
|
195
|
-
|
|
213
|
+
|
|
196
214
|
if not data:
|
|
197
215
|
#print("data",data)
|
|
198
216
|
print("Detection data is required for color detection")
|
|
@@ -201,23 +219,17 @@ class ColorDetectionUseCase(BaseProcessor):
|
|
|
201
219
|
context.input_format = input_format
|
|
202
220
|
context.confidence_threshold = config.confidence_threshold
|
|
203
221
|
|
|
204
|
-
|
|
205
222
|
self.logger.info(f"Processing color detection with format: {input_format.value}")
|
|
206
|
-
|
|
223
|
+
|
|
207
224
|
# Step 1: Apply confidence filtering
|
|
208
225
|
processed_data = filter_by_confidence(data, config.confidence_threshold)
|
|
209
|
-
|
|
210
|
-
|
|
226
|
+
|
|
211
227
|
# Step 2: Apply category mapping if provided
|
|
212
228
|
if config.index_to_category:
|
|
213
|
-
|
|
214
|
-
#self.logger.debug("Applied category mapping")
|
|
229
|
+
color_processed_data = apply_category_mapping(processed_data, config.index_to_category)
|
|
215
230
|
|
|
216
|
-
if
|
|
217
|
-
color_processed_data = [d for d in processed_data if d.get('category') in self.target_categories]
|
|
218
|
-
self.logger.debug("Applied category filtering")
|
|
231
|
+
color_processed_data = [d for d in color_processed_data if d['category'] in self.target_categories]
|
|
219
232
|
|
|
220
|
-
|
|
221
233
|
raw_processed_data = [copy.deepcopy(det) for det in color_processed_data]
|
|
222
234
|
# Step 3: Apply bounding box smoothing if enabled
|
|
223
235
|
if config.enable_smoothing:
|
|
@@ -232,26 +244,26 @@ class ColorDetectionUseCase(BaseProcessor):
|
|
|
232
244
|
)
|
|
233
245
|
self.smoothing_tracker = BBoxSmoothingTracker(smoothing_config)
|
|
234
246
|
color_processed_data = bbox_smoothing(color_processed_data, self.smoothing_tracker.config, self.smoothing_tracker)
|
|
235
|
-
|
|
247
|
+
|
|
236
248
|
# Step 4: Apply advanced tracking
|
|
237
249
|
try:
|
|
238
250
|
from ..advanced_tracker import AdvancedTracker
|
|
239
251
|
from ..advanced_tracker.config import TrackerConfig
|
|
240
|
-
|
|
252
|
+
|
|
241
253
|
if self.tracker is None:
|
|
242
254
|
tracker_config = TrackerConfig()
|
|
243
255
|
self.tracker = AdvancedTracker(tracker_config)
|
|
244
256
|
self.logger.info("Initialized AdvancedTracker for color detection tracking")
|
|
245
|
-
|
|
257
|
+
|
|
246
258
|
color_processed_data = self.tracker.update(color_processed_data)
|
|
247
|
-
|
|
259
|
+
|
|
248
260
|
except Exception as e:
|
|
249
261
|
self.logger.warning(f"AdvancedTracker failed: {e}")
|
|
250
|
-
|
|
262
|
+
|
|
251
263
|
|
|
252
264
|
color_processed_data = self._attach_masks_to_detections(color_processed_data, raw_processed_data)
|
|
253
265
|
self._total_frame_counter += 1
|
|
254
|
-
|
|
266
|
+
|
|
255
267
|
frame_number = None
|
|
256
268
|
if stream_info:
|
|
257
269
|
input_settings = stream_info.get("input_settings", {})
|
|
@@ -260,19 +272,55 @@ class ColorDetectionUseCase(BaseProcessor):
|
|
|
260
272
|
# If start and end frame are the same, it's a single frame
|
|
261
273
|
if start_frame is not None and end_frame is not None and start_frame == end_frame:
|
|
262
274
|
frame_number = start_frame
|
|
263
|
-
|
|
275
|
+
|
|
264
276
|
# Step 7: Analyze colors in media
|
|
265
277
|
color_analysis = self._analyze_colors_in_media(
|
|
266
|
-
color_processed_data,
|
|
267
|
-
input_bytes,
|
|
278
|
+
color_processed_data,
|
|
279
|
+
input_bytes,
|
|
268
280
|
config
|
|
269
281
|
)
|
|
282
|
+
if config.zone_config:
|
|
283
|
+
color_processed_data = self._is_in_zone_robust(color_processed_data,config.zone_config)
|
|
284
|
+
print(color_processed_data)
|
|
285
|
+
try:
|
|
286
|
+
print("About to call process_color_in_frame...")
|
|
287
|
+
|
|
288
|
+
if config.detector is None:
|
|
289
|
+
print("ERROR: Detector is None after initialization attempt!")
|
|
290
|
+
curr_frame_color = {}
|
|
291
|
+
|
|
292
|
+
# else:
|
|
293
|
+
# if color_processed_data:
|
|
294
|
+
# t_id = color_processed_data[0].get('track_id')
|
|
295
|
+
# if t_id is not None and t_id not in self.all_color_data:
|
|
296
|
+
# # curr_frame_color = {}
|
|
297
|
+
# curr_frame_color = config.detector.process_color_in_frame(color_processed_data,input_bytes,config.zone_config,stream_info)
|
|
298
|
+
# res_dict[curr_frame_color[t_id]['color']] = curr_frame_color[t_id]['confidence']
|
|
299
|
+
# else:
|
|
300
|
+
# curr_frame_color = {}
|
|
301
|
+
# print("process_color_in_frame completed successfully")
|
|
302
|
+
# else:
|
|
303
|
+
# curr_frame_color = {}
|
|
304
|
+
|
|
305
|
+
#------------------------ORiginal Code to run on all frames-----------------------
|
|
306
|
+
else:
|
|
307
|
+
print(len(color_processed_data))
|
|
308
|
+
curr_frame_color = config.detector.process_color_in_frame(
|
|
309
|
+
color_processed_data,
|
|
310
|
+
input_bytes,
|
|
311
|
+
config.zone_config,
|
|
312
|
+
stream_info,
|
|
313
|
+
)
|
|
314
|
+
print("process_color_in_frame completed successfully")
|
|
315
|
+
except Exception as e:
|
|
316
|
+
print(f"ERROR in process_color_in_frame: {e}")
|
|
317
|
+
import traceback
|
|
318
|
+
traceback.print_exc()
|
|
319
|
+
curr_frame_color = {}
|
|
270
320
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
# Step 8: Update color tracking state
|
|
321
|
+
self.update_vehicle_stats(curr_frame_color)
|
|
274
322
|
self._update_color_tracking_state_from_analysis(color_analysis)
|
|
275
|
-
|
|
323
|
+
|
|
276
324
|
# Step 9: Calculate summaries
|
|
277
325
|
color_summary = self._calculate_color_summary(color_analysis, config)
|
|
278
326
|
totals = self.get_total_color_counts()
|
|
@@ -284,14 +332,16 @@ class ColorDetectionUseCase(BaseProcessor):
|
|
|
284
332
|
if color and tid is not None:
|
|
285
333
|
tmp[color].add(tid)
|
|
286
334
|
totals = {color: len(ids) for color, ids in tmp.items()}
|
|
287
|
-
total_category_counts = self.get_total_category_counts()
|
|
335
|
+
total_category_counts = self.get_total_category_counts(color_processed_data)
|
|
288
336
|
color_summary['total_color_counts'] = totals
|
|
289
337
|
color_summary['total_category_counts'] = total_category_counts
|
|
290
338
|
|
|
291
339
|
general_summary = self._calculate_general_summary(processed_data, config)
|
|
292
|
-
|
|
340
|
+
new_color_summary = self.merge_color_summary(color_processed_data,curr_frame_color)
|
|
341
|
+
|
|
293
342
|
# Step 10: Zone analysis
|
|
294
|
-
|
|
343
|
+
self.color_helper(curr_frame_color)
|
|
344
|
+
|
|
295
345
|
zone_analysis = {}
|
|
296
346
|
if config.zone_config and config.zone_config['zones']:
|
|
297
347
|
frame_data = color_processed_data
|
|
@@ -302,19 +352,19 @@ class ColorDetectionUseCase(BaseProcessor):
|
|
|
302
352
|
zone_analysis[zone_name] = enhanced_data
|
|
303
353
|
|
|
304
354
|
|
|
305
|
-
|
|
355
|
+
|
|
306
356
|
# Step 11: Generate alerts, incidents, tracking stats, and summary
|
|
307
357
|
alerts = self._check_alerts(color_summary, frame_number, config)
|
|
308
358
|
|
|
309
359
|
incidents_list = self._generate_incidents(color_summary, alerts, config, frame_number, stream_info)
|
|
310
360
|
incidents_list = []
|
|
311
361
|
|
|
312
|
-
tracking_stats_list = self._generate_tracking_stats(color_summary, alerts, config, frame_number, stream_info
|
|
362
|
+
tracking_stats_list = self._generate_tracking_stats(new_color_summary,color_summary, alerts, config,curr_frame_color, frame_number, stream_info)
|
|
313
363
|
|
|
314
364
|
business_analytics_list = []
|
|
315
365
|
summary_list = self._generate_summary(color_summary, incidents_list, tracking_stats_list, business_analytics_list, alerts)
|
|
316
366
|
|
|
317
|
-
|
|
367
|
+
|
|
318
368
|
incidents = incidents_list[0] if incidents_list else {}
|
|
319
369
|
tracking_stats = tracking_stats_list[0] if tracking_stats_list else {}
|
|
320
370
|
business_analytics = business_analytics_list[0] if business_analytics_list else {}
|
|
@@ -327,7 +377,7 @@ class ColorDetectionUseCase(BaseProcessor):
|
|
|
327
377
|
"zone_analysis": zone_analysis,
|
|
328
378
|
"human_text": summary}
|
|
329
379
|
}
|
|
330
|
-
|
|
380
|
+
|
|
331
381
|
context.mark_completed()
|
|
332
382
|
|
|
333
383
|
# Build result object following the new pattern
|
|
@@ -341,23 +391,168 @@ class ColorDetectionUseCase(BaseProcessor):
|
|
|
341
391
|
proc_time = time.time() - processing_start
|
|
342
392
|
processing_latency_ms = proc_time * 1000.0
|
|
343
393
|
processing_fps = (1.0 / proc_time) if proc_time > 0 else None
|
|
344
|
-
# Log the performance metrics using the module-level logger
|
|
345
394
|
print("latency in ms:",processing_latency_ms,"| Throughput fps:",processing_fps,"| Frame_Number:",self._total_frame_counter)
|
|
346
395
|
return result
|
|
347
|
-
|
|
396
|
+
|
|
348
397
|
except Exception as e:
|
|
349
398
|
self.logger.error(f"Color detection failed: {str(e)}", exc_info=True)
|
|
350
399
|
if context:
|
|
351
400
|
context.mark_completed()
|
|
352
401
|
return self.create_error_result(
|
|
353
|
-
str(e),
|
|
402
|
+
str(e),
|
|
354
403
|
type(e).__name__,
|
|
355
404
|
usecase=self.name,
|
|
356
405
|
category=self.category,
|
|
357
406
|
context=context
|
|
358
407
|
)
|
|
359
|
-
|
|
408
|
+
|
|
409
|
+
def update_vehicle_stats(self, frame_detections: dict):
|
|
410
|
+
"""
|
|
411
|
+
Update global vehicle statistics ensuring uniqueness per track_id and per zone.
|
|
412
|
+
If the same vehicle (track_id) is seen again:
|
|
413
|
+
- Ignore if confidence is lower.
|
|
414
|
+
- Update its color if confidence is higher.
|
|
415
|
+
"""
|
|
416
|
+
|
|
417
|
+
# Ensure zone-level data structures exist
|
|
418
|
+
if not hasattr(self, "zone_vehicle_stats"):
|
|
419
|
+
self.zone_vehicle_stats = defaultdict(lambda: defaultdict(lambda: defaultdict(int)))
|
|
420
|
+
|
|
421
|
+
for _, det in frame_detections.items():
|
|
422
|
+
track_id = det.get('track_id')
|
|
423
|
+
if track_id is None:
|
|
424
|
+
continue
|
|
425
|
+
|
|
426
|
+
vehicle_type = det.get('object_label', 'unknown').lower()
|
|
427
|
+
color = det.get('color', 'unknown').lower()
|
|
428
|
+
conf = det.get('confidence', 0.0)
|
|
429
|
+
zone = det.get('zone_name', 'Unknown_Zone')
|
|
430
|
+
|
|
431
|
+
# If this track_id is new → add and count
|
|
432
|
+
if track_id not in self.vehicle_tracks:
|
|
433
|
+
self.vehicle_tracks[track_id] = {
|
|
434
|
+
'object_label': vehicle_type,
|
|
435
|
+
'color': color,
|
|
436
|
+
'confidence': conf,
|
|
437
|
+
'zone': zone
|
|
438
|
+
}
|
|
439
|
+
self.vehicle_stats[vehicle_type][color] += 1
|
|
440
|
+
self.zone_vehicle_stats[zone][vehicle_type][color] += 1
|
|
441
|
+
|
|
442
|
+
else:
|
|
443
|
+
existing = self.vehicle_tracks[track_id]
|
|
444
|
+
if conf > existing['confidence']:
|
|
445
|
+
old_color = existing['color']
|
|
446
|
+
old_zone = existing.get('zone', zone)
|
|
447
|
+
old_type = existing.get('object_label', vehicle_type)
|
|
448
|
+
|
|
449
|
+
# Decrease old counts
|
|
450
|
+
self.vehicle_stats[old_type][old_color] -= 1
|
|
451
|
+
if self.vehicle_stats[old_type][old_color] <= 0:
|
|
452
|
+
del self.vehicle_stats[old_type][old_color]
|
|
453
|
+
|
|
454
|
+
self.zone_vehicle_stats[old_zone][old_type][old_color] -= 1
|
|
455
|
+
if self.zone_vehicle_stats[old_zone][old_type][old_color] <= 0:
|
|
456
|
+
del self.zone_vehicle_stats[old_zone][old_type][old_color]
|
|
457
|
+
|
|
458
|
+
# Update track info
|
|
459
|
+
self.vehicle_tracks[track_id].update({
|
|
460
|
+
'color': color,
|
|
461
|
+
'confidence': conf,
|
|
462
|
+
'zone': zone,
|
|
463
|
+
})
|
|
464
|
+
|
|
465
|
+
# Increase new counts
|
|
466
|
+
self.vehicle_stats[vehicle_type][color] += 1
|
|
467
|
+
self.zone_vehicle_stats[zone][vehicle_type][color] += 1
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
def merge_color_summary(self,detections_data: List[Dict[str, Any]], curr_frame_color: Dict[int, Dict[str, Any]]) -> Dict[str, Any]:
|
|
471
|
+
"""
|
|
472
|
+
Combine base detections with current frame color information and produce a color summary.
|
|
473
|
+
Returns structure similar to _calculate_color_summary().
|
|
474
|
+
"""
|
|
475
|
+
|
|
476
|
+
category_colors = defaultdict(lambda: defaultdict(int))
|
|
477
|
+
detections = []
|
|
478
|
+
counts = {}
|
|
479
|
+
|
|
480
|
+
# Merge detections with color info
|
|
481
|
+
for record in detections_data:
|
|
482
|
+
track_id = record.get("track_id")
|
|
483
|
+
category = record.get("category", "unknown")
|
|
484
|
+
conf = record.get("confidence", 0.0)
|
|
485
|
+
bbox = record.get("bounding_box", {})
|
|
486
|
+
frame_id = record.get("frame_id")
|
|
487
|
+
zone_name = record.get("zone_name", "Unknown")
|
|
488
|
+
|
|
489
|
+
# Get color from curr_frame_color
|
|
490
|
+
main_color = "unknown"
|
|
491
|
+
if track_id in curr_frame_color:
|
|
492
|
+
main_color = curr_frame_color[track_id].get("color", "unknown")
|
|
493
|
+
|
|
494
|
+
category_colors[category][main_color] += 1
|
|
495
|
+
counts[category] = counts.get(category, 0) + 1
|
|
496
|
+
|
|
497
|
+
detections.append({
|
|
498
|
+
"bounding_box": bbox,
|
|
499
|
+
"category": category,
|
|
500
|
+
"confidence": conf,
|
|
501
|
+
"track_id": track_id,
|
|
502
|
+
"frame_id": frame_id,
|
|
503
|
+
"main_color": main_color,
|
|
504
|
+
"zone_name": zone_name
|
|
505
|
+
})
|
|
506
|
+
|
|
507
|
+
# Flatten color distribution
|
|
508
|
+
all_colors = defaultdict(int)
|
|
509
|
+
for category_data in category_colors.values():
|
|
510
|
+
for color, count in category_data.items():
|
|
511
|
+
all_colors[color] += count
|
|
512
|
+
|
|
513
|
+
# Find dominant color per category
|
|
514
|
+
dominant_colors = {}
|
|
515
|
+
for category, colors in category_colors.items():
|
|
516
|
+
if colors:
|
|
517
|
+
color, count = max(colors.items(), key=lambda x: x[1])
|
|
518
|
+
dominant_colors[category] = {
|
|
519
|
+
"color": color,
|
|
520
|
+
"count": count,
|
|
521
|
+
"percentage": round((count / sum(colors.values())) * 100, 1)
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
# Final summary dict
|
|
525
|
+
summary = {
|
|
526
|
+
"total_count": sum(counts.values()),
|
|
527
|
+
"per_category_count": counts,
|
|
528
|
+
"detections": detections,
|
|
529
|
+
"color_distribution": dict(all_colors),
|
|
530
|
+
"dominant_colors": dominant_colors
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
return summary
|
|
534
|
+
|
|
535
|
+
def get_vehicle_stats(self):
|
|
536
|
+
"""Return the current global vehicle statistics as a normal dictionary."""
|
|
537
|
+
return {vtype: dict(colors) for vtype, colors in self.vehicle_stats.items()}
|
|
538
|
+
|
|
539
|
+
def _is_in_zone_robust(self,detections,zones):
|
|
540
|
+
if not detections:
|
|
541
|
+
return {}
|
|
542
|
+
new_data = []
|
|
543
|
+
for det in detections:
|
|
544
|
+
bbox = det.get('bounding_box')
|
|
545
|
+
cx,cy = get_bbox_bottom25_center(bbox)
|
|
546
|
+
for zone, region in zones.items():
|
|
547
|
+
for reg, poly in region.items():
|
|
548
|
+
if point_in_polygon((cx,cy),poly):
|
|
549
|
+
det['zone_name'] = reg
|
|
550
|
+
new_data.append(det)
|
|
551
|
+
return new_data
|
|
552
|
+
|
|
360
553
|
def color_helper(self, curr_data):
|
|
554
|
+
if curr_data is None:
|
|
555
|
+
return
|
|
361
556
|
for tid, data in curr_data.items():
|
|
362
557
|
if tid not in self.all_color_data:
|
|
363
558
|
# First time seeing this track
|
|
@@ -391,25 +586,16 @@ class ColorDetectionUseCase(BaseProcessor):
|
|
|
391
586
|
# update track info
|
|
392
587
|
self.all_color_data[tid]["color"] = new_color
|
|
393
588
|
self.all_color_data[tid]["confidence"] = data.get("confidence")
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
589
|
+
# return self.all_color_data
|
|
397
590
|
|
|
398
591
|
def _analyze_colors_in_media(
|
|
399
|
-
self,
|
|
400
|
-
data: Any,
|
|
401
|
-
media_bytes: bytes,
|
|
592
|
+
self,
|
|
593
|
+
data: Any,
|
|
594
|
+
media_bytes: bytes,
|
|
402
595
|
config: ColorDetectionConfig
|
|
403
596
|
) -> List[Dict[str, Any]]:
|
|
404
597
|
"""Analyze colors of detected objects in video frames or images."""
|
|
405
|
-
|
|
406
|
-
# Determine if input is video or image
|
|
407
|
-
is_video = self._is_video_bytes(media_bytes)
|
|
408
|
-
|
|
409
|
-
if is_video:
|
|
410
|
-
return self._analyze_colors_in_video(data, media_bytes, config)
|
|
411
|
-
else:
|
|
412
|
-
return self._analyze_colors_in_image(data, media_bytes, config)
|
|
598
|
+
return self._analyze_colors_in_image(data, media_bytes, config)
|
|
413
599
|
|
|
414
600
|
def _update_color_tracking_state_from_analysis(self, color_analysis: List[Dict[str, Any]]) -> None:
|
|
415
601
|
"""Update total tracking store using analyzed color results.
|
|
@@ -422,7 +608,7 @@ class ColorDetectionUseCase(BaseProcessor):
|
|
|
422
608
|
self._color_total_track_ids = existing_store
|
|
423
609
|
# Reset current frame tracking for this frame
|
|
424
610
|
self._color_current_frame_track_ids = defaultdict(set)
|
|
425
|
-
|
|
611
|
+
|
|
426
612
|
for rec in color_analysis:
|
|
427
613
|
cat = rec.get('category')
|
|
428
614
|
color = rec.get('main_color')
|
|
@@ -466,7 +652,7 @@ class ColorDetectionUseCase(BaseProcessor):
|
|
|
466
652
|
self._color_total_track_ids[key].add(track_id)
|
|
467
653
|
# Also update current frame tracking
|
|
468
654
|
self._color_current_frame_track_ids[key].add(track_id)
|
|
469
|
-
|
|
655
|
+
|
|
470
656
|
def _is_video_bytes(self, media_bytes: bytes) -> bool:
|
|
471
657
|
"""Determine if bytes represent a video file."""
|
|
472
658
|
# Check common video file signatures
|
|
@@ -477,16 +663,16 @@ class ColorDetectionUseCase(BaseProcessor):
|
|
|
477
663
|
b'\x1aE\xdf\xa3', # MKV/WebM
|
|
478
664
|
b'ftyp', # General MP4 family
|
|
479
665
|
]
|
|
480
|
-
|
|
666
|
+
|
|
481
667
|
for signature in video_signatures:
|
|
482
668
|
if media_bytes.startswith(signature) or signature in media_bytes[:50]:
|
|
483
669
|
return True
|
|
484
670
|
return False
|
|
485
|
-
|
|
671
|
+
|
|
486
672
|
def _analyze_colors_in_video(
|
|
487
|
-
self,
|
|
488
|
-
data: Any,
|
|
489
|
-
video_bytes: bytes,
|
|
673
|
+
self,
|
|
674
|
+
data: Any,
|
|
675
|
+
video_bytes: bytes,
|
|
490
676
|
config: ColorDetectionConfig
|
|
491
677
|
) -> List[Dict[str, Any]]:
|
|
492
678
|
with tempfile.NamedTemporaryFile(suffix=".mp4", delete=False) as temp_video:
|
|
@@ -574,22 +760,22 @@ class ColorDetectionUseCase(BaseProcessor):
|
|
|
574
760
|
os.unlink(video_path)
|
|
575
761
|
|
|
576
762
|
def _analyze_colors_in_image(
|
|
577
|
-
self,
|
|
578
|
-
data: Any,
|
|
579
|
-
image_bytes: bytes,
|
|
763
|
+
self,
|
|
764
|
+
data: Any,
|
|
765
|
+
image_bytes: bytes,
|
|
580
766
|
config: ColorDetectionConfig
|
|
581
767
|
) -> List[Dict[str, Any]]:
|
|
582
768
|
image_array = np.frombuffer(image_bytes, np.uint8)
|
|
583
769
|
image = cv2.imdecode(image_array, cv2.IMREAD_COLOR)
|
|
584
770
|
#image = self.jpeg.decode(image_bytes, pixel_format=TJPF_RGB)
|
|
585
|
-
|
|
771
|
+
|
|
586
772
|
if image is None:
|
|
587
773
|
raise RuntimeError("Failed to decode image from bytes")
|
|
588
|
-
|
|
774
|
+
|
|
589
775
|
rgb_image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
|
|
590
776
|
color_analysis = []
|
|
591
777
|
detections = self._get_frame_detections(data, "0")
|
|
592
|
-
|
|
778
|
+
|
|
593
779
|
for detection in detections:
|
|
594
780
|
if detection.get("confidence", 1.0) < config.confidence_threshold:
|
|
595
781
|
continue
|
|
@@ -610,12 +796,14 @@ class ColorDetectionUseCase(BaseProcessor):
|
|
|
610
796
|
if not in_any_zone:
|
|
611
797
|
continue # Skip detections outside zones
|
|
612
798
|
|
|
613
|
-
crop = self._crop_bbox(rgb_image, bbox, config.bbox_format)
|
|
614
|
-
if crop.size == 0:
|
|
615
|
-
|
|
799
|
+
# crop = self._crop_bbox(rgb_image, bbox, config.bbox_format)
|
|
800
|
+
# if crop.size == 0:
|
|
801
|
+
# continue
|
|
616
802
|
|
|
617
|
-
major_colors = extract_major_colors(crop, k=config.top_k_colors)
|
|
618
|
-
main_color = major_colors[0][0] if major_colors else "unknown"
|
|
803
|
+
# major_colors = extract_major_colors(crop, k=config.top_k_colors)
|
|
804
|
+
# main_color = major_colors[0][0] if major_colors else "unknown"
|
|
805
|
+
main_color = "unknown"
|
|
806
|
+
major_colors = []
|
|
619
807
|
|
|
620
808
|
color_record = {
|
|
621
809
|
"frame_id": "0",
|
|
@@ -630,10 +818,10 @@ class ColorDetectionUseCase(BaseProcessor):
|
|
|
630
818
|
"zone_name": zone_name
|
|
631
819
|
}
|
|
632
820
|
color_analysis.append(color_record)
|
|
633
|
-
|
|
821
|
+
|
|
634
822
|
return color_analysis
|
|
635
|
-
|
|
636
|
-
|
|
823
|
+
|
|
824
|
+
|
|
637
825
|
def _get_frame_detections(self, data: Any, frame_key: str) -> List[Dict[str, Any]]:
|
|
638
826
|
"""Extract detections for a specific frame from data."""
|
|
639
827
|
if isinstance(data, dict):
|
|
@@ -644,11 +832,11 @@ class ColorDetectionUseCase(BaseProcessor):
|
|
|
644
832
|
return data
|
|
645
833
|
else:
|
|
646
834
|
return []
|
|
647
|
-
|
|
835
|
+
|
|
648
836
|
def _crop_bbox(self, image: np.ndarray, bbox: Dict[str, Any], bbox_format: str) -> np.ndarray:
|
|
649
837
|
"""Crop bounding box region from image."""
|
|
650
838
|
h, w = image.shape[:2]
|
|
651
|
-
|
|
839
|
+
|
|
652
840
|
# Auto-detect bbox format
|
|
653
841
|
if bbox_format == "auto":
|
|
654
842
|
if "xmin" in bbox:
|
|
@@ -657,7 +845,7 @@ class ColorDetectionUseCase(BaseProcessor):
|
|
|
657
845
|
bbox_format = "x_y_width_height"
|
|
658
846
|
else:
|
|
659
847
|
return np.zeros((0, 0, 3), dtype=np.uint8)
|
|
660
|
-
|
|
848
|
+
|
|
661
849
|
# Extract coordinates based on format
|
|
662
850
|
if bbox_format == "xmin_ymin_xmax_ymax":
|
|
663
851
|
xmin = max(0, int(bbox["xmin"]))
|
|
@@ -671,9 +859,9 @@ class ColorDetectionUseCase(BaseProcessor):
|
|
|
671
859
|
ymax = min(h, int(bbox["y"] + bbox["height"]))
|
|
672
860
|
else:
|
|
673
861
|
return np.zeros((0, 0, 3), dtype=np.uint8)
|
|
674
|
-
|
|
862
|
+
|
|
675
863
|
return image[ymin:ymax, xmin:xmax]
|
|
676
|
-
|
|
864
|
+
|
|
677
865
|
def _calculate_color_summary(self, color_analysis: List[Dict], config: ColorDetectionConfig) -> Dict[str, Any]:
|
|
678
866
|
category_colors = defaultdict(lambda: defaultdict(int))
|
|
679
867
|
total_detections = len(color_analysis)
|
|
@@ -693,7 +881,7 @@ class ColorDetectionUseCase(BaseProcessor):
|
|
|
693
881
|
"main_color": record["main_color"]
|
|
694
882
|
})
|
|
695
883
|
|
|
696
|
-
|
|
884
|
+
|
|
697
885
|
self.logger.debug(f"Valid detections after filtering: {len(detections)}")
|
|
698
886
|
summary = {
|
|
699
887
|
"total_count": sum(counts.values()),
|
|
@@ -725,14 +913,14 @@ class ColorDetectionUseCase(BaseProcessor):
|
|
|
725
913
|
|
|
726
914
|
|
|
727
915
|
return summary
|
|
728
|
-
|
|
916
|
+
|
|
729
917
|
def _calculate_general_summary(self, processed_data: Any, config: ColorDetectionConfig) -> Dict[str, Any]:
|
|
730
918
|
"""Calculate general detection summary."""
|
|
731
|
-
|
|
919
|
+
|
|
732
920
|
# Count objects by category
|
|
733
921
|
category_counts = defaultdict(int)
|
|
734
922
|
total_objects = 0
|
|
735
|
-
|
|
923
|
+
|
|
736
924
|
if isinstance(processed_data, dict):
|
|
737
925
|
# Frame-based format
|
|
738
926
|
for frame_data in processed_data.values():
|
|
@@ -749,18 +937,18 @@ class ColorDetectionUseCase(BaseProcessor):
|
|
|
749
937
|
category = detection.get("category", "unknown")
|
|
750
938
|
category_counts[category] += 1
|
|
751
939
|
total_objects += 1
|
|
752
|
-
|
|
940
|
+
|
|
753
941
|
return {
|
|
754
942
|
"total_objects": total_objects,
|
|
755
943
|
"category_counts": dict(category_counts),
|
|
756
944
|
"categories_detected": list(category_counts.keys())
|
|
757
945
|
}
|
|
758
|
-
|
|
946
|
+
|
|
759
947
|
def _calculate_metrics(self, color_analysis: List[Dict], color_summary: Dict, config: ColorDetectionConfig, context: ProcessingContext) -> Dict[str, Any]:
|
|
760
948
|
"""Calculate detailed metrics for analytics."""
|
|
761
949
|
total_detections = len(color_analysis)
|
|
762
950
|
unique_colors = len(color_summary.get("color_distribution", {}))
|
|
763
|
-
|
|
951
|
+
|
|
764
952
|
metrics = {
|
|
765
953
|
"total_detections": total_detections,
|
|
766
954
|
"unique_colors": unique_colors,
|
|
@@ -772,15 +960,15 @@ class ColorDetectionUseCase(BaseProcessor):
|
|
|
772
960
|
"detection_rate": 0.0,
|
|
773
961
|
"average_colors_per_detection": config.top_k_colors
|
|
774
962
|
}
|
|
775
|
-
|
|
963
|
+
|
|
776
964
|
# Calculate color diversity
|
|
777
965
|
if total_detections > 0:
|
|
778
966
|
metrics["color_diversity"] = (unique_colors / total_detections) * 100
|
|
779
|
-
|
|
967
|
+
|
|
780
968
|
# Calculate detection rate
|
|
781
969
|
if config.time_window_minutes and config.time_window_minutes > 0:
|
|
782
970
|
metrics["detection_rate"] = (total_detections / config.time_window_minutes) * 60
|
|
783
|
-
|
|
971
|
+
|
|
784
972
|
# Per-category metrics
|
|
785
973
|
if color_summary.get("categories"):
|
|
786
974
|
category_metrics = {}
|
|
@@ -792,7 +980,7 @@ class ColorDetectionUseCase(BaseProcessor):
|
|
|
792
980
|
"color_diversity": (len(colors) / category_total) * 100 if category_total > 0 else 0
|
|
793
981
|
}
|
|
794
982
|
metrics["category_metrics"] = category_metrics
|
|
795
|
-
|
|
983
|
+
|
|
796
984
|
# Processing settings
|
|
797
985
|
metrics["processing_settings"] = {
|
|
798
986
|
"confidence_threshold": config.confidence_threshold,
|
|
@@ -801,12 +989,12 @@ class ColorDetectionUseCase(BaseProcessor):
|
|
|
801
989
|
"target_categories": config.target_categories,
|
|
802
990
|
"enable_unique_counting": config.enable_unique_counting
|
|
803
991
|
}
|
|
804
|
-
|
|
992
|
+
|
|
805
993
|
return metrics
|
|
806
|
-
|
|
994
|
+
|
|
807
995
|
def _extract_predictions(self, color_analysis: List[Dict], config: ColorDetectionConfig) -> List[Dict]:
|
|
808
996
|
"""Extract predictions in standard format."""
|
|
809
|
-
|
|
997
|
+
|
|
810
998
|
predictions = []
|
|
811
999
|
for record in color_analysis:
|
|
812
1000
|
prediction = {
|
|
@@ -821,9 +1009,9 @@ class ColorDetectionUseCase(BaseProcessor):
|
|
|
821
1009
|
if "detection_id" in record:
|
|
822
1010
|
prediction["id"] = record["detection_id"]
|
|
823
1011
|
predictions.append(prediction)
|
|
824
|
-
|
|
1012
|
+
|
|
825
1013
|
return predictions
|
|
826
|
-
|
|
1014
|
+
|
|
827
1015
|
def _generate_summary(self, summary: dict, incidents: List, tracking_stats: List, business_analytics: List, alerts: List) -> List[str]:
|
|
828
1016
|
"""
|
|
829
1017
|
Generate a human_text string for the tracking_stat, incident, business analytics and alerts.
|
|
@@ -834,7 +1022,7 @@ class ColorDetectionUseCase(BaseProcessor):
|
|
|
834
1022
|
if len(incidents) > 0:
|
|
835
1023
|
lines.append("Incidents: "+f"\n\t{incidents[0].get('human_text', 'No incidents detected')}")
|
|
836
1024
|
if len(tracking_stats) > 0:
|
|
837
|
-
lines.append(
|
|
1025
|
+
lines.append(f"\t{tracking_stats[0].get('human_text', 'No tracking statistics detected')}")
|
|
838
1026
|
if len(business_analytics) > 0:
|
|
839
1027
|
lines.append("Business Analytics: "+f"\t{business_analytics[0].get('human_text', 'No business analytics detected')}")
|
|
840
1028
|
|
|
@@ -842,7 +1030,7 @@ class ColorDetectionUseCase(BaseProcessor):
|
|
|
842
1030
|
lines.append("Summary: "+"No Summary Data")
|
|
843
1031
|
|
|
844
1032
|
return ["\n".join(lines)]
|
|
845
|
-
|
|
1033
|
+
|
|
846
1034
|
def _generate_events(self, color_summary: Dict, alerts: List, config: ColorDetectionConfig, frame_number: Optional[int] = None) -> List[Dict]:
|
|
847
1035
|
"""Generate structured events with frame-based keys."""
|
|
848
1036
|
frame_key = str(frame_number) if frame_number is not None else "current_frame"
|
|
@@ -906,15 +1094,17 @@ class ColorDetectionUseCase(BaseProcessor):
|
|
|
906
1094
|
frame_events.append(alert_event)
|
|
907
1095
|
|
|
908
1096
|
return events
|
|
909
|
-
|
|
1097
|
+
|
|
910
1098
|
def _generate_tracking_stats(
|
|
911
1099
|
self,
|
|
1100
|
+
new_color_summary: Dict,
|
|
912
1101
|
counting_summary: Dict,
|
|
913
1102
|
alerts: Any,
|
|
914
1103
|
config: ColorDetectionConfig,
|
|
1104
|
+
curr_frame_color: Any,
|
|
1105
|
+
total_color_data: Any,
|
|
915
1106
|
frame_number: Optional[int] = None,
|
|
916
1107
|
stream_info: Optional[Dict[str, Any]] = None,
|
|
917
|
-
curr_frame_color: Any = None
|
|
918
1108
|
) -> List[Dict]:
|
|
919
1109
|
"""Generate structured tracking stats for the output format with frame-based keys, including track_ids_info and detections with masks."""
|
|
920
1110
|
# frame_key = str(frame_number) if frame_number is not None else "current_frame"
|
|
@@ -925,7 +1115,7 @@ class ColorDetectionUseCase(BaseProcessor):
|
|
|
925
1115
|
total_detections = counting_summary.get("total_count", 0)
|
|
926
1116
|
total_color_counts_dict = counting_summary.get("total_color_counts", {})
|
|
927
1117
|
total_category_counts_dict = counting_summary.get("total_category_counts", {})
|
|
928
|
-
cumulative_total = sum(total_color_counts_dict.values()) if total_color_counts_dict else 0
|
|
1118
|
+
# cumulative_total = sum(total_color_counts_dict.values()) if total_color_counts_dict else 0
|
|
929
1119
|
per_category_count = counting_summary.get("per_category_count", {})
|
|
930
1120
|
|
|
931
1121
|
# Compute current color counts from detections
|
|
@@ -939,20 +1129,13 @@ class ColorDetectionUseCase(BaseProcessor):
|
|
|
939
1129
|
|
|
940
1130
|
current_timestamp = self._get_current_timestamp_str(stream_info, precision=False)
|
|
941
1131
|
start_timestamp = self._get_start_timestamp_str(stream_info, precision=False)
|
|
942
|
-
|
|
1132
|
+
|
|
943
1133
|
# Create high precision timestamps for input_timestamp and reset_timestamp
|
|
944
1134
|
high_precision_start_timestamp = self._get_current_timestamp_str(stream_info, precision=True)
|
|
945
1135
|
high_precision_reset_timestamp = self._get_start_timestamp_str(stream_info, precision=True)
|
|
946
1136
|
|
|
947
1137
|
camera_info = self.get_camera_info_from_stream(stream_info)
|
|
948
|
-
total_color_data = self.color_helper(curr_frame_color)
|
|
949
|
-
print("========================CURR FRAME=======================")
|
|
950
|
-
print(curr_frame_color)
|
|
951
|
-
print("========================CURR FRAME=======================")
|
|
952
|
-
|
|
953
|
-
print("========================TOTAL=======================")
|
|
954
|
-
print(total_color_data)
|
|
955
|
-
print("========================TOTAL=======================")
|
|
1138
|
+
# total_color_data = self.color_helper(curr_frame_color)
|
|
956
1139
|
|
|
957
1140
|
human_text_lines = []
|
|
958
1141
|
color_counts = {}
|
|
@@ -963,51 +1146,72 @@ class ColorDetectionUseCase(BaseProcessor):
|
|
|
963
1146
|
if color not in color_counts:
|
|
964
1147
|
color_counts[color] = 0
|
|
965
1148
|
color_counts[color] += 1
|
|
1149
|
+
zone_frame_data = {}
|
|
1150
|
+
if curr_frame_color:
|
|
1151
|
+
for tid, data in curr_frame_color.items():
|
|
1152
|
+
zone = data.get("zone_name", "Unknown_Zone")
|
|
1153
|
+
color = data.get("color", "unknown")
|
|
1154
|
+
category = data.get("object_label", "unknown")
|
|
1155
|
+
|
|
1156
|
+
if zone not in zone_frame_data:
|
|
1157
|
+
zone_frame_data[zone] = {
|
|
1158
|
+
"color_counts": {},
|
|
1159
|
+
"category_counts": {}
|
|
1160
|
+
}
|
|
966
1161
|
|
|
967
|
-
|
|
968
|
-
|
|
1162
|
+
# Count colors
|
|
1163
|
+
zone_frame_data[zone]["color_counts"][color] = (
|
|
1164
|
+
zone_frame_data[zone]["color_counts"].get(color, 0) + 1
|
|
1165
|
+
)
|
|
969
1166
|
|
|
1167
|
+
# Count vehicle types
|
|
1168
|
+
zone_frame_data[zone]["category_counts"][category] = (
|
|
1169
|
+
zone_frame_data[zone]["category_counts"].get(category, 0) + 1
|
|
1170
|
+
)
|
|
970
1171
|
|
|
971
1172
|
# CURRENT FRAME section
|
|
972
1173
|
human_text_lines.append(f"CURRENT FRAME @ {current_timestamp}:")
|
|
973
|
-
if total_detections
|
|
974
|
-
# Vehicle categories (current frame)
|
|
975
|
-
category_counts = [f"{count} {cat}" for cat, count in per_category_count.items()]
|
|
976
|
-
if len(category_counts) == 1:
|
|
977
|
-
detection_text = category_counts[0] + " detected"
|
|
978
|
-
elif len(category_counts) == 2:
|
|
979
|
-
detection_text = f"{category_counts[0]} and {category_counts[1]} detected"
|
|
980
|
-
else:
|
|
981
|
-
detection_text = f"{', '.join(category_counts[:-1])}, and {category_counts[-1]} detected"
|
|
982
|
-
human_text_lines.append(f"\t- {detection_text}")
|
|
983
|
-
|
|
984
|
-
# Colors (current frame)
|
|
985
|
-
if color_counts:
|
|
986
|
-
color_counts_text = ", ".join([f"{count} {color}" for color, count in color_counts.items()])
|
|
987
|
-
human_text_lines.append(f"\t- Colors: {color_counts_text}")
|
|
988
|
-
else:
|
|
1174
|
+
if not curr_frame_color or total_detections == 0:
|
|
989
1175
|
human_text_lines.append(f"\t- No detections")
|
|
1176
|
+
else:
|
|
1177
|
+
for zone_name, stats in zone_frame_data.items():
|
|
1178
|
+
color_counts = stats["color_counts"]
|
|
1179
|
+
per_category_count = stats["category_counts"]
|
|
1180
|
+
if config.zone_config:
|
|
1181
|
+
human_text_lines.append(f"\t{zone_name}:")
|
|
1182
|
+
if per_category_count:
|
|
1183
|
+
category_counts = [f"{count} {cat}" for cat, count in per_category_count.items()]
|
|
1184
|
+
if len(category_counts) == 1:
|
|
1185
|
+
detection_text = category_counts[0] + " detected"
|
|
1186
|
+
elif len(category_counts) == 2:
|
|
1187
|
+
detection_text = f"{category_counts[0]} and {category_counts[1]} detected"
|
|
1188
|
+
else:
|
|
1189
|
+
detection_text = f"{', '.join(category_counts[:-1])}, and {category_counts[-1]} detected"
|
|
1190
|
+
human_text_lines.append(f"\t\t- {detection_text}")
|
|
1191
|
+
|
|
1192
|
+
if color_counts:
|
|
1193
|
+
color_counts_text = ", ".join([f"{count} {color}" for color, count in color_counts.items()])
|
|
1194
|
+
human_text_lines.append(f"\t\t- Colors: {color_counts_text}")
|
|
990
1195
|
|
|
991
1196
|
human_text_lines.append("") # spacing
|
|
992
1197
|
|
|
1198
|
+
cumulative_total = sum(self.all_color_counts.values())
|
|
1199
|
+
stats = self.zone_vehicle_stats
|
|
1200
|
+
|
|
993
1201
|
# TOTAL SINCE section
|
|
994
1202
|
human_text_lines.append(f"TOTAL SINCE {start_timestamp}:")
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
human_text_lines.append("\t-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
if count > 0:
|
|
1008
|
-
human_text_lines.append(f"\t\t- {color}: {count}")
|
|
1009
|
-
# Build current_counts array in expected format
|
|
1010
|
-
# Build arrays
|
|
1203
|
+
for zone_name, vehicles in stats.items():
|
|
1204
|
+
total_in_zone = sum(sum(colors.values()) for colors in vehicles.values())
|
|
1205
|
+
if config.zone_config:
|
|
1206
|
+
human_text_lines.append(f"\t{zone_name}:")
|
|
1207
|
+
human_text_lines.append(f"\t\t- Total Detected: {total_in_zone}")
|
|
1208
|
+
|
|
1209
|
+
for vehicle_type, colors in vehicles.items():
|
|
1210
|
+
total_type_count = sum(colors.values())
|
|
1211
|
+
human_text_lines.append(f"\t\t- {vehicle_type}: {total_type_count}")
|
|
1212
|
+
for color, count in colors.items():
|
|
1213
|
+
human_text_lines.append(f"\t\t\t- {color}: {count}")
|
|
1214
|
+
|
|
1011
1215
|
current_counts_categories = []
|
|
1012
1216
|
for cat, count in per_category_count.items():
|
|
1013
1217
|
if count > 0 or total_detections > 0:
|
|
@@ -1030,9 +1234,9 @@ class ColorDetectionUseCase(BaseProcessor):
|
|
|
1030
1234
|
# Include detections with masks from counting_summary
|
|
1031
1235
|
# Prepare detections without confidence scores (as per eg.json)
|
|
1032
1236
|
detections = []
|
|
1033
|
-
for detection in
|
|
1237
|
+
for detection in new_color_summary.get("detections", []):
|
|
1034
1238
|
bbox = detection.get("bounding_box", {})
|
|
1035
|
-
category = detection.get("
|
|
1239
|
+
category = detection.get("main_color", "No_color")
|
|
1036
1240
|
# Include segmentation if available (like in eg.json)
|
|
1037
1241
|
if detection.get("masks"):
|
|
1038
1242
|
segmentation= detection.get("masks", [])
|
|
@@ -1077,68 +1281,16 @@ class ColorDetectionUseCase(BaseProcessor):
|
|
|
1077
1281
|
}
|
|
1078
1282
|
]
|
|
1079
1283
|
|
|
1284
|
+
|
|
1080
1285
|
# Keep backward-compat: put colors into total_counts and categories into current_counts
|
|
1081
1286
|
tracking_stat=self.create_tracking_stats(total_counts=total_counts_colors, current_counts=current_counts_categories,
|
|
1082
1287
|
detections=detections, human_text=human_text, camera_info=camera_info, alerts=alerts, alert_settings=alert_settings,
|
|
1083
1288
|
reset_settings=reset_settings, start_time=high_precision_start_timestamp ,
|
|
1084
1289
|
reset_time=high_precision_reset_timestamp)
|
|
1085
1290
|
|
|
1086
|
-
# Add explicit breakdowns for consumers who want both types
|
|
1087
|
-
# tracking_stat["current_category_counts"] = current_counts_categories
|
|
1088
|
-
# tracking_stat["current_color_counts"] = current_counts_colors
|
|
1089
|
-
# tracking_stat["total_category_counts"] = total_counts_categories
|
|
1090
|
-
# tracking_stat["total_color_counts"] = total_counts_colors
|
|
1091
|
-
|
|
1092
1291
|
tracking_stats.append(tracking_stat)
|
|
1093
1292
|
return tracking_stats
|
|
1094
|
-
|
|
1095
|
-
def _generate_human_text_for_tracking(self, total_detections: int, color_summary: Dict, insights: List[str], summary: str, config: ColorDetectionConfig) -> str:
|
|
1096
|
-
"""Generate human-readable text for tracking stats."""
|
|
1097
|
-
from datetime import datetime, timezone
|
|
1098
|
-
|
|
1099
|
-
text_parts = [
|
|
1100
|
-
#f"Tracking Start Time: {datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M')}",
|
|
1101
|
-
#f"Objects Analyzed: {total_detections}"
|
|
1102
|
-
]
|
|
1103
|
-
|
|
1104
|
-
if config.time_window_minutes:
|
|
1105
|
-
detection_rate_per_hour = (total_detections / config.time_window_minutes) * 60
|
|
1106
|
-
#text_parts.append(f"Detection Rate: {detection_rate_per_hour:.1f} objects per hour")
|
|
1107
|
-
|
|
1108
|
-
# Add color statistics
|
|
1109
|
-
unique_colors = len(color_summary.get("color_distribution", {}))
|
|
1110
|
-
#text_parts.append(f"Unique Colors Detected: {unique_colors}")
|
|
1111
|
-
|
|
1112
|
-
if total_detections > 0:
|
|
1113
|
-
color_diversity = (unique_colors / total_detections) * 100
|
|
1114
|
-
#text_parts.append(f"Color Diversity: {color_diversity:.1f}%")
|
|
1115
|
-
|
|
1116
|
-
# Add category breakdown
|
|
1117
|
-
categories = color_summary.get("categories", {})
|
|
1118
|
-
if categories:
|
|
1119
|
-
#text_parts.append(f"Categories Analyzed: {len(categories)}")
|
|
1120
|
-
for category, colors in categories.items():
|
|
1121
|
-
category_total = sum(colors.values())
|
|
1122
|
-
if category_total > 0:
|
|
1123
|
-
dominant_color = max(colors.items(), key=lambda x: x[1])[0] if colors else "unknown"
|
|
1124
|
-
text_parts.append(f" {category_total} {category.title()} detected, Color: {dominant_color}")
|
|
1125
|
-
|
|
1126
|
-
# Add color distribution summary
|
|
1127
|
-
color_distribution = color_summary.get("color_distribution", {})
|
|
1128
|
-
if color_distribution:
|
|
1129
|
-
top_colors = sorted(color_distribution.items(), key=lambda x: x[1], reverse=True)[:3]
|
|
1130
|
-
#text_parts.append("Top Colors:")
|
|
1131
|
-
for color, count in top_colors:
|
|
1132
|
-
percentage = (count / total_detections) * 100
|
|
1133
|
-
#text_parts.append(f" {color.title()}: {count} objects ({percentage:.1f}%)")
|
|
1134
|
-
|
|
1135
|
-
# Add key insights
|
|
1136
|
-
# if insights:
|
|
1137
|
-
# text_parts.append("Key Color Insights:")
|
|
1138
|
-
# for insight in insights[:3]: # Limit to first 3 insights
|
|
1139
|
-
# text_parts.append(f" - {insight}")
|
|
1140
|
-
|
|
1141
|
-
return "\n".join(text_parts)
|
|
1293
|
+
|
|
1142
1294
|
|
|
1143
1295
|
def reset_tracker(self) -> None:
|
|
1144
1296
|
"""Reset the advanced tracker instance."""
|
|
@@ -1222,7 +1374,7 @@ class ColorDetectionUseCase(BaseProcessor):
|
|
|
1222
1374
|
filtered.append(detections[best_idx])
|
|
1223
1375
|
used[best_idx] = True
|
|
1224
1376
|
return filtered
|
|
1225
|
-
|
|
1377
|
+
|
|
1226
1378
|
def get_config_schema(self) -> Dict[str, Any]:
|
|
1227
1379
|
"""Get JSON schema for configuration validation."""
|
|
1228
1380
|
return {
|
|
@@ -1242,7 +1394,7 @@ class ColorDetectionUseCase(BaseProcessor):
|
|
|
1242
1394
|
"required": ["confidence_threshold", "top_k_colors"],
|
|
1243
1395
|
"additionalProperties": False
|
|
1244
1396
|
}
|
|
1245
|
-
|
|
1397
|
+
|
|
1246
1398
|
def create_default_config(self, **overrides) -> ColorDetectionConfig:
|
|
1247
1399
|
"""Create default configuration with optional overrides."""
|
|
1248
1400
|
defaults = {
|
|
@@ -1261,7 +1413,7 @@ class ColorDetectionUseCase(BaseProcessor):
|
|
|
1261
1413
|
}
|
|
1262
1414
|
defaults.update(overrides)
|
|
1263
1415
|
return ColorDetectionConfig(**defaults)
|
|
1264
|
-
|
|
1416
|
+
|
|
1265
1417
|
def _update_color_tracking_state(self, detections: List[Dict]):
|
|
1266
1418
|
"""Track unique track_ids per category and color for total count."""
|
|
1267
1419
|
# Ensure storage is a defaultdict(set) to allow safe .add()
|
|
@@ -1298,20 +1450,19 @@ class ColorDetectionUseCase(BaseProcessor):
|
|
|
1298
1450
|
color_to_ids[color].update(ids)
|
|
1299
1451
|
return {color: len(ids) for color, ids in color_to_ids.items()}
|
|
1300
1452
|
|
|
1301
|
-
def get_total_category_counts(self):
|
|
1453
|
+
def get_total_category_counts(self,data):
|
|
1302
1454
|
"""Return total unique track_id count per category (across all colors)."""
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
return {cat: len(ids) for cat, ids in category_to_ids.items()}
|
|
1455
|
+
for det in data:
|
|
1456
|
+
track_id = det.get("track_id")
|
|
1457
|
+
category = det.get("category")
|
|
1458
|
+
if track_id and category:
|
|
1459
|
+
if category not in self.total_category_count:
|
|
1460
|
+
self.total_category_count[category] = set()
|
|
1461
|
+
self.total_category_count[category].add(track_id)
|
|
1462
|
+
|
|
1463
|
+
# Convert sets to counts
|
|
1464
|
+
return {cat: len(track_ids) for cat, track_ids in self.total_category_count.items()}
|
|
1465
|
+
|
|
1315
1466
|
|
|
1316
1467
|
def _get_track_ids_info(self, detections: List[Dict]) -> Dict[str, Any]:
|
|
1317
1468
|
"""Get detailed information about track IDs for color detections (per frame)."""
|
|
@@ -1379,7 +1530,7 @@ class ColorDetectionUseCase(BaseProcessor):
|
|
|
1379
1530
|
det.setdefault("masks", ["EMPTY"])
|
|
1380
1531
|
|
|
1381
1532
|
return processed_detections
|
|
1382
|
-
|
|
1533
|
+
|
|
1383
1534
|
def _generate_incidents(self, counting_summary: Dict, alerts: List, config: ColorDetectionConfig,
|
|
1384
1535
|
frame_number: Optional[int] = None, stream_info: Optional[Dict[str, Any]] = None) -> List[Dict]:
|
|
1385
1536
|
"""Generate structured events for the output format with frame-based keys."""
|
|
@@ -1390,7 +1541,7 @@ class ColorDetectionUseCase(BaseProcessor):
|
|
|
1390
1541
|
total_detections = counting_summary.get("total_count", 0)
|
|
1391
1542
|
current_timestamp = self._get_current_timestamp_str(stream_info)
|
|
1392
1543
|
camera_info = self.get_camera_info_from_stream(stream_info)
|
|
1393
|
-
|
|
1544
|
+
|
|
1394
1545
|
self._ascending_alert_list = self._ascending_alert_list[-900:] if len(self._ascending_alert_list) > 900 else self._ascending_alert_list
|
|
1395
1546
|
|
|
1396
1547
|
if total_detections > 0:
|
|
@@ -1401,11 +1552,11 @@ class ColorDetectionUseCase(BaseProcessor):
|
|
|
1401
1552
|
if start_timestamp and self.current_incident_end_timestamp=='N/A':
|
|
1402
1553
|
self.current_incident_end_timestamp = 'Incident still active'
|
|
1403
1554
|
elif start_timestamp and self.current_incident_end_timestamp=='Incident still active':
|
|
1404
|
-
if len(self._ascending_alert_list) >= 15 and sum(self._ascending_alert_list[-15:]) / 15 < 1.5:
|
|
1555
|
+
if len(self._ascending_alert_list) >= 15 and sum(self._ascending_alert_list[-15:]) / 15 < 1.5:
|
|
1405
1556
|
self.current_incident_end_timestamp = current_timestamp
|
|
1406
1557
|
elif self.current_incident_end_timestamp!='Incident still active' and self.current_incident_end_timestamp!='N/A':
|
|
1407
1558
|
self.current_incident_end_timestamp = 'N/A'
|
|
1408
|
-
|
|
1559
|
+
|
|
1409
1560
|
if config.alert_config and hasattr(config.alert_config, 'count_thresholds') and config.alert_config.count_thresholds:
|
|
1410
1561
|
threshold = config.alert_config.count_thresholds.get("all", 15)
|
|
1411
1562
|
intensity = min(10.0, (total_detections / threshold) * 10)
|
|
@@ -1456,7 +1607,7 @@ class ColorDetectionUseCase(BaseProcessor):
|
|
|
1456
1607
|
getattr(config.alert_config, 'alert_value', ['JSON']) if hasattr(config.alert_config, 'alert_value') else ['JSON'])
|
|
1457
1608
|
}
|
|
1458
1609
|
})
|
|
1459
|
-
|
|
1610
|
+
|
|
1460
1611
|
event= self.create_incident(incident_id=self.CASE_TYPE+'_'+str(frame_number), incident_type=self.CASE_TYPE,
|
|
1461
1612
|
severity_level=level, human_text=human_text, camera_info=camera_info, alerts=alerts, alert_settings=alert_settings,
|
|
1462
1613
|
start_time=start_timestamp, end_time=self.current_incident_end_timestamp,
|
|
@@ -1468,7 +1619,7 @@ class ColorDetectionUseCase(BaseProcessor):
|
|
|
1468
1619
|
incidents.append({})
|
|
1469
1620
|
|
|
1470
1621
|
return incidents
|
|
1471
|
-
|
|
1622
|
+
|
|
1472
1623
|
def _check_alerts(self, summary: dict, frame_number:Any, config: ColorDetectionConfig) -> List[Dict]:
|
|
1473
1624
|
"""
|
|
1474
1625
|
Check if any alert thresholds are exceeded and return alert dicts.
|
|
@@ -1510,7 +1661,7 @@ class ColorDetectionUseCase(BaseProcessor):
|
|
|
1510
1661
|
if hasattr(config.alert_config, 'count_thresholds') and config.alert_config.count_thresholds:
|
|
1511
1662
|
|
|
1512
1663
|
for category, threshold in config.alert_config.count_thresholds.items():
|
|
1513
|
-
if category == "all" and total > threshold:
|
|
1664
|
+
if category == "all" and total > threshold:
|
|
1514
1665
|
|
|
1515
1666
|
alerts.append({
|
|
1516
1667
|
"alert_type": getattr(config.alert_config, 'alert_type', ['Default']) if hasattr(config.alert_config, 'alert_type') else ['Default'],
|
|
@@ -1520,7 +1671,7 @@ class ColorDetectionUseCase(BaseProcessor):
|
|
|
1520
1671
|
"ascending": get_trend(self._ascending_alert_list, lookback=900, threshold=0.8),
|
|
1521
1672
|
"settings": {t: v for t, v in zip(getattr(config.alert_config, 'alert_type', ['Default']) if hasattr(config.alert_config, 'alert_type') else ['Default'],
|
|
1522
1673
|
getattr(config.alert_config, 'alert_value', ['JSON']) if hasattr(config.alert_config, 'alert_value') else ['JSON'])
|
|
1523
|
-
}
|
|
1674
|
+
}
|
|
1524
1675
|
})
|
|
1525
1676
|
elif category in summary.get("per_category_count", {}):
|
|
1526
1677
|
count = summary.get("per_category_count", {})[category]
|
|
@@ -1533,12 +1684,17 @@ class ColorDetectionUseCase(BaseProcessor):
|
|
|
1533
1684
|
"ascending": get_trend(self._ascending_alert_list, lookback=900, threshold=0.8),
|
|
1534
1685
|
"settings": {t: v for t, v in zip(getattr(config.alert_config, 'alert_type', ['Default']) if hasattr(config.alert_config, 'alert_type') else ['Default'],
|
|
1535
1686
|
getattr(config.alert_config, 'alert_value', ['JSON']) if hasattr(config.alert_config, 'alert_value') else ['JSON'])
|
|
1536
|
-
}
|
|
1687
|
+
}
|
|
1537
1688
|
})
|
|
1538
1689
|
else:
|
|
1539
1690
|
pass
|
|
1540
1691
|
return alerts
|
|
1541
|
-
|
|
1692
|
+
|
|
1693
|
+
def _format_timestamp_for_stream(self, timestamp: float) -> str:
|
|
1694
|
+
"""Format timestamp for streams (YYYY:MM:DD HH:MM:SS format)."""
|
|
1695
|
+
dt = datetime.fromtimestamp(float(timestamp), tz=timezone.utc)
|
|
1696
|
+
return dt.strftime('%Y:%m:%d %H:%M:%S')
|
|
1697
|
+
|
|
1542
1698
|
def _format_timestamp_for_video(self, timestamp: float) -> str:
|
|
1543
1699
|
"""Format timestamp for video chunks (HH:MM:SS.ms format)."""
|
|
1544
1700
|
hours = int(timestamp // 3600)
|
|
@@ -1546,11 +1702,6 @@ class ColorDetectionUseCase(BaseProcessor):
|
|
|
1546
1702
|
seconds = round(float(timestamp % 60),2)
|
|
1547
1703
|
return f"{hours:02d}:{minutes:02d}:{seconds:.1f}"
|
|
1548
1704
|
|
|
1549
|
-
def _format_timestamp_for_stream(self, timestamp: float) -> str:
|
|
1550
|
-
"""Format timestamp for streams (YYYY:MM:DD HH:MM:SS format)."""
|
|
1551
|
-
dt = datetime.fromtimestamp(timestamp, tz=timezone.utc)
|
|
1552
|
-
return dt.strftime('%Y:%m:%d %H:%M:%S')
|
|
1553
|
-
|
|
1554
1705
|
def _get_current_timestamp_str(self, stream_info: Optional[Dict[str, Any]], precision=False, frame_id: Optional[str]=None) -> str:
|
|
1555
1706
|
"""Get formatted current timestamp based on stream type."""
|
|
1556
1707
|
|
|
@@ -1564,6 +1715,7 @@ class ColorDetectionUseCase(BaseProcessor):
|
|
|
1564
1715
|
start_time = stream_info.get("input_settings", {}).get("start_frame", 30)/stream_info.get("input_settings", {}).get("original_fps", 30)
|
|
1565
1716
|
stream_time_str = self._format_timestamp_for_video(start_time)
|
|
1566
1717
|
|
|
1718
|
+
|
|
1567
1719
|
return self._format_timestamp(stream_info.get("input_settings", {}).get("stream_time", "NA"))
|
|
1568
1720
|
else:
|
|
1569
1721
|
return datetime.now(timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC")
|
|
@@ -1575,8 +1727,7 @@ class ColorDetectionUseCase(BaseProcessor):
|
|
|
1575
1727
|
start_time = stream_info.get("input_settings", {}).get("start_frame", 30)/stream_info.get("input_settings", {}).get("original_fps", 30)
|
|
1576
1728
|
|
|
1577
1729
|
stream_time_str = self._format_timestamp_for_video(start_time)
|
|
1578
|
-
|
|
1579
|
-
|
|
1730
|
+
|
|
1580
1731
|
return self._format_timestamp(stream_info.get("input_settings", {}).get("stream_time", "NA"))
|
|
1581
1732
|
else:
|
|
1582
1733
|
stream_time_str = stream_info.get("input_settings", {}).get("stream_info", {}).get("stream_time", "")
|
|
@@ -1595,22 +1746,22 @@ class ColorDetectionUseCase(BaseProcessor):
|
|
|
1595
1746
|
"""Get formatted start timestamp for 'TOTAL SINCE' based on stream type."""
|
|
1596
1747
|
if not stream_info:
|
|
1597
1748
|
return "00:00:00"
|
|
1598
|
-
|
|
1749
|
+
|
|
1599
1750
|
if precision:
|
|
1600
1751
|
if self.start_timer is None:
|
|
1601
|
-
self.start_timer = stream_info.get("input_settings", {}).get("stream_time", "
|
|
1752
|
+
self.start_timer = stream_info.get("input_settings", {}).get("stream_time", datetime.now(timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC"))
|
|
1602
1753
|
return self._format_timestamp(self.start_timer)
|
|
1603
1754
|
elif stream_info.get("input_settings", {}).get("start_frame", "na") == 1:
|
|
1604
|
-
self.start_timer = stream_info.get("input_settings", {}).get("stream_time", "
|
|
1755
|
+
self.start_timer = stream_info.get("input_settings", {}).get("stream_time", datetime.now(timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC"))
|
|
1605
1756
|
return self._format_timestamp(self.start_timer)
|
|
1606
1757
|
else:
|
|
1607
1758
|
return self._format_timestamp(self.start_timer)
|
|
1608
1759
|
|
|
1609
1760
|
if self.start_timer is None:
|
|
1610
|
-
self.start_timer = stream_info.get("input_settings", {}).get("stream_time", "
|
|
1761
|
+
self.start_timer = stream_info.get("input_settings", {}).get("stream_time", datetime.now(timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC"))
|
|
1611
1762
|
return self._format_timestamp(self.start_timer)
|
|
1612
1763
|
elif stream_info.get("input_settings", {}).get("start_frame", "na") == 1:
|
|
1613
|
-
self.start_timer = stream_info.get("input_settings", {}).get("stream_time", "
|
|
1764
|
+
self.start_timer = stream_info.get("input_settings", {}).get("stream_time", datetime.now(timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC"))
|
|
1614
1765
|
return self._format_timestamp(self.start_timer)
|
|
1615
1766
|
|
|
1616
1767
|
else:
|
|
@@ -1632,7 +1783,7 @@ class ColorDetectionUseCase(BaseProcessor):
|
|
|
1632
1783
|
dt = datetime.fromtimestamp(self._tracking_start_time, tz=timezone.utc)
|
|
1633
1784
|
dt = dt.replace(minute=0, second=0, microsecond=0)
|
|
1634
1785
|
return dt.strftime('%Y:%m:%d %H:%M:%S')
|
|
1635
|
-
|
|
1786
|
+
|
|
1636
1787
|
def _format_timestamp(self, timestamp: Any) -> str:
|
|
1637
1788
|
"""Format a timestamp so that exactly two digits follow the decimal point (milliseconds).
|
|
1638
1789
|
|
|
@@ -1693,37 +1844,37 @@ class ColorDetectionUseCase(BaseProcessor):
|
|
|
1693
1844
|
"""Update zone tracking with current frame data."""
|
|
1694
1845
|
if not zone_analysis or not config.zone_config or not config.zone_config['zones']:
|
|
1695
1846
|
return {}
|
|
1696
|
-
|
|
1847
|
+
|
|
1697
1848
|
enhanced_zone_analysis = {}
|
|
1698
1849
|
zones = config.zone_config['zones']
|
|
1699
|
-
|
|
1850
|
+
|
|
1700
1851
|
# Initialize current frame zone tracks
|
|
1701
1852
|
current_frame_zone_tracks = {zone_name: set() for zone_name in zones.keys()}
|
|
1702
|
-
|
|
1853
|
+
|
|
1703
1854
|
# Initialize zone tracking storage
|
|
1704
1855
|
for zone_name in zones.keys():
|
|
1705
1856
|
if zone_name not in self._zone_current_track_ids:
|
|
1706
1857
|
self._zone_current_track_ids[zone_name] = set()
|
|
1707
1858
|
if zone_name not in self._zone_total_track_ids:
|
|
1708
1859
|
self._zone_total_track_ids[zone_name] = set()
|
|
1709
|
-
|
|
1860
|
+
|
|
1710
1861
|
# Check each detection against each zone
|
|
1711
1862
|
for detection in detections:
|
|
1712
1863
|
track_id = detection.get("track_id")
|
|
1713
1864
|
if track_id is None:
|
|
1714
1865
|
continue
|
|
1715
|
-
|
|
1866
|
+
|
|
1716
1867
|
bbox = detection.get("bounding_box", detection.get("bbox"))
|
|
1717
1868
|
if not bbox:
|
|
1718
1869
|
continue
|
|
1719
|
-
|
|
1870
|
+
|
|
1720
1871
|
# Check which zone this detection is in
|
|
1721
1872
|
for zone_name, zone_polygon in zones.items():
|
|
1722
1873
|
if self._is_in_zone(bbox, zone_polygon):
|
|
1723
1874
|
current_frame_zone_tracks[zone_name].add(track_id)
|
|
1724
1875
|
if track_id not in self.color_det_dict: # Use color_det_dict for consistency
|
|
1725
1876
|
self.color_det_dict[track_id] = [detection.get("main_color", "unknown"), detection.get("confidence", 0.0)]
|
|
1726
|
-
|
|
1877
|
+
|
|
1727
1878
|
# Update zone tracking for each zone
|
|
1728
1879
|
for zone_name, zone_counts in zone_analysis.items():
|
|
1729
1880
|
current_tracks = current_frame_zone_tracks.get(zone_name, set())
|
|
@@ -1731,7 +1882,7 @@ class ColorDetectionUseCase(BaseProcessor):
|
|
|
1731
1882
|
self._zone_total_track_ids[zone_name].update(current_tracks)
|
|
1732
1883
|
self._zone_current_counts[zone_name] = len(current_tracks)
|
|
1733
1884
|
self._zone_total_counts[zone_name] = len(self._zone_total_track_ids[zone_name])
|
|
1734
|
-
|
|
1885
|
+
|
|
1735
1886
|
enhanced_zone_analysis[zone_name] = {
|
|
1736
1887
|
"current_count": self._zone_current_counts[zone_name],
|
|
1737
1888
|
"total_count": self._zone_total_counts[zone_name],
|
|
@@ -1739,7 +1890,7 @@ class ColorDetectionUseCase(BaseProcessor):
|
|
|
1739
1890
|
"total_track_ids": list(self._zone_total_track_ids[zone_name]),
|
|
1740
1891
|
"original_counts": zone_counts
|
|
1741
1892
|
}
|
|
1742
|
-
|
|
1893
|
+
|
|
1743
1894
|
return enhanced_zone_analysis
|
|
1744
1895
|
|
|
1745
1896
|
def _compute_iou(self, box1: Any, box2: Any) -> float:
|