matrice-analytics 0.1.60__py3-none-any.whl → 0.1.89__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_analytics/post_processing/config.py +2 -2
- matrice_analytics/post_processing/core/base.py +1 -1
- matrice_analytics/post_processing/face_reg/embedding_manager.py +8 -8
- matrice_analytics/post_processing/face_reg/face_recognition.py +886 -201
- matrice_analytics/post_processing/face_reg/face_recognition_client.py +68 -2
- matrice_analytics/post_processing/usecases/advanced_customer_service.py +908 -498
- matrice_analytics/post_processing/usecases/color_detection.py +18 -18
- matrice_analytics/post_processing/usecases/customer_service.py +356 -9
- matrice_analytics/post_processing/usecases/fire_detection.py +149 -11
- matrice_analytics/post_processing/usecases/license_plate_monitoring.py +548 -40
- matrice_analytics/post_processing/usecases/people_counting.py +11 -11
- matrice_analytics/post_processing/usecases/vehicle_monitoring.py +34 -34
- matrice_analytics/post_processing/usecases/weapon_detection.py +98 -22
- matrice_analytics/post_processing/utils/alert_instance_utils.py +950 -0
- matrice_analytics/post_processing/utils/business_metrics_manager_utils.py +1245 -0
- matrice_analytics/post_processing/utils/incident_manager_utils.py +1657 -0
- {matrice_analytics-0.1.60.dist-info → matrice_analytics-0.1.89.dist-info}/METADATA +1 -1
- {matrice_analytics-0.1.60.dist-info → matrice_analytics-0.1.89.dist-info}/RECORD +21 -18
- {matrice_analytics-0.1.60.dist-info → matrice_analytics-0.1.89.dist-info}/WHEEL +0 -0
- {matrice_analytics-0.1.60.dist-info → matrice_analytics-0.1.89.dist-info}/licenses/LICENSE.txt +0 -0
- {matrice_analytics-0.1.60.dist-info → matrice_analytics-0.1.89.dist-info}/top_level.txt +0 -0
|
@@ -333,20 +333,20 @@ class PeopleCountingUseCase(BaseProcessor):
|
|
|
333
333
|
})
|
|
334
334
|
|
|
335
335
|
human_text_lines = []
|
|
336
|
-
human_text_lines.append(f"CURRENT FRAME @ {current_timestamp}")
|
|
336
|
+
human_text_lines.append(f"CURRENT FRAME @ {current_timestamp}:")
|
|
337
337
|
for cat, count in per_category_count.items():
|
|
338
338
|
human_text_lines.append(f"\t- People Detected: {count}")
|
|
339
339
|
human_text_lines.append("")
|
|
340
|
-
human_text_lines.append(f"TOTAL SINCE {start_timestamp}")
|
|
341
|
-
for cat, count in total_counts_dict.items():
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
if alerts:
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
else:
|
|
349
|
-
|
|
340
|
+
# human_text_lines.append(f"TOTAL SINCE {start_timestamp}")
|
|
341
|
+
# for cat, count in total_counts_dict.items():
|
|
342
|
+
# if count > 0:
|
|
343
|
+
# human_text_lines.append("")
|
|
344
|
+
# human_text_lines.append(f"\t- Total unique people count: {count}")
|
|
345
|
+
# if alerts:
|
|
346
|
+
# for alert in alerts:
|
|
347
|
+
# human_text_lines.append(f"Alerts: {alert.get('settings', {})} sent @ {current_timestamp}")
|
|
348
|
+
# else:
|
|
349
|
+
# human_text_lines.append("Alerts: None")
|
|
350
350
|
human_text = "\n".join(human_text_lines)
|
|
351
351
|
|
|
352
352
|
reset_settings = [{"interval_type": "daily", "reset_time": {"value": 9, "time_unit": "hour"}}]
|
|
@@ -633,40 +633,40 @@ class VehicleMonitoringUseCase(BaseProcessor):
|
|
|
633
633
|
human_text_lines.append(f"\t\t- {cat}: {count}")
|
|
634
634
|
|
|
635
635
|
human_text_lines.append("")
|
|
636
|
-
human_text_lines.append(f"TOTAL SINCE @ {start_timestamp}:")
|
|
637
|
-
|
|
638
|
-
# Display total counts - zone-wise or category-wise
|
|
639
|
-
if zone_analysis:
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
else:
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
# Display alerts
|
|
663
|
-
if alerts:
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
else:
|
|
668
|
-
|
|
669
|
-
|
|
636
|
+
# human_text_lines.append(f"TOTAL SINCE @ {start_timestamp}:")
|
|
637
|
+
|
|
638
|
+
# # Display total counts - zone-wise or category-wise
|
|
639
|
+
# if zone_analysis:
|
|
640
|
+
# human_text_lines.append("\t- Total Vehicles by Zone:")
|
|
641
|
+
# for zone_name, zone_data in zone_analysis.items():
|
|
642
|
+
# total_count = 0
|
|
643
|
+
# if isinstance(zone_data, dict):
|
|
644
|
+
# # Prefer the numeric cumulative total if available
|
|
645
|
+
# if "total_count" in zone_data and isinstance(zone_data.get("total_count"), (int, float)):
|
|
646
|
+
# total_count = zone_data.get("total_count", 0)
|
|
647
|
+
# # Fallback: compute from list of total_track_ids if present
|
|
648
|
+
# elif "total_track_ids" in zone_data and isinstance(zone_data.get("total_track_ids"), list):
|
|
649
|
+
# total_count = len(zone_data.get("total_track_ids", []))
|
|
650
|
+
# else:
|
|
651
|
+
# # Last resort: try to sum numeric values present
|
|
652
|
+
# counts_dict = zone_data if isinstance(zone_data, dict) else {}
|
|
653
|
+
# total_count = sum(v for v in counts_dict.values() if isinstance(v, (int, float)))
|
|
654
|
+
# human_text_lines.append(f"\t\t- {zone_name}: {int(total_count)}")
|
|
655
|
+
# else:
|
|
656
|
+
# if total_counts_dict:
|
|
657
|
+
# human_text_lines.append("\t- Total Unique Vehicles:")
|
|
658
|
+
# for cat, count in total_counts_dict.items():
|
|
659
|
+
# if count > 0:
|
|
660
|
+
# human_text_lines.append(f"\t\t- {cat}: {count}")
|
|
661
|
+
|
|
662
|
+
# # Display alerts
|
|
663
|
+
# if alerts:
|
|
664
|
+
# human_text_lines.append("")
|
|
665
|
+
# for alert in alerts:
|
|
666
|
+
# human_text_lines.append(f"Alerts: {alert.get('settings', {})} sent @ {current_timestamp}")
|
|
667
|
+
# else:
|
|
668
|
+
# human_text_lines.append("")
|
|
669
|
+
# human_text_lines.append("Alerts: None")
|
|
670
670
|
|
|
671
671
|
human_text = "\n".join(human_text_lines)
|
|
672
672
|
|
|
@@ -585,23 +585,6 @@ class WeaponDetectionUseCase(BaseProcessor):
|
|
|
585
585
|
"""Return total unique track_id count for each category."""
|
|
586
586
|
return {cat: len(ids) for cat, ids in getattr(self, '_per_category_total_track_ids', {}).items()}
|
|
587
587
|
|
|
588
|
-
def _format_timestamp(self, timestamp: Any) -> str:
|
|
589
|
-
"""Format a timestamp so that exactly two digits follow the decimal point (milliseconds)."""
|
|
590
|
-
if isinstance(timestamp, (int, float)):
|
|
591
|
-
timestamp = datetime.fromtimestamp(timestamp, timezone.utc).strftime('%Y-%m-%d-%H:%M:%S.%f UTC')
|
|
592
|
-
if not isinstance(timestamp, str):
|
|
593
|
-
return str(timestamp)
|
|
594
|
-
if '.' not in timestamp:
|
|
595
|
-
return timestamp
|
|
596
|
-
main_part, fractional_and_suffix = timestamp.split('.', 1)
|
|
597
|
-
if ' ' in fractional_and_suffix:
|
|
598
|
-
fractional_part, suffix = fractional_and_suffix.split(' ', 1)
|
|
599
|
-
suffix = ' ' + suffix
|
|
600
|
-
else:
|
|
601
|
-
fractional_part, suffix = fractional_and_suffix, ''
|
|
602
|
-
fractional_part = (fractional_part + '00')[:2]
|
|
603
|
-
return f"{main_part}.{fractional_part}{suffix}"
|
|
604
|
-
|
|
605
588
|
def _format_timestamp_for_stream(self, timestamp: float) -> str:
|
|
606
589
|
"""Format timestamp for streams (YYYY:MM:DD HH:MM:SS format)."""
|
|
607
590
|
dt = datetime.fromtimestamp(timestamp, tz=timezone.utc)
|
|
@@ -614,8 +597,56 @@ class WeaponDetectionUseCase(BaseProcessor):
|
|
|
614
597
|
seconds = round(float(timestamp % 60), 2)
|
|
615
598
|
return f"{hours:02d}:{minutes:02d}:{seconds:.1f}"
|
|
616
599
|
|
|
600
|
+
def _format_timestamp(self, timestamp: Any) -> str:
|
|
601
|
+
"""Format a timestamp to match the current timestamp format: YYYY:MM:DD HH:MM:SS.
|
|
602
|
+
|
|
603
|
+
The input can be either:
|
|
604
|
+
1. A numeric Unix timestamp (``float`` / ``int``) – it will be converted to datetime.
|
|
605
|
+
2. A string in the format ``YYYY-MM-DD-HH:MM:SS.ffffff UTC``.
|
|
606
|
+
|
|
607
|
+
The returned value will be in the format: YYYY:MM:DD HH:MM:SS (no milliseconds, no UTC suffix).
|
|
608
|
+
|
|
609
|
+
Example
|
|
610
|
+
-------
|
|
611
|
+
>>> self._format_timestamp("2025-10-27-19:31:20.187574 UTC")
|
|
612
|
+
'2025:10:27 19:31:20'
|
|
613
|
+
"""
|
|
614
|
+
|
|
615
|
+
# Convert numeric timestamps to datetime first
|
|
616
|
+
if isinstance(timestamp, (int, float)):
|
|
617
|
+
dt = datetime.fromtimestamp(timestamp, timezone.utc)
|
|
618
|
+
return dt.strftime('%Y:%m:%d %H:%M:%S')
|
|
619
|
+
|
|
620
|
+
# Ensure we are working with a string from here on
|
|
621
|
+
if not isinstance(timestamp, str):
|
|
622
|
+
return str(timestamp)
|
|
623
|
+
|
|
624
|
+
# Remove ' UTC' suffix if present
|
|
625
|
+
timestamp_clean = timestamp.replace(' UTC', '').strip()
|
|
626
|
+
|
|
627
|
+
# Remove milliseconds if present (everything after the last dot)
|
|
628
|
+
if '.' in timestamp_clean:
|
|
629
|
+
timestamp_clean = timestamp_clean.split('.')[0]
|
|
630
|
+
|
|
631
|
+
# Parse the timestamp string and convert to desired format
|
|
632
|
+
try:
|
|
633
|
+
# Handle format: YYYY-MM-DD-HH:MM:SS
|
|
634
|
+
if timestamp_clean.count('-') >= 2:
|
|
635
|
+
# Replace first two dashes with colons for date part, third with space
|
|
636
|
+
parts = timestamp_clean.split('-')
|
|
637
|
+
if len(parts) >= 4:
|
|
638
|
+
# parts = ['2025', '10', '27', '19:31:20']
|
|
639
|
+
formatted = f"{parts[0]}:{parts[1]}:{parts[2]} {'-'.join(parts[3:])}"
|
|
640
|
+
return formatted
|
|
641
|
+
except Exception:
|
|
642
|
+
pass
|
|
643
|
+
|
|
644
|
+
# If parsing fails, return the cleaned string as-is
|
|
645
|
+
return timestamp_clean
|
|
646
|
+
|
|
617
647
|
def _get_current_timestamp_str(self, stream_info: Optional[Dict[str, Any]], precision=False, frame_id: Optional[str]=None) -> str:
|
|
618
648
|
"""Get formatted current timestamp based on stream type."""
|
|
649
|
+
|
|
619
650
|
if not stream_info:
|
|
620
651
|
return "00:00:00.00"
|
|
621
652
|
if precision:
|
|
@@ -625,15 +656,20 @@ class WeaponDetectionUseCase(BaseProcessor):
|
|
|
625
656
|
else:
|
|
626
657
|
start_time = stream_info.get("input_settings", {}).get("start_frame", 30)/stream_info.get("input_settings", {}).get("original_fps", 30)
|
|
627
658
|
stream_time_str = self._format_timestamp_for_video(start_time)
|
|
659
|
+
|
|
628
660
|
return self._format_timestamp(stream_info.get("input_settings", {}).get("stream_time", "NA"))
|
|
629
661
|
else:
|
|
630
662
|
return datetime.now(timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC")
|
|
663
|
+
|
|
631
664
|
if stream_info.get("input_settings", {}).get("start_frame", "na") != "na":
|
|
632
665
|
if frame_id:
|
|
633
666
|
start_time = int(frame_id)/stream_info.get("input_settings", {}).get("original_fps", 30)
|
|
634
667
|
else:
|
|
635
668
|
start_time = stream_info.get("input_settings", {}).get("start_frame", 30)/stream_info.get("input_settings", {}).get("original_fps", 30)
|
|
669
|
+
|
|
636
670
|
stream_time_str = self._format_timestamp_for_video(start_time)
|
|
671
|
+
|
|
672
|
+
|
|
637
673
|
return self._format_timestamp(stream_info.get("input_settings", {}).get("stream_time", "NA"))
|
|
638
674
|
else:
|
|
639
675
|
stream_time_str = stream_info.get("input_settings", {}).get("stream_info", {}).get("stream_time", "")
|
|
@@ -652,24 +688,62 @@ class WeaponDetectionUseCase(BaseProcessor):
|
|
|
652
688
|
"""Get formatted start timestamp for 'TOTAL SINCE' based on stream type."""
|
|
653
689
|
if not stream_info:
|
|
654
690
|
return "00:00:00"
|
|
691
|
+
|
|
655
692
|
if precision:
|
|
656
693
|
if self.start_timer is None:
|
|
657
|
-
|
|
694
|
+
candidate = stream_info.get("input_settings", {}).get("stream_time")
|
|
695
|
+
if not candidate or candidate == "NA":
|
|
696
|
+
candidate = datetime.now(timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC")
|
|
697
|
+
self.start_timer = candidate
|
|
658
698
|
return self._format_timestamp(self.start_timer)
|
|
659
699
|
elif stream_info.get("input_settings", {}).get("start_frame", "na") == 1:
|
|
660
|
-
|
|
700
|
+
candidate = stream_info.get("input_settings", {}).get("stream_time")
|
|
701
|
+
if not candidate or candidate == "NA":
|
|
702
|
+
candidate = datetime.now(timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC")
|
|
703
|
+
self.start_timer = candidate
|
|
661
704
|
return self._format_timestamp(self.start_timer)
|
|
662
705
|
else:
|
|
663
706
|
return self._format_timestamp(self.start_timer)
|
|
707
|
+
|
|
664
708
|
if self.start_timer is None:
|
|
665
|
-
|
|
709
|
+
# Prefer direct input_settings.stream_time if available and not NA
|
|
710
|
+
candidate = stream_info.get("input_settings", {}).get("stream_time")
|
|
711
|
+
if not candidate or candidate == "NA":
|
|
712
|
+
# Fallback to nested stream_info.stream_time used by current timestamp path
|
|
713
|
+
stream_time_str = stream_info.get("input_settings", {}).get("stream_info", {}).get("stream_time", "")
|
|
714
|
+
if stream_time_str:
|
|
715
|
+
try:
|
|
716
|
+
timestamp_str = stream_time_str.replace(" UTC", "")
|
|
717
|
+
dt = datetime.strptime(timestamp_str, "%Y-%m-%d-%H:%M:%S.%f")
|
|
718
|
+
self._tracking_start_time = dt.replace(tzinfo=timezone.utc).timestamp()
|
|
719
|
+
candidate = datetime.fromtimestamp(self._tracking_start_time, timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC")
|
|
720
|
+
except:
|
|
721
|
+
candidate = datetime.now(timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC")
|
|
722
|
+
else:
|
|
723
|
+
candidate = datetime.now(timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC")
|
|
724
|
+
self.start_timer = candidate
|
|
666
725
|
return self._format_timestamp(self.start_timer)
|
|
667
726
|
elif stream_info.get("input_settings", {}).get("start_frame", "na") == 1:
|
|
668
|
-
|
|
727
|
+
candidate = stream_info.get("input_settings", {}).get("stream_time")
|
|
728
|
+
if not candidate or candidate == "NA":
|
|
729
|
+
stream_time_str = stream_info.get("input_settings", {}).get("stream_info", {}).get("stream_time", "")
|
|
730
|
+
if stream_time_str:
|
|
731
|
+
try:
|
|
732
|
+
timestamp_str = stream_time_str.replace(" UTC", "")
|
|
733
|
+
dt = datetime.strptime(timestamp_str, "%Y-%m-%d-%H:%M:%S.%f")
|
|
734
|
+
ts = dt.replace(tzinfo=timezone.utc).timestamp()
|
|
735
|
+
candidate = datetime.fromtimestamp(ts, timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC")
|
|
736
|
+
except:
|
|
737
|
+
candidate = datetime.now(timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC")
|
|
738
|
+
else:
|
|
739
|
+
candidate = datetime.now(timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC")
|
|
740
|
+
self.start_timer = candidate
|
|
669
741
|
return self._format_timestamp(self.start_timer)
|
|
742
|
+
|
|
670
743
|
else:
|
|
671
|
-
if self.start_timer is not None:
|
|
744
|
+
if self.start_timer is not None and self.start_timer != "NA":
|
|
672
745
|
return self._format_timestamp(self.start_timer)
|
|
746
|
+
|
|
673
747
|
if self._tracking_start_time is None:
|
|
674
748
|
stream_time_str = stream_info.get("input_settings", {}).get("stream_info", {}).get("stream_time", "")
|
|
675
749
|
if stream_time_str:
|
|
@@ -681,10 +755,12 @@ class WeaponDetectionUseCase(BaseProcessor):
|
|
|
681
755
|
self._tracking_start_time = time.time()
|
|
682
756
|
else:
|
|
683
757
|
self._tracking_start_time = time.time()
|
|
758
|
+
|
|
684
759
|
dt = datetime.fromtimestamp(self._tracking_start_time, tz=timezone.utc)
|
|
685
760
|
dt = dt.replace(minute=0, second=0, microsecond=0)
|
|
686
761
|
return dt.strftime('%Y:%m:%d %H:%M:%S')
|
|
687
762
|
|
|
763
|
+
|
|
688
764
|
def _compute_iou(self, box1: Any, box2: Any) -> float:
|
|
689
765
|
"""Compute IoU between two bounding boxes."""
|
|
690
766
|
def _bbox_to_list(bbox):
|