matrice 1.0.99268__py3-none-any.whl → 1.0.99270__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 +170 -67
- {matrice-1.0.99268.dist-info → matrice-1.0.99270.dist-info}/METADATA +1 -1
- {matrice-1.0.99268.dist-info → matrice-1.0.99270.dist-info}/RECORD +7 -7
- {matrice-1.0.99268.dist-info → matrice-1.0.99270.dist-info}/WHEEL +0 -0
- {matrice-1.0.99268.dist-info → matrice-1.0.99270.dist-info}/licenses/LICENSE.txt +0 -0
- {matrice-1.0.99268.dist-info → matrice-1.0.99270.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
|
@@ -66,6 +66,7 @@ class ProximityUseCase(BaseProcessor):
|
|
66
66
|
# Proximity counting
|
67
67
|
self._total_proximity_count = 0 # Total proximity events across all calls
|
68
68
|
self._observed_proximity_pairs: Set[frozenset] = set() # Unique canonical ID pairs seen across frames
|
69
|
+
self._last_frame_proximity_pairs: Set[tuple] = set() # Pairs detected in the most recent frame (track-id based)
|
69
70
|
|
70
71
|
|
71
72
|
# --------------------------------------------------------------------- #
|
@@ -287,8 +288,8 @@ class ProximityUseCase(BaseProcessor):
|
|
287
288
|
# Update tracking state BEFORE proximity calculation so we have canonical IDs
|
288
289
|
self._update_tracking_state(counting_summary)
|
289
290
|
|
290
|
-
# Calculate unique proximity events for this frame
|
291
|
-
proximity_count = self._count_proximity_events(counting_summary["detections"])
|
291
|
+
# Calculate unique proximity events for this frame (meters-aware)
|
292
|
+
proximity_count = self._count_proximity_events(counting_summary["detections"], config, stream_info)
|
292
293
|
counting_summary["proximity_events"] = proximity_count
|
293
294
|
counting_summary["total_proximity_count"] = self._total_proximity_count
|
294
295
|
|
@@ -585,91 +586,166 @@ class ProximityUseCase(BaseProcessor):
|
|
585
586
|
)
|
586
587
|
return [tracking_stat]
|
587
588
|
|
588
|
-
def _count_proximity_events(self, detections: List[Dict[str, Any]]) -> int:
|
589
|
+
def _count_proximity_events(self, detections: List[Dict[str, Any]], config: ProximityConfig, stream_info: Optional[Dict[str, Any]] = None) -> int:
|
589
590
|
"""Count UNIQUE proximity events between detections in a frame.
|
590
591
|
|
591
592
|
Rules:
|
592
|
-
-
|
593
|
-
-
|
594
|
-
- Count each pair once (i < j) using Euclidean distance between
|
593
|
+
- Use IoU-NMS to deduplicate overlapping boxes (highest confidence kept).
|
594
|
+
- Use track IDs when available to build stable (id1,id2) pairs.
|
595
|
+
- Count each pair once (i < j) using Euclidean distance between box centers.
|
596
|
+
- Distance is evaluated in meters when calibration is available; otherwise, fallback to pixel threshold.
|
595
597
|
- Maintain a running set of unique canonical-ID pairs across frames to compute total unique proximity events.
|
596
598
|
"""
|
597
599
|
if not detections:
|
598
600
|
return 0
|
599
601
|
|
600
|
-
|
601
|
-
|
602
|
+
# Determine threshold strategy
|
603
|
+
meters_per_pixel = self._get_meters_per_pixel(config, stream_info)
|
604
|
+
threshold_meters = getattr(config, "proximity_threshold_meters", 1.0)
|
605
|
+
threshold_pixels_fallback = getattr(config, "proximity_threshold_pixels", 400.0)
|
602
606
|
|
603
|
-
|
604
|
-
unique_detections: List[Dict[str, Any]] = []
|
605
|
-
seen_track_ids: Set[Any] = set()
|
607
|
+
overlap_iou_threshold = getattr(self, "_proximity_iou_duplicate_threshold", 0.5)
|
606
608
|
|
609
|
+
# Helper: convert bbox to xyxy list
|
610
|
+
def _to_xyxy(bbox: Any) -> List[float]:
|
611
|
+
if isinstance(bbox, list):
|
612
|
+
if len(bbox) >= 4:
|
613
|
+
return [float(bbox[0]), float(bbox[1]), float(bbox[2]), float(bbox[3])]
|
614
|
+
return []
|
615
|
+
if isinstance(bbox, dict):
|
616
|
+
if all(k in bbox for k in ("xmin", "ymin", "xmax", "ymax")):
|
617
|
+
return [float(bbox["xmin"]), float(bbox["ymin"]), float(bbox["xmax"]), float(bbox["ymax"])]
|
618
|
+
if all(k in bbox for k in ("x1", "y1", "x2", "y2")):
|
619
|
+
return [float(bbox["x1"]), float(bbox["y1"]), float(bbox["x2"]), float(bbox["y2"])]
|
620
|
+
# Fallback: take first four values
|
621
|
+
vals = list(bbox.values())
|
622
|
+
if len(vals) >= 4:
|
623
|
+
return [float(vals[0]), float(vals[1]), float(vals[2]), float(vals[3])]
|
624
|
+
return []
|
625
|
+
return []
|
626
|
+
|
627
|
+
# Prepare tracked detections (track_id, bbox_xyxy, conf)
|
628
|
+
tracked_detections: List[Dict[str, Any]] = []
|
607
629
|
for det in detections:
|
608
|
-
|
609
|
-
bbox = det.get("bounding_box", det.get("bbox", {}))
|
630
|
+
bbox = _to_xyxy(det.get("bounding_box", det.get("bbox", {})))
|
610
631
|
if not bbox:
|
611
632
|
continue
|
633
|
+
tracked_detections.append({
|
634
|
+
"track_id": det.get("track_id"),
|
635
|
+
"bbox": bbox,
|
636
|
+
"confidence": float(det.get("confidence", 1.0))
|
637
|
+
})
|
612
638
|
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
|
617
|
-
|
618
|
-
|
619
|
-
|
639
|
+
# IoU-NMS to remove overlapping boxes, keep highest confidence
|
640
|
+
kept: List[Dict[str, Any]] = self._nms_by_iou(tracked_detections, overlap_iou_threshold)
|
641
|
+
|
642
|
+
# Compute centroids
|
643
|
+
centroids: Dict[Any, tuple] = {}
|
644
|
+
for td in kept:
|
645
|
+
x1, y1, x2, y2 = map(float, td["bbox"])
|
646
|
+
cx, cy = (x1 + x2) / 2.0, (y1 + y2) / 2.0
|
647
|
+
centroids[td.get("track_id")] = (cx, cy)
|
648
|
+
|
649
|
+
# Build current frame proximity pairs using track IDs only when both exist
|
650
|
+
current_pairs: Set[tuple] = set()
|
651
|
+
ids_list = [tid for tid in centroids.keys() if tid is not None]
|
652
|
+
for i in range(len(ids_list)):
|
653
|
+
for j in range(i + 1, len(ids_list)):
|
654
|
+
id1, id2 = ids_list[i], ids_list[j]
|
655
|
+
cx1, cy1 = centroids[id1]
|
656
|
+
cx2, cy2 = centroids[id2]
|
657
|
+
pixel_distance = math.hypot(cx1 - cx2, cy1 - cy2)
|
658
|
+
|
659
|
+
is_close = False
|
660
|
+
if meters_per_pixel:
|
661
|
+
meters_distance = pixel_distance * float(meters_per_pixel)
|
662
|
+
is_close = meters_distance < float(threshold_meters)
|
663
|
+
else:
|
664
|
+
is_close = pixel_distance < float(threshold_pixels_fallback)
|
620
665
|
|
621
|
-
|
622
|
-
|
623
|
-
|
624
|
-
kept_bbox = kept.get("bounding_box", kept.get("bbox", {}))
|
625
|
-
if self._compute_iou(bbox, kept_bbox) >= iou_duplicate_threshold:
|
626
|
-
is_duplicate = True
|
627
|
-
break
|
628
|
-
if not is_duplicate:
|
629
|
-
unique_detections.append(det)
|
666
|
+
if is_close:
|
667
|
+
pair = (id1, id2) if id1 <= id2 else (id2, id1)
|
668
|
+
current_pairs.add(pair)
|
630
669
|
|
631
|
-
#
|
632
|
-
|
670
|
+
# Update global unique proximity pairs
|
671
|
+
new_unique_pairs = {frozenset(p) for p in current_pairs} - self._observed_proximity_pairs
|
672
|
+
if new_unique_pairs:
|
673
|
+
self._total_proximity_count += len(new_unique_pairs)
|
674
|
+
self._observed_proximity_pairs.update(new_unique_pairs)
|
633
675
|
|
634
|
-
|
635
|
-
|
636
|
-
|
637
|
-
|
638
|
-
|
639
|
-
continue
|
676
|
+
# Store last frame pairs
|
677
|
+
self._last_frame_proximity_pairs = current_pairs
|
678
|
+
|
679
|
+
# Return count of unique pairs in current frame
|
680
|
+
return len(current_pairs)
|
640
681
|
|
641
|
-
|
642
|
-
|
643
|
-
|
644
|
-
|
645
|
-
|
646
|
-
|
647
|
-
|
648
|
-
|
649
|
-
|
650
|
-
|
651
|
-
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
|
656
|
-
|
657
|
-
|
658
|
-
|
659
|
-
|
660
|
-
|
661
|
-
|
662
|
-
|
663
|
-
|
664
|
-
|
665
|
-
|
682
|
+
def _nms_by_iou(self, detections: List[Dict[str, Any]], iou_threshold: float) -> List[Dict[str, Any]]:
|
683
|
+
"""Perform simple IoU-based NMS on a list of detections.
|
684
|
+
|
685
|
+
Each detection is a dict with keys: 'bbox' as [x1,y1,x2,y2], 'confidence' (float), and optional 'track_id'.
|
686
|
+
Keeps highest-confidence detections when overlap exceeds threshold.
|
687
|
+
"""
|
688
|
+
if not detections:
|
689
|
+
return []
|
690
|
+
# Sort by confidence descending
|
691
|
+
dets = sorted(detections, key=lambda d: float(d.get("confidence", 1.0)), reverse=True)
|
692
|
+
kept: List[Dict[str, Any]] = []
|
693
|
+
for det in dets:
|
694
|
+
should_keep = True
|
695
|
+
for kept_det in kept:
|
696
|
+
if self._compute_iou(det["bbox"], kept_det["bbox"]) >= iou_threshold:
|
697
|
+
should_keep = False
|
698
|
+
break
|
699
|
+
if should_keep:
|
700
|
+
kept.append(det)
|
701
|
+
return kept
|
702
|
+
|
703
|
+
def _get_meters_per_pixel(self, config: ProximityConfig, stream_info: Optional[Dict[str, Any]] = None) -> Optional[float]:
|
704
|
+
"""Compute meters-per-pixel scale using config and optional stream_info.
|
705
|
+
|
706
|
+
Priority:
|
707
|
+
1) config.meters_per_pixel (direct override)
|
708
|
+
2) config.scene_width_meters + frame width in pixels
|
709
|
+
3) config.scene_height_meters + frame height in pixels
|
710
|
+
Returns None if insufficient information.
|
711
|
+
"""
|
712
|
+
# Direct override
|
713
|
+
if hasattr(config, "meters_per_pixel") and getattr(config, "meters_per_pixel"):
|
714
|
+
try:
|
715
|
+
return float(getattr(config, "meters_per_pixel"))
|
716
|
+
except Exception: # noqa: BLE001
|
717
|
+
pass
|
718
|
+
|
719
|
+
width_px = None
|
720
|
+
height_px = None
|
721
|
+
if stream_info and isinstance(stream_info, dict):
|
722
|
+
input_settings = stream_info.get("input_settings", {}) or {}
|
723
|
+
resolution = input_settings.get("resolution", {}) or {}
|
724
|
+
width_px = resolution.get("width") or input_settings.get("frame_width")
|
725
|
+
height_px = resolution.get("height") or input_settings.get("frame_height")
|
726
|
+
|
727
|
+
# Derive from scene real-world width
|
728
|
+
if hasattr(config, "scene_width_meters") and getattr(config, "scene_width_meters") and width_px:
|
729
|
+
try:
|
730
|
+
return float(getattr(config, "scene_width_meters")) / float(width_px)
|
731
|
+
except Exception: # noqa: BLE001
|
732
|
+
pass
|
733
|
+
|
734
|
+
# Derive from scene real-world height
|
735
|
+
if hasattr(config, "scene_height_meters") and getattr(config, "scene_height_meters") and height_px:
|
736
|
+
try:
|
737
|
+
return float(getattr(config, "scene_height_meters")) / float(height_px)
|
738
|
+
except Exception: # noqa: BLE001
|
739
|
+
pass
|
740
|
+
|
741
|
+
return None
|
666
742
|
|
667
743
|
def _generate_human_text_for_tracking(
|
668
744
|
self,
|
669
|
-
|
745
|
+
total_people: int,
|
670
746
|
detections,
|
671
|
-
|
672
|
-
|
747
|
+
total_unique_count: int,
|
748
|
+
config: ProximityConfig,
|
673
749
|
frame_id: str,
|
674
750
|
alerts: Any = None,
|
675
751
|
stream_info: Optional[Dict[str, Any]] = None) -> str:
|
@@ -681,8 +757,8 @@ class ProximityUseCase(BaseProcessor):
|
|
681
757
|
|
682
758
|
human_text_lines.append(f"CURRENT FRAME @ {current_timestamp}:")
|
683
759
|
|
684
|
-
# Add proximity count to human text
|
685
|
-
proximity_count = self._count_proximity_events(detections)
|
760
|
+
# Add proximity count to human text (meters-aware)
|
761
|
+
proximity_count = self._count_proximity_events(detections, config, stream_info)
|
686
762
|
if proximity_count > 0:
|
687
763
|
human_text_lines.append(f"\t- Current Frame Proximity: {proximity_count}")
|
688
764
|
else:
|
@@ -1592,6 +1668,33 @@ class ProximityUseCase(BaseProcessor):
|
|
1592
1668
|
"default": True,
|
1593
1669
|
"description": "Enable unique proximity detection using tracking"
|
1594
1670
|
},
|
1671
|
+
"proximity_threshold_meters": {
|
1672
|
+
"type": "number",
|
1673
|
+
"minimum": 0.1,
|
1674
|
+
"default": 1.0,
|
1675
|
+
"description": "Distance threshold in meters to consider two people in proximity"
|
1676
|
+
},
|
1677
|
+
"meters_per_pixel": {
|
1678
|
+
"type": "number",
|
1679
|
+
"minimum": 0,
|
1680
|
+
"description": "Direct meters-per-pixel calibration override. If set, used for distance conversion."
|
1681
|
+
},
|
1682
|
+
"scene_width_meters": {
|
1683
|
+
"type": "number",
|
1684
|
+
"minimum": 0,
|
1685
|
+
"description": "Real-world width of the scene captured by the frame (meters). Used to derive meters-per-pixel with frame width."
|
1686
|
+
},
|
1687
|
+
"scene_height_meters": {
|
1688
|
+
"type": "number",
|
1689
|
+
"minimum": 0,
|
1690
|
+
"description": "Real-world height of the scene captured by the frame (meters). Used to derive meters-per-pixel with frame height."
|
1691
|
+
},
|
1692
|
+
"proximity_threshold_pixels": {
|
1693
|
+
"type": "number",
|
1694
|
+
"minimum": 1,
|
1695
|
+
"default": 400,
|
1696
|
+
"description": "Fallback pixel threshold if no calibration is available"
|
1697
|
+
},
|
1595
1698
|
"time_window_minutes": {
|
1596
1699
|
"type": "integer",
|
1597
1700
|
"minimum": 1,
|
@@ -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=EBpu9tR_OOPfES0lM-YzBJN4FMwxc31TRKMS4OSLg4k,86507
|
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.99270.dist-info/licenses/LICENSE.txt,sha256=2bm9uFabQZ3Ykb_SaSU_uUbAj2-htc6WJQmS_65qD00,1073
|
247
|
+
matrice-1.0.99270.dist-info/METADATA,sha256=oBNUFZl4QKsG87Nbl2m9QwrbUSzHoKLMSEKZ3l9VMQI,14624
|
248
|
+
matrice-1.0.99270.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
249
|
+
matrice-1.0.99270.dist-info/top_level.txt,sha256=P97js8ur6o5ClRqMH3Cjoab_NqbJ6sOQ3rJmVzKBvMc,8
|
250
|
+
matrice-1.0.99270.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|