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.
@@ -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
- - 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.
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
- proximity_threshold = 400.0 # pixels, screen space
601
- iou_duplicate_threshold = getattr(self, "_proximity_iou_duplicate_threshold", 0.5)
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
- # Step 1: Deduplicate detections
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
- track_id = det.get("track_id")
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
- # Prefer canonical track IDs if present (set earlier in _update_tracking_state)
614
- if track_id is not None:
615
- if track_id in seen_track_ids:
616
- continue
617
- seen_track_ids.add(track_id)
618
- unique_detections.append(det)
619
- continue
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
- # Fallback: IoU-based dedup for pure detection outputs without track IDs
622
- is_duplicate = False
623
- for kept in unique_detections:
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
- # Step 2: Compute proximity pairs uniquely (i < j)
632
- frame_unique_count = 0
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
- for i in range(len(unique_detections)):
635
- det_i = unique_detections[i]
636
- bbox_i = det_i.get("bounding_box", det_i.get("bbox", {}))
637
- p_i = get_bbox_bottom25_center(bbox_i) or get_bbox_center(bbox_i)
638
- if not p_i:
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
- for j in range(i + 1, len(unique_detections)):
642
- det_j = unique_detections[j]
643
- bbox_j = det_j.get("bounding_box", det_j.get("bbox", {}))
644
- p_j = get_bbox_bottom25_center(bbox_j) or get_bbox_center(bbox_j)
645
- if not p_j:
646
- continue
647
-
648
- dx = float(p_i[0]) - float(p_j[0])
649
- dy = float(p_i[1]) - float(p_j[1])
650
- distance = math.hypot(dx, dy)
651
- if distance >= proximity_threshold:
652
- continue
653
-
654
- frame_unique_count += 1
655
-
656
- # Update global unique proximity pairs only when both have canonical IDs
657
- id_i = det_i.get("track_id")
658
- id_j = det_j.get("track_id")
659
- if id_i is not None and id_j is not None:
660
- pair_key = frozenset({id_i, id_j})
661
- if pair_key not in self._observed_proximity_pairs:
662
- self._observed_proximity_pairs.add(pair_key)
663
- self._total_proximity_count += 1
664
-
665
- return frame_unique_count
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
- _total_people: int,
745
+ total_people: int,
670
746
  detections,
671
- _total_unique_count: int,
672
- _config: ProximityConfig,
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,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: matrice
3
- Version: 1.0.99268
3
+ Version: 1.0.99270
4
4
  Summary: SDK for connecting to matrice.ai services
5
5
  Home-page: https://github.com/matrice-ai/python-sdk
6
6
  Author: Matrice.ai
@@ -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=rU6EWou2HOA_4Ks0LUTSczQbT04jhN1FofuAQ7dtrZ4,102355
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=HjUZMKZHAgpD_YiyTXFAoaO4awF_QkY9oflCIVFlsiM,81157
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.99268.dist-info/licenses/LICENSE.txt,sha256=2bm9uFabQZ3Ykb_SaSU_uUbAj2-htc6WJQmS_65qD00,1073
247
- matrice-1.0.99268.dist-info/METADATA,sha256=aU45pkw_HtMNp9C-mI1pSRKXVAI9djwHr1UCaRvpbCA,14624
248
- matrice-1.0.99268.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
249
- matrice-1.0.99268.dist-info/top_level.txt,sha256=P97js8ur6o5ClRqMH3Cjoab_NqbJ6sOQ3rJmVzKBvMc,8
250
- matrice-1.0.99268.dist-info/RECORD,,
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,,