matrice 1.0.99267__py3-none-any.whl → 1.0.99269__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/core/config.py +6 -0
- matrice/deploy/utils/post_processing/usecases/proximity_detection.py +250 -106
- {matrice-1.0.99267.dist-info → matrice-1.0.99269.dist-info}/METADATA +1 -1
- {matrice-1.0.99267.dist-info → matrice-1.0.99269.dist-info}/RECORD +7 -7
- {matrice-1.0.99267.dist-info → matrice-1.0.99269.dist-info}/WHEEL +0 -0
- {matrice-1.0.99267.dist-info → matrice-1.0.99269.dist-info}/licenses/LICENSE.txt +0 -0
- {matrice-1.0.99267.dist-info → matrice-1.0.99269.dist-info}/top_level.txt +0 -0
@@ -391,6 +391,12 @@ class ProximityConfig(BaseConfig):
|
|
391
391
|
enable_unique_counting: bool = True
|
392
392
|
time_window_minutes: int = 60
|
393
393
|
|
394
|
+
proximity_threshold_meters: float = 1.0
|
395
|
+
proximity_threshold_pixels: float = 250.0
|
396
|
+
meters_per_pixel: float = 0.0028
|
397
|
+
scene_width_meters: float = 0.0
|
398
|
+
scene_height_meters: float = 0.0
|
399
|
+
|
394
400
|
# Category mapping
|
395
401
|
person_categories: List[str] = field(default_factory=lambda: ["person"])
|
396
402
|
index_to_category: Optional[Dict[int, str]] = None
|
@@ -6,9 +6,9 @@ with zone-based analysis, tracking, and alerting capabilities.
|
|
6
6
|
"""
|
7
7
|
|
8
8
|
from typing import Any, Dict, List, Optional, Set
|
9
|
-
from dataclasses import asdict
|
10
9
|
import time
|
11
10
|
from datetime import datetime, timezone
|
11
|
+
import math
|
12
12
|
|
13
13
|
from ..core.base import BaseProcessor, ProcessingContext, ProcessingResult, ConfigProtocol, ResultFormat
|
14
14
|
from ..core.config import ProximityConfig, ZoneConfig, AlertConfig
|
@@ -65,6 +65,7 @@ class ProximityUseCase(BaseProcessor):
|
|
65
65
|
|
66
66
|
# Proximity counting
|
67
67
|
self._total_proximity_count = 0 # Total proximity events across all calls
|
68
|
+
self._observed_proximity_pairs: Set[frozenset] = set() # Unique canonical ID pairs seen across frames
|
68
69
|
|
69
70
|
|
70
71
|
# --------------------------------------------------------------------- #
|
@@ -108,7 +109,7 @@ class ProximityUseCase(BaseProcessor):
|
|
108
109
|
Returns:
|
109
110
|
ProcessingResult: Processing result with standardized agg_summary structure
|
110
111
|
"""
|
111
|
-
start_time = time.time()
|
112
|
+
# start_time = time.time()
|
112
113
|
|
113
114
|
try:
|
114
115
|
# Ensure we have the right config type
|
@@ -144,8 +145,8 @@ class ProximityUseCase(BaseProcessor):
|
|
144
145
|
else:
|
145
146
|
return self._process_single_frame(data, config, context, stream_info)
|
146
147
|
|
147
|
-
except Exception as e:
|
148
|
-
self.logger.error(
|
148
|
+
except Exception as e: # noqa: BLE001
|
149
|
+
self.logger.error("Proximity detection failed: %s", str(e), exc_info=True)
|
149
150
|
|
150
151
|
if context:
|
151
152
|
context.mark_completed()
|
@@ -283,9 +284,13 @@ class ProximityUseCase(BaseProcessor):
|
|
283
284
|
current_count = counting_summary["categories"].get(category, 0)
|
284
285
|
counting_summary["categories"][category] = current_count + 1
|
285
286
|
|
286
|
-
|
287
|
+
# Update tracking state BEFORE proximity calculation so we have canonical IDs
|
288
|
+
self._update_tracking_state(counting_summary)
|
289
|
+
|
290
|
+
# Calculate unique proximity events for this frame (meters-aware)
|
291
|
+
proximity_count = self._count_proximity_events(counting_summary["detections"], config, stream_info)
|
287
292
|
counting_summary["proximity_events"] = proximity_count
|
288
|
-
counting_summary["total_proximity_count"] = self._total_proximity_count
|
293
|
+
counting_summary["total_proximity_count"] = self._total_proximity_count
|
289
294
|
|
290
295
|
# Step 5: Zone analysis for this frame
|
291
296
|
zone_analysis = {}
|
@@ -301,9 +306,6 @@ class ProximityUseCase(BaseProcessor):
|
|
301
306
|
for zone_name, enhanced_data in enhanced_zone_analysis.items():
|
302
307
|
zone_analysis[zone_name] = enhanced_data
|
303
308
|
|
304
|
-
# Step 4.5: Always update tracking state (regardless of enable_unique_counting setting)
|
305
|
-
self._update_tracking_state(counting_summary)
|
306
|
-
|
307
309
|
# Step 5: Generate insights and alerts for this frame
|
308
310
|
alerts = self._check_alerts(counting_summary, zone_analysis, config, frame_id)
|
309
311
|
|
@@ -402,11 +404,10 @@ class ProximityUseCase(BaseProcessor):
|
|
402
404
|
|
403
405
|
# Add zone-specific events if applicable
|
404
406
|
if zone_analysis:
|
405
|
-
human_text_lines.append(
|
407
|
+
human_text_lines.append("\t- ZONE EVENTS:")
|
406
408
|
for zone_name, zone_count in zone_analysis.items():
|
407
409
|
zone_total = self._robust_zone_total(zone_count)
|
408
410
|
if zone_total > 0:
|
409
|
-
zone_intensity = min(10.0, zone_total / 5.0)
|
410
411
|
zone_level = "info"
|
411
412
|
if intensity >= 9:
|
412
413
|
level = "critical"
|
@@ -432,14 +433,14 @@ class ProximityUseCase(BaseProcessor):
|
|
432
433
|
incidents.append(event)
|
433
434
|
return incidents
|
434
435
|
|
435
|
-
def _generate_tracking_stats(self, counting_summary: Dict, zone_analysis: Dict, config: ProximityConfig, frame_id: str, alerts: Any=
|
436
|
+
def _generate_tracking_stats(self, counting_summary: Dict, zone_analysis: Dict, config: ProximityConfig, frame_id: str, alerts: Any=None, stream_info: Optional[Dict[str, Any]] = None) -> List[Dict]:
|
436
437
|
"""Generate tracking stats using standardized methods."""
|
437
438
|
|
438
439
|
total_people = counting_summary.get("total_objects", 0)
|
439
440
|
|
440
441
|
# Get total count from cached tracking state
|
441
442
|
total_unique_count = self.get_total_count()
|
442
|
-
current_frame_count = self.get_current_frame_count()
|
443
|
+
# current_frame_count = self.get_current_frame_count()
|
443
444
|
|
444
445
|
# Get camera info using standardized method
|
445
446
|
camera_info = self.get_camera_info_from_stream(stream_info)
|
@@ -520,7 +521,7 @@ class ProximityUseCase(BaseProcessor):
|
|
520
521
|
detection_obj = self.create_detection_object(category, bbox)
|
521
522
|
detections.append(detection_obj)
|
522
523
|
|
523
|
-
|
524
|
+
# detections prepared above are used only for output formatting
|
524
525
|
# Build alerts and alert_settings arrays
|
525
526
|
alert_settings = []
|
526
527
|
if config.alert_config and hasattr(config.alert_config, 'alert_type'):
|
@@ -534,42 +535,44 @@ class ProximityUseCase(BaseProcessor):
|
|
534
535
|
}
|
535
536
|
})
|
536
537
|
if zone_analysis:
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
return 0
|
556
|
-
human_text_lines.append(f"\t- People Detected: {total_people}")
|
557
|
-
human_text_lines.append("")
|
558
|
-
human_text_lines.append(f"TOTAL SINCE @ {start_timestamp}:")
|
559
|
-
|
560
|
-
for zone_name, zone_count in zone_analysis.items():
|
561
|
-
zone_total = robust_zone_total(zone_count)
|
562
|
-
human_text_lines.append(f"\t- Zone name: {zone_name}")
|
563
|
-
human_text_lines.append(f"\t\t- Total count in zone: {zone_total-1}")
|
564
|
-
|
565
|
-
if total_unique_count > 0:
|
566
|
-
human_text_lines.append(f"\t- Total unique people in the scene: {total_unique_count}")
|
567
|
-
if alerts:
|
568
|
-
for alert in alerts:
|
569
|
-
human_text_lines.append(f"Alerts: {alert.get('settings', {})} sent @ {current_timestamp}")
|
538
|
+
human_text_lines = []
|
539
|
+
current_timestamp = self._get_current_timestamp_str(stream_info, frame_id=frame_id)
|
540
|
+
start_timestamp = self._get_start_timestamp_str(stream_info)
|
541
|
+
human_text_lines.append(f"CURRENT FRAME @ {current_timestamp}:")
|
542
|
+
|
543
|
+
def robust_zone_total(zone_count):
|
544
|
+
if isinstance(zone_count, dict):
|
545
|
+
total = 0
|
546
|
+
for v in zone_count.values():
|
547
|
+
if isinstance(v, int):
|
548
|
+
total += v
|
549
|
+
elif isinstance(v, list) and total == 0:
|
550
|
+
total += len(v)
|
551
|
+
return total
|
552
|
+
elif isinstance(zone_count, list):
|
553
|
+
return len(zone_count)
|
554
|
+
elif isinstance(zone_count, int):
|
555
|
+
return zone_count
|
570
556
|
else:
|
571
|
-
|
572
|
-
|
557
|
+
return 0
|
558
|
+
|
559
|
+
human_text_lines.append(f"\t- People Detected: {total_people}")
|
560
|
+
human_text_lines.append("")
|
561
|
+
human_text_lines.append(f"TOTAL SINCE @ {start_timestamp}:")
|
562
|
+
|
563
|
+
for zone_name, zone_count in zone_analysis.items():
|
564
|
+
zone_total = robust_zone_total(zone_count)
|
565
|
+
human_text_lines.append(f"\t- Zone name: {zone_name}")
|
566
|
+
human_text_lines.append(f"\t\t- Total count in zone: {zone_total}")
|
567
|
+
|
568
|
+
if total_unique_count > 0:
|
569
|
+
human_text_lines.append(f"\t- Total unique people in the scene: {total_unique_count}")
|
570
|
+
if alerts:
|
571
|
+
for alert in alerts:
|
572
|
+
human_text_lines.append(f"Alerts: {alert.get('settings', {})} sent @ {current_timestamp}")
|
573
|
+
else:
|
574
|
+
human_text_lines.append("Alerts: None")
|
575
|
+
human_text = "\n".join(human_text_lines)
|
573
576
|
else:
|
574
577
|
human_text = self._generate_human_text_for_tracking(total_people, detections, total_unique_count, config, frame_id, alerts, stream_info)
|
575
578
|
|
@@ -580,43 +583,152 @@ class ProximityUseCase(BaseProcessor):
|
|
580
583
|
tracking_stat = self.create_tracking_stats(
|
581
584
|
total_counts, current_counts, detections, human_text, camera_info, alerts, alert_settings, start_time=high_precision_start_timestamp, reset_time=high_precision_reset_timestamp
|
582
585
|
)
|
583
|
-
print(tracking_stat)
|
584
586
|
return [tracking_stat]
|
585
587
|
|
586
|
-
def _count_proximity_events(self, detections):
|
587
|
-
"""Count proximity events between detections.
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
|
595
|
-
|
596
|
-
|
588
|
+
def _count_proximity_events(self, detections: List[Dict[str, Any]], config: ProximityConfig, stream_info: Optional[Dict[str, Any]] = None) -> int:
|
589
|
+
"""Count UNIQUE proximity events between detections in a frame.
|
590
|
+
|
591
|
+
Rules:
|
592
|
+
- If canonical track IDs are present, deduplicate by track_id first.
|
593
|
+
- Otherwise, deduplicate overlapping boxes using IoU to avoid duplicate detections of the same person.
|
594
|
+
- Count each pair once (i < j) using Euclidean distance between bottom-center points.
|
595
|
+
- Distance is evaluated in meters when calibration is available. If unavailable, pixel threshold is used as a fallback.
|
596
|
+
- Maintain a running set of unique canonical-ID pairs across frames to compute total unique proximity events.
|
597
|
+
"""
|
598
|
+
if not detections:
|
599
|
+
return 0
|
600
|
+
|
601
|
+
# Determine threshold strategy
|
602
|
+
meters_per_pixel = self._get_meters_per_pixel(config, stream_info)
|
603
|
+
threshold_meters = getattr(config, "proximity_threshold_meters", 1.0)
|
604
|
+
threshold_pixels_fallback = getattr(config, "proximity_threshold_pixels", 400.0)
|
605
|
+
|
606
|
+
iou_duplicate_threshold = getattr(self, "_proximity_iou_duplicate_threshold", 0.5)
|
607
|
+
|
608
|
+
# Step 1: Deduplicate detections
|
609
|
+
unique_detections: List[Dict[str, Any]] = []
|
610
|
+
seen_track_ids: Set[Any] = set()
|
611
|
+
|
612
|
+
for det in detections:
|
613
|
+
track_id = det.get("track_id")
|
614
|
+
bbox = det.get("bounding_box", det.get("bbox", {}))
|
615
|
+
if not bbox:
|
616
|
+
continue
|
617
|
+
|
618
|
+
# Prefer canonical track IDs if present (set earlier in _update_tracking_state)
|
619
|
+
if track_id is not None:
|
620
|
+
if track_id in seen_track_ids:
|
597
621
|
continue
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
622
|
+
seen_track_ids.add(track_id)
|
623
|
+
unique_detections.append(det)
|
624
|
+
continue
|
625
|
+
|
626
|
+
# Fallback: IoU-based dedup for pure detection outputs without track IDs
|
627
|
+
is_duplicate = False
|
628
|
+
for kept in unique_detections:
|
629
|
+
kept_bbox = kept.get("bounding_box", kept.get("bbox", {}))
|
630
|
+
if self._compute_iou(bbox, kept_bbox) >= iou_duplicate_threshold:
|
631
|
+
is_duplicate = True
|
632
|
+
break
|
633
|
+
if not is_duplicate:
|
634
|
+
unique_detections.append(det)
|
635
|
+
|
636
|
+
# Step 2: Compute proximity pairs uniquely (i < j)
|
637
|
+
frame_unique_count = 0
|
638
|
+
|
639
|
+
for i in range(len(unique_detections)):
|
640
|
+
det_i = unique_detections[i]
|
641
|
+
bbox_i = det_i.get("bounding_box", det_i.get("bbox", {}))
|
642
|
+
p_i = get_bbox_bottom25_center(bbox_i) or get_bbox_center(bbox_i)
|
643
|
+
if not p_i:
|
644
|
+
continue
|
645
|
+
|
646
|
+
for j in range(i + 1, len(unique_detections)):
|
647
|
+
det_j = unique_detections[j]
|
648
|
+
bbox_j = det_j.get("bounding_box", det_j.get("bbox", {}))
|
649
|
+
p_j = get_bbox_bottom25_center(bbox_j) or get_bbox_center(bbox_j)
|
650
|
+
if not p_j:
|
651
|
+
continue
|
652
|
+
|
653
|
+
dx = float(p_i[0]) - float(p_j[0])
|
654
|
+
dy = float(p_i[1]) - float(p_j[1])
|
655
|
+
pixel_distance = math.hypot(dx, dy)
|
656
|
+
|
657
|
+
is_close = False
|
658
|
+
if meters_per_pixel:
|
659
|
+
meters_distance = pixel_distance * float(meters_per_pixel)
|
660
|
+
if meters_distance < float(threshold_meters):
|
661
|
+
is_close = True
|
662
|
+
else:
|
663
|
+
if pixel_distance < float(threshold_pixels_fallback):
|
664
|
+
is_close = True
|
665
|
+
|
666
|
+
if not is_close:
|
667
|
+
continue
|
668
|
+
|
669
|
+
frame_unique_count += 1
|
670
|
+
|
671
|
+
# Update global unique proximity pairs only when both have canonical IDs
|
672
|
+
id_i = det_i.get("track_id")
|
673
|
+
id_j = det_j.get("track_id")
|
674
|
+
if id_i is not None and id_j is not None:
|
675
|
+
pair_key = frozenset({id_i, id_j})
|
676
|
+
if pair_key not in self._observed_proximity_pairs:
|
677
|
+
self._observed_proximity_pairs.add(pair_key)
|
678
|
+
self._total_proximity_count += 1
|
679
|
+
|
680
|
+
return frame_unique_count
|
681
|
+
|
682
|
+
def _get_meters_per_pixel(self, config: ProximityConfig, stream_info: Optional[Dict[str, Any]] = None) -> Optional[float]:
|
683
|
+
"""Compute meters-per-pixel scale using config and optional stream_info.
|
684
|
+
|
685
|
+
Priority:
|
686
|
+
1) config.meters_per_pixel (direct override)
|
687
|
+
2) config.scene_width_meters + frame width in pixels
|
688
|
+
3) config.scene_height_meters + frame height in pixels
|
689
|
+
Returns None if insufficient information.
|
690
|
+
"""
|
691
|
+
# Direct override
|
692
|
+
if hasattr(config, "meters_per_pixel") and getattr(config, "meters_per_pixel"):
|
693
|
+
try:
|
694
|
+
return float(getattr(config, "meters_per_pixel"))
|
695
|
+
except Exception: # noqa: BLE001
|
696
|
+
pass
|
697
|
+
|
698
|
+
width_px = None
|
699
|
+
height_px = None
|
700
|
+
if stream_info and isinstance(stream_info, dict):
|
701
|
+
input_settings = stream_info.get("input_settings", {}) or {}
|
702
|
+
resolution = input_settings.get("resolution", {}) or {}
|
703
|
+
width_px = resolution.get("width") or input_settings.get("frame_width")
|
704
|
+
height_px = resolution.get("height") or input_settings.get("frame_height")
|
705
|
+
|
706
|
+
# Derive from scene real-world width
|
707
|
+
if hasattr(config, "scene_width_meters") and getattr(config, "scene_width_meters") and width_px:
|
708
|
+
try:
|
709
|
+
return float(getattr(config, "scene_width_meters")) / float(width_px)
|
710
|
+
except Exception: # noqa: BLE001
|
711
|
+
pass
|
712
|
+
|
713
|
+
# Derive from scene real-world height
|
714
|
+
if hasattr(config, "scene_height_meters") and getattr(config, "scene_height_meters") and height_px:
|
715
|
+
try:
|
716
|
+
return float(getattr(config, "scene_height_meters")) / float(height_px)
|
717
|
+
except Exception: # noqa: BLE001
|
718
|
+
pass
|
719
|
+
|
720
|
+
return None
|
608
721
|
|
609
722
|
def _generate_human_text_for_tracking(
|
610
|
-
self,
|
611
|
-
total_people: int,
|
612
|
-
detections,
|
613
|
-
total_unique_count: int,
|
614
|
-
config: ProximityConfig,
|
615
|
-
frame_id: str,
|
616
|
-
alerts:Any=
|
723
|
+
self,
|
724
|
+
total_people: int,
|
725
|
+
detections,
|
726
|
+
total_unique_count: int,
|
727
|
+
config: ProximityConfig,
|
728
|
+
frame_id: str,
|
729
|
+
alerts: Any = None,
|
617
730
|
stream_info: Optional[Dict[str, Any]] = None) -> str:
|
618
731
|
"""Generate human-readable text for tracking stats in old format."""
|
619
|
-
from datetime import datetime, timezone
|
620
732
|
|
621
733
|
human_text_lines=[]
|
622
734
|
current_timestamp = self._get_current_timestamp_str(stream_info, precision=True, frame_id=frame_id)
|
@@ -624,9 +736,8 @@ class ProximityUseCase(BaseProcessor):
|
|
624
736
|
|
625
737
|
human_text_lines.append(f"CURRENT FRAME @ {current_timestamp}:")
|
626
738
|
|
627
|
-
# Add proximity count to human text
|
628
|
-
|
629
|
-
proximity_count = self._count_proximity_events(detections)
|
739
|
+
# Add proximity count to human text (meters-aware)
|
740
|
+
proximity_count = self._count_proximity_events(detections, config, stream_info)
|
630
741
|
if proximity_count > 0:
|
631
742
|
human_text_lines.append(f"\t- Current Frame Proximity: {proximity_count}")
|
632
743
|
else:
|
@@ -635,7 +746,10 @@ class ProximityUseCase(BaseProcessor):
|
|
635
746
|
human_text_lines.append("")
|
636
747
|
if proximity_count > 0:
|
637
748
|
human_text_lines.append(f"TOTAL SINCE @ {start_timestamp}:")
|
638
|
-
|
749
|
+
# If tracking IDs are available, _total_proximity_count already includes this frame's new pairs
|
750
|
+
# Otherwise, fall back to showing the current frame's count
|
751
|
+
total_unique = self._total_proximity_count if self._total_proximity_count > 0 else proximity_count
|
752
|
+
human_text_lines.append(f"\t- Total Proximity Count: {total_unique}")
|
639
753
|
|
640
754
|
if alerts:
|
641
755
|
for alert in alerts:
|
@@ -712,7 +826,6 @@ class ProximityUseCase(BaseProcessor):
|
|
712
826
|
for zone_name, threshold in config.alert_config.occupancy_thresholds.items():
|
713
827
|
if zone_name in zone_analysis:
|
714
828
|
# Calculate zone_count robustly (supports int, list, dict values)
|
715
|
-
print('ZONEEE',zone_name, zone_analysis[zone_name])
|
716
829
|
zone_count = self._robust_zone_total(zone_analysis[zone_name])
|
717
830
|
if zone_count >= threshold:
|
718
831
|
alerts.append({
|
@@ -771,7 +884,7 @@ class ProximityUseCase(BaseProcessor):
|
|
771
884
|
|
772
885
|
return business_analytics
|
773
886
|
|
774
|
-
def _generate_summary(self,
|
887
|
+
def _generate_summary(self, _summary: dict, incidents: List, tracking_stats: List, business_analytics: List, _alerts: List) -> List[str]:
|
775
888
|
"""
|
776
889
|
Generate a human_text string for the tracking_stat, incident, business analytics and alerts.
|
777
890
|
"""
|
@@ -880,8 +993,8 @@ class ProximityUseCase(BaseProcessor):
|
|
880
993
|
prediction["frame_id"] = frame_id
|
881
994
|
predictions.append(prediction)
|
882
995
|
|
883
|
-
except Exception as e:
|
884
|
-
self.logger.warning(
|
996
|
+
except Exception as e: # noqa: BLE001
|
997
|
+
self.logger.warning("Failed to extract predictions: %s", str(e))
|
885
998
|
|
886
999
|
return predictions
|
887
1000
|
|
@@ -939,7 +1052,9 @@ class ProximityUseCase(BaseProcessor):
|
|
939
1052
|
current_frame_tracks.add(canonical_id)
|
940
1053
|
|
941
1054
|
# Update total track IDs with new canonical IDs from current frame
|
942
|
-
old_total_count
|
1055
|
+
# old_total_count can be used for debugging or analytics if needed
|
1056
|
+
# Keeping it for potential future use but suppressing linter warning
|
1057
|
+
old_total_count = len(self._total_track_ids) # noqa: F841
|
943
1058
|
self._total_track_ids.update(current_frame_tracks)
|
944
1059
|
self._current_frame_track_ids = current_frame_tracks
|
945
1060
|
|
@@ -952,10 +1067,12 @@ class ProximityUseCase(BaseProcessor):
|
|
952
1067
|
new_tracks = current_frame_tracks - (self._total_track_ids - current_frame_tracks)
|
953
1068
|
if new_tracks:
|
954
1069
|
self.logger.debug(
|
955
|
-
|
1070
|
+
"Tracking state updated: %s new canonical track IDs added, total unique tracks: %s",
|
1071
|
+
len(new_tracks), self._total_count)
|
956
1072
|
else:
|
957
1073
|
self.logger.debug(
|
958
|
-
|
1074
|
+
"Tracking state updated: %s current frame canonical tracks, total unique tracks: %s",
|
1075
|
+
len(current_frame_tracks), self._total_count)
|
959
1076
|
|
960
1077
|
def get_total_count(self) -> int:
|
961
1078
|
"""Get the total count of unique people tracked across all calls."""
|
@@ -972,7 +1089,7 @@ class ProximityUseCase(BaseProcessor):
|
|
972
1089
|
def set_global_frame_offset(self, offset: int) -> None:
|
973
1090
|
"""Set the global frame offset for video chunk processing."""
|
974
1091
|
self._global_frame_offset = offset
|
975
|
-
self.logger.info(
|
1092
|
+
self.logger.info("Global frame offset set to: %s", offset)
|
976
1093
|
|
977
1094
|
def get_global_frame_offset(self) -> int:
|
978
1095
|
"""Get the current global frame offset."""
|
@@ -982,7 +1099,7 @@ class ProximityUseCase(BaseProcessor):
|
|
982
1099
|
"""Update global frame offset after processing a chunk."""
|
983
1100
|
old_offset = self._global_frame_offset
|
984
1101
|
self._global_frame_offset += frames_in_chunk
|
985
|
-
self.logger.info(
|
1102
|
+
self.logger.info("Global frame offset updated: %s -> %s (added %s frames)", old_offset, self._global_frame_offset, frames_in_chunk)
|
986
1103
|
|
987
1104
|
def get_global_frame_id(self, local_frame_id: str) -> str:
|
988
1105
|
"""Convert local frame ID to global frame ID."""
|
@@ -1093,14 +1210,14 @@ class ProximityUseCase(BaseProcessor):
|
|
1093
1210
|
# Update timestamp
|
1094
1211
|
self._last_update_time = time.time()
|
1095
1212
|
|
1096
|
-
self.logger.info(
|
1213
|
+
self.logger.info("Cleared %s current frame tracks and %s zone current tracks. Cumulative total preserved: %s", old_current_count, cleared_zone_tracks, self._total_count)
|
1097
1214
|
return old_current_count
|
1098
1215
|
|
1099
1216
|
def reset_frame_counter(self) -> None:
|
1100
1217
|
"""Reset only the frame counter."""
|
1101
1218
|
old_count = self._total_frame_counter
|
1102
1219
|
self._total_frame_counter = 0
|
1103
|
-
self.logger.info(
|
1220
|
+
self.logger.info("Frame counter reset from %s to 0", old_count)
|
1104
1221
|
|
1105
1222
|
def clear_expired_tracks(self, max_age_seconds: float = 300.0) -> int:
|
1106
1223
|
"""
|
@@ -1125,7 +1242,7 @@ class ProximityUseCase(BaseProcessor):
|
|
1125
1242
|
if current_time - self._last_update_time > max_age_seconds:
|
1126
1243
|
# Use the safe method that preserves cumulative totals
|
1127
1244
|
cleared_count = self.clear_current_frame_tracking()
|
1128
|
-
self.logger.info(
|
1245
|
+
self.logger.info("Manual cleanup: cleared %s expired current frame tracks (age > %ss)", cleared_count, max_age_seconds)
|
1129
1246
|
return cleared_count
|
1130
1247
|
return 0
|
1131
1248
|
|
@@ -1267,12 +1384,12 @@ class ProximityUseCase(BaseProcessor):
|
|
1267
1384
|
return datetime.now(timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC")
|
1268
1385
|
|
1269
1386
|
if stream_info.get("input_settings", {}).get("start_frame", "na") != "na":
|
1270
|
-
|
1271
|
-
|
1272
|
-
|
1273
|
-
|
1274
|
-
|
1275
|
-
|
1387
|
+
if frame_id:
|
1388
|
+
start_time = int(frame_id)/stream_info.get("input_settings", {}).get("original_fps", 30)
|
1389
|
+
else:
|
1390
|
+
start_time = stream_info.get("input_settings", {}).get("start_frame", 30)/stream_info.get("input_settings", {}).get("original_fps", 30)
|
1391
|
+
stream_time_str = self._format_timestamp_for_video(start_time)
|
1392
|
+
return stream_time_str
|
1276
1393
|
else:
|
1277
1394
|
# For streams, use stream_time from stream_info
|
1278
1395
|
stream_time_str = stream_info.get("input_settings", {}).get("stream_info", {}).get("stream_time", "")
|
@@ -1284,7 +1401,7 @@ class ProximityUseCase(BaseProcessor):
|
|
1284
1401
|
dt = datetime.strptime(timestamp_str, "%Y-%m-%d-%H:%M:%S.%f")
|
1285
1402
|
timestamp = dt.replace(tzinfo=timezone.utc).timestamp()
|
1286
1403
|
return self._format_timestamp_for_stream(timestamp)
|
1287
|
-
except:
|
1404
|
+
except Exception: # noqa: BLE001
|
1288
1405
|
# Fallback to current time if parsing fails
|
1289
1406
|
return self._format_timestamp_for_stream(time.time())
|
1290
1407
|
else:
|
@@ -1316,7 +1433,7 @@ class ProximityUseCase(BaseProcessor):
|
|
1316
1433
|
timestamp_str = stream_time_str.replace(" UTC", "")
|
1317
1434
|
dt = datetime.strptime(timestamp_str, "%Y-%m-%d-%H:%M:%S.%f")
|
1318
1435
|
self._tracking_start_time = dt.replace(tzinfo=timezone.utc).timestamp()
|
1319
|
-
except:
|
1436
|
+
except Exception: # noqa: BLE001
|
1320
1437
|
# Fallback to current time if parsing fails
|
1321
1438
|
self._tracking_start_time = time.time()
|
1322
1439
|
else:
|
@@ -1450,7 +1567,7 @@ class ProximityUseCase(BaseProcessor):
|
|
1450
1567
|
info["last_update"] = now
|
1451
1568
|
info["raw_ids"].add(raw_id)
|
1452
1569
|
self.logger.debug(
|
1453
|
-
|
1570
|
+
"Merged raw track %s into canonical track %s (IoU=%.2f)", raw_id, canonical_id, iou)
|
1454
1571
|
return canonical_id
|
1455
1572
|
|
1456
1573
|
# No match found – create a new canonical track
|
@@ -1461,7 +1578,7 @@ class ProximityUseCase(BaseProcessor):
|
|
1461
1578
|
"last_update": now,
|
1462
1579
|
"raw_ids": {raw_id},
|
1463
1580
|
}
|
1464
|
-
self.logger.debug(
|
1581
|
+
self.logger.debug("Registered new canonical track %s", canonical_id)
|
1465
1582
|
return canonical_id
|
1466
1583
|
|
1467
1584
|
def _format_timestamp(self, timestamp: float) -> str:
|
@@ -1530,6 +1647,33 @@ class ProximityUseCase(BaseProcessor):
|
|
1530
1647
|
"default": True,
|
1531
1648
|
"description": "Enable unique proximity detection using tracking"
|
1532
1649
|
},
|
1650
|
+
"proximity_threshold_meters": {
|
1651
|
+
"type": "number",
|
1652
|
+
"minimum": 0.1,
|
1653
|
+
"default": 1.0,
|
1654
|
+
"description": "Distance threshold in meters to consider two people in proximity"
|
1655
|
+
},
|
1656
|
+
"meters_per_pixel": {
|
1657
|
+
"type": "number",
|
1658
|
+
"minimum": 0,
|
1659
|
+
"description": "Direct meters-per-pixel calibration override. If set, used for distance conversion."
|
1660
|
+
},
|
1661
|
+
"scene_width_meters": {
|
1662
|
+
"type": "number",
|
1663
|
+
"minimum": 0,
|
1664
|
+
"description": "Real-world width of the scene captured by the frame (meters). Used to derive meters-per-pixel with frame width."
|
1665
|
+
},
|
1666
|
+
"scene_height_meters": {
|
1667
|
+
"type": "number",
|
1668
|
+
"minimum": 0,
|
1669
|
+
"description": "Real-world height of the scene captured by the frame (meters). Used to derive meters-per-pixel with frame height."
|
1670
|
+
},
|
1671
|
+
"proximity_threshold_pixels": {
|
1672
|
+
"type": "number",
|
1673
|
+
"minimum": 1,
|
1674
|
+
"default": 400,
|
1675
|
+
"description": "Fallback pixel threshold if no calibration is available"
|
1676
|
+
},
|
1533
1677
|
"time_window_minutes": {
|
1534
1678
|
"type": "integer",
|
1535
1679
|
"minimum": 1,
|
@@ -1603,5 +1747,5 @@ class ProximityUseCase(BaseProcessor):
|
|
1603
1747
|
self.smoothing_tracker = BBoxSmoothingTracker(smoothing_config)
|
1604
1748
|
|
1605
1749
|
smoothed_data = bbox_smoothing(data, self.smoothing_tracker.config, self.smoothing_tracker)
|
1606
|
-
self.logger.debug(
|
1750
|
+
self.logger.debug("Applied bbox smoothing to tracking results")
|
1607
1751
|
return smoothed_data
|
@@ -140,7 +140,7 @@ matrice/deploy/utils/post_processing/advanced_tracker/strack.py,sha256=rVH2xOysZ
|
|
140
140
|
matrice/deploy/utils/post_processing/advanced_tracker/tracker.py,sha256=D-PKZ2Pxutmlu--icyxuxjvnWBrzrmZcEChYS0nx00M,14328
|
141
141
|
matrice/deploy/utils/post_processing/core/__init__.py,sha256=MPMj_iRv--PfKBpYN12IjReAzSU7aRMVD6VW-LC95-M,1379
|
142
142
|
matrice/deploy/utils/post_processing/core/base.py,sha256=V_DmaMLtrIunrN8Aq9iLeMIQPlkbCE-9d7n0Yz-nKQg,28228
|
143
|
-
matrice/deploy/utils/post_processing/core/config.py,sha256=
|
143
|
+
matrice/deploy/utils/post_processing/core/config.py,sha256=ogmNUbfOoC4SE-SmUEkqIKBeR0p0j3M0hF2_KvzpNp0,102560
|
144
144
|
matrice/deploy/utils/post_processing/core/config_utils.py,sha256=Y_Czm9RmtHuxzBZzGUBA57JRyx5r6tzrM5l89Dbdf_w,28871
|
145
145
|
matrice/deploy/utils/post_processing/test_cases/__init__.py,sha256=zUU2kKrIcCl8WeyjjQViwp7PWTZlKPuF8M2pZkxoNNQ,42
|
146
146
|
matrice/deploy/utils/post_processing/test_cases/run_tests.py,sha256=RBFGvxFR-gozxnQFzkWLrs90vLlp8Bsn-Z7MLQrNw4o,4731
|
@@ -204,7 +204,7 @@ matrice/deploy/utils/post_processing/usecases/plaque_segmentation_img.py,sha256=
|
|
204
204
|
matrice/deploy/utils/post_processing/usecases/pothole_segmentation.py,sha256=jXTb8ZqInp5xJ-O3Zp3zQBiryFVD0-WBbhW6Kux_NDo,44905
|
205
205
|
matrice/deploy/utils/post_processing/usecases/ppe_compliance.py,sha256=G9P9j9E9nfNJInHJxmK1Lb4daFBlG5hq0aqotTLvFFE,30146
|
206
206
|
matrice/deploy/utils/post_processing/usecases/price_tag_detection.py,sha256=09Tp6MGAHh95s-NSAp-4WC9iCc20sajWApuUBAvgXiQ,39880
|
207
|
-
matrice/deploy/utils/post_processing/usecases/proximity_detection.py,sha256=
|
207
|
+
matrice/deploy/utils/post_processing/usecases/proximity_detection.py,sha256=QyknlaCyvzRg0j9Vndi1prYNW1elz0ARNBimHRyBak4,85207
|
208
208
|
matrice/deploy/utils/post_processing/usecases/road_lane_detection.py,sha256=V_KxwBtAHSNkyoH8sXw-U-P3J8ToXtX3ncc69gn6Tds,31591
|
209
209
|
matrice/deploy/utils/post_processing/usecases/road_traffic_density.py,sha256=YiHQ0kKhXglagHPvygywxMqZAw8s0WharrBQqLQj2q4,40311
|
210
210
|
matrice/deploy/utils/post_processing/usecases/road_view_segmentation.py,sha256=BcBbOOg5622KuvzKrzs9cJW1wkRoIIcOab0N7BONQKQ,44986
|
@@ -243,8 +243,8 @@ matrice/deployment/camera_manager.py,sha256=e1Lc81RJP5wUWRdTgHO6tMWF9BkBdHOSVyx3
|
|
243
243
|
matrice/deployment/deployment.py,sha256=HFt151eWq6iqIAMsQvurpV2WNxW6Cx_gIUVfnVy5SWE,48093
|
244
244
|
matrice/deployment/inference_pipeline.py,sha256=6b4Mm3-qt-Zy0BeiJfFQdImOn3FzdNCY-7ET7Rp8PMk,37911
|
245
245
|
matrice/deployment/streaming_gateway_manager.py,sha256=ifYGl3g25wyU39HwhPQyI2OgF3M6oIqKMWt8RXtMxY8,21401
|
246
|
-
matrice-1.0.
|
247
|
-
matrice-1.0.
|
248
|
-
matrice-1.0.
|
249
|
-
matrice-1.0.
|
250
|
-
matrice-1.0.
|
246
|
+
matrice-1.0.99269.dist-info/licenses/LICENSE.txt,sha256=2bm9uFabQZ3Ykb_SaSU_uUbAj2-htc6WJQmS_65qD00,1073
|
247
|
+
matrice-1.0.99269.dist-info/METADATA,sha256=y9DrDgXE7DfQ8kc1rALCkIrPanFK1zTR3Eoc8DxVVyo,14624
|
248
|
+
matrice-1.0.99269.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
249
|
+
matrice-1.0.99269.dist-info/top_level.txt,sha256=P97js8ur6o5ClRqMH3Cjoab_NqbJ6sOQ3rJmVzKBvMc,8
|
250
|
+
matrice-1.0.99269.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|