matrice 1.0.99269__py3-none-any.whl → 1.0.99271__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.
@@ -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
  # --------------------------------------------------------------------- #
@@ -535,44 +536,44 @@ class ProximityUseCase(BaseProcessor):
535
536
  }
536
537
  })
537
538
  if zone_analysis:
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
556
- else:
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}:")
539
+ human_text_lines = []
540
+ current_timestamp = self._get_current_timestamp_str(stream_info, frame_id=frame_id)
541
+ start_timestamp = self._get_start_timestamp_str(stream_info)
542
+ human_text_lines.append(f"CURRENT FRAME @ {current_timestamp}:")
543
+
544
+ def robust_zone_total(zone_count):
545
+ if isinstance(zone_count, dict):
546
+ total = 0
547
+ for v in zone_count.values():
548
+ if isinstance(v, int):
549
+ total += v
550
+ elif isinstance(v, list) and total == 0:
551
+ total += len(v)
552
+ return total
553
+ elif isinstance(zone_count, list):
554
+ return len(zone_count)
555
+ elif isinstance(zone_count, int):
556
+ return zone_count
557
+ else:
558
+ return 0
562
559
 
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}")
560
+ human_text_lines.append(f"\t- People Detected: {total_people}")
561
+ human_text_lines.append("")
562
+ human_text_lines.append(f"TOTAL SINCE @ {start_timestamp}:")
563
+
564
+ for zone_name, zone_count in zone_analysis.items():
565
+ zone_total = robust_zone_total(zone_count)
566
+ human_text_lines.append(f"\t- Zone name: {zone_name}")
566
567
  human_text_lines.append(f"\t\t- Total count in zone: {zone_total}")
567
568
 
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)
569
+ if total_unique_count > 0:
570
+ human_text_lines.append(f"\t- Total unique people in the scene: {total_unique_count}")
571
+ if alerts:
572
+ for alert in alerts:
573
+ human_text_lines.append(f"Alerts: {alert.get('settings', {})} sent @ {current_timestamp}")
574
+ else:
575
+ human_text_lines.append("Alerts: None")
576
+ human_text = "\n".join(human_text_lines)
576
577
  else:
577
578
  human_text = self._generate_human_text_for_tracking(total_people, detections, total_unique_count, config, frame_id, alerts, stream_info)
578
579
 
@@ -589,10 +590,10 @@ class ProximityUseCase(BaseProcessor):
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.
595
- - Distance is evaluated in meters when calibration is available. If unavailable, pixel threshold is used as a fallback.
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.
596
597
  - Maintain a running set of unique canonical-ID pairs across frames to compute total unique proximity events.
597
598
  """
598
599
  if not detections:
@@ -603,81 +604,113 @@ class ProximityUseCase(BaseProcessor):
603
604
  threshold_meters = getattr(config, "proximity_threshold_meters", 1.0)
604
605
  threshold_pixels_fallback = getattr(config, "proximity_threshold_pixels", 400.0)
605
606
 
606
- iou_duplicate_threshold = getattr(self, "_proximity_iou_duplicate_threshold", 0.5)
607
+ overlap_iou_threshold = getattr(self, "_proximity_iou_duplicate_threshold", 0.5)
607
608
 
608
- # Step 1: Deduplicate detections
609
- unique_detections: List[Dict[str, Any]] = []
610
- seen_track_ids: Set[Any] = set()
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 []
611
626
 
627
+ # Prepare tracked detections (track_id, bbox_xyxy, conf)
628
+ tracked_detections: List[Dict[str, Any]] = []
612
629
  for det in detections:
613
- track_id = det.get("track_id")
614
- bbox = det.get("bounding_box", det.get("bbox", {}))
630
+ bbox = _to_xyxy(det.get("bounding_box", det.get("bbox", {})))
615
631
  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:
621
- continue
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
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
+ })
652
638
 
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)
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 and keep alignment arrays for IDs
643
+ centroids: List[tuple] = []
644
+ track_ids: List[Any] = []
645
+ for td in kept:
646
+ x1, y1, x2, y2 = map(float, td["bbox"])
647
+ # Use box center (matching your reference snippet); switch to bottom-center if needed
648
+ cx, cy = (x1 + x2) / 2.0, (y1 + y2) / 2.0
649
+ centroids.append((cx, cy))
650
+ track_ids.append(td.get("track_id"))
651
+
652
+ n = len(centroids)
653
+ current_pairs_by_ids: Set[tuple] = set()
654
+ current_pairs_all: Set[tuple] = set()
655
+
656
+ # Build current frame proximity pairs for all detections (even without IDs)
657
+ for i in range(n):
658
+ cx1, cy1 = centroids[i]
659
+ for j in range(i + 1, n):
660
+ cx2, cy2 = centroids[j]
661
+ pixel_distance = math.hypot(cx1 - cx2, cy1 - cy2)
656
662
 
657
- is_close = False
658
663
  if meters_per_pixel:
659
664
  meters_distance = pixel_distance * float(meters_per_pixel)
660
- if meters_distance < float(threshold_meters):
661
- is_close = True
665
+ is_close = meters_distance < float(threshold_meters)
662
666
  else:
663
- if pixel_distance < float(threshold_pixels_fallback):
664
- is_close = True
667
+ is_close = pixel_distance < float(threshold_pixels_fallback)
665
668
 
666
669
  if not is_close:
667
670
  continue
668
671
 
669
- frame_unique_count += 1
672
+ # For per-frame count, include every close pair
673
+ current_pairs_all.add((i, j))
670
674
 
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")
675
+ # For global unique, require both IDs
676
+ id_i = track_ids[i]
677
+ id_j = track_ids[j]
674
678
  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
+ pair_ids = (id_i, id_j) if id_i <= id_j else (id_j, id_i)
680
+ current_pairs_by_ids.add(pair_ids)
681
+
682
+ # Update global unique proximity pairs using ID pairs only
683
+ new_unique_pairs = {frozenset(p) for p in current_pairs_by_ids} - self._observed_proximity_pairs
684
+ if new_unique_pairs:
685
+ self._total_proximity_count += len(new_unique_pairs)
686
+ self._observed_proximity_pairs.update(new_unique_pairs)
687
+
688
+ # Store last frame pairs (ID pairs if available, else index pairs as fallback)
689
+ self._last_frame_proximity_pairs = current_pairs_by_ids if current_pairs_by_ids else current_pairs_all
679
690
 
680
- return frame_unique_count
691
+ # Return count of pairs detected in the current frame
692
+ return len(current_pairs_by_ids) if current_pairs_by_ids else len(current_pairs_all)
693
+
694
+ def _nms_by_iou(self, detections: List[Dict[str, Any]], iou_threshold: float) -> List[Dict[str, Any]]:
695
+ """Perform simple IoU-based NMS on a list of detections.
696
+
697
+ Each detection is a dict with keys: 'bbox' as [x1,y1,x2,y2], 'confidence' (float), and optional 'track_id'.
698
+ Keeps highest-confidence detections when overlap exceeds threshold.
699
+ """
700
+ if not detections:
701
+ return []
702
+ # Sort by confidence descending
703
+ dets = sorted(detections, key=lambda d: float(d.get("confidence", 1.0)), reverse=True)
704
+ kept: List[Dict[str, Any]] = []
705
+ for det in dets:
706
+ should_keep = True
707
+ for kept_det in kept:
708
+ if self._compute_iou(det["bbox"], kept_det["bbox"]) >= iou_threshold:
709
+ should_keep = False
710
+ break
711
+ if should_keep:
712
+ kept.append(det)
713
+ return kept
681
714
 
682
715
  def _get_meters_per_pixel(self, config: ProximityConfig, stream_info: Optional[Dict[str, Any]] = None) -> Optional[float]:
683
716
  """Compute meters-per-pixel scale using config and optional stream_info.
@@ -720,12 +753,12 @@ class ProximityUseCase(BaseProcessor):
720
753
  return None
721
754
 
722
755
  def _generate_human_text_for_tracking(
723
- self,
724
- total_people: int,
725
- detections,
726
- total_unique_count: int,
727
- config: ProximityConfig,
728
- frame_id: str,
756
+ self,
757
+ total_people: int,
758
+ detections,
759
+ total_unique_count: int,
760
+ config: ProximityConfig,
761
+ frame_id: str,
729
762
  alerts: Any = None,
730
763
  stream_info: Optional[Dict[str, Any]] = None) -> str:
731
764
  """Generate human-readable text for tracking stats in old format."""
@@ -744,12 +777,8 @@ class ProximityUseCase(BaseProcessor):
744
777
  human_text_lines.append("\t- No Proximity Events Detected")
745
778
 
746
779
  human_text_lines.append("")
747
- if proximity_count > 0:
748
- human_text_lines.append(f"TOTAL SINCE @ {start_timestamp}:")
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}")
780
+ human_text_lines.append(f"TOTAL SINCE @ {start_timestamp}:")
781
+ human_text_lines.append(f"\t- Total Proximity Count: {self._total_proximity_count}")
753
782
 
754
783
  if alerts:
755
784
  for alert in alerts:
@@ -1384,12 +1413,12 @@ class ProximityUseCase(BaseProcessor):
1384
1413
  return datetime.now(timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC")
1385
1414
 
1386
1415
  if stream_info.get("input_settings", {}).get("start_frame", "na") != "na":
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
1416
+ if frame_id:
1417
+ start_time = int(frame_id)/stream_info.get("input_settings", {}).get("original_fps", 30)
1418
+ else:
1419
+ start_time = stream_info.get("input_settings", {}).get("start_frame", 30)/stream_info.get("input_settings", {}).get("original_fps", 30)
1420
+ stream_time_str = self._format_timestamp_for_video(start_time)
1421
+ return stream_time_str
1393
1422
  else:
1394
1423
  # For streams, use stream_time from stream_info
1395
1424
  stream_time_str = stream_info.get("input_settings", {}).get("stream_info", {}).get("stream_time", "")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: matrice
3
- Version: 1.0.99269
3
+ Version: 1.0.99271
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
@@ -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=QyknlaCyvzRg0j9Vndi1prYNW1elz0ARNBimHRyBak4,85207
207
+ matrice/deploy/utils/post_processing/usecases/proximity_detection.py,sha256=tbERNzbuczj1GSLwApxt6_T3sYodFh8roThrPhj-OUI,87022
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.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,,
246
+ matrice-1.0.99271.dist-info/licenses/LICENSE.txt,sha256=2bm9uFabQZ3Ykb_SaSU_uUbAj2-htc6WJQmS_65qD00,1073
247
+ matrice-1.0.99271.dist-info/METADATA,sha256=KGoqAjTGhMEnZY1dF18S3l9f_ZK1Vn7EICyPlTbDkcg,14624
248
+ matrice-1.0.99271.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
249
+ matrice-1.0.99271.dist-info/top_level.txt,sha256=P97js8ur6o5ClRqMH3Cjoab_NqbJ6sOQ3rJmVzKBvMc,8
250
+ matrice-1.0.99271.dist-info/RECORD,,