matrice-analytics 0.1.43__py3-none-any.whl → 0.1.45__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.

Potentially problematic release.


This version of matrice-analytics might be problematic. Click here for more details.

@@ -1566,63 +1566,64 @@ class LicensePlateMonitorUseCase(BaseProcessor):
1566
1566
  }
1567
1567
  return canonical_id
1568
1568
 
1569
+ def _format_timestamp_for_stream(self, timestamp: float) -> str:
1570
+ """Format timestamp for streams (YYYY:MM:DD HH:MM:SS format)."""
1571
+ dt = datetime.fromtimestamp(timestamp, tz=timezone.utc)
1572
+ return dt.strftime('%Y:%m:%d %H:%M:%S')
1573
+
1574
+ def _format_timestamp_for_video(self, timestamp: float) -> str:
1575
+ """Format timestamp for video chunks (HH:MM:SS.ms format)."""
1576
+ hours = int(timestamp // 3600)
1577
+ minutes = int((timestamp % 3600) // 60)
1578
+ seconds = round(float(timestamp % 60), 2)
1579
+ return f"{hours:02d}:{minutes:02d}:{seconds:.1f}"
1580
+
1569
1581
  def _format_timestamp(self, timestamp: Any) -> str:
1570
- """Format a timestamp so that exactly two digits follow the decimal point (milliseconds).
1582
+ """Format a timestamp to match the current timestamp format: YYYY:MM:DD HH:MM:SS.
1571
1583
 
1572
1584
  The input can be either:
1573
- 1. A numeric Unix timestamp (``float`` / ``int``) it will first be converted to a
1574
- string in the format ``YYYY-MM-DD-HH:MM:SS.ffffff UTC``.
1575
- 2. A string already following the same layout.
1585
+ 1. A numeric Unix timestamp (``float`` / ``int``)it will be converted to datetime.
1586
+ 2. A string in the format ``YYYY-MM-DD-HH:MM:SS.ffffff UTC``.
1576
1587
 
1577
- The returned value preserves the overall format of the input but truncates or pads
1578
- the fractional seconds portion to **exactly two digits**.
1588
+ The returned value will be in the format: YYYY:MM:DD HH:MM:SS (no milliseconds, no UTC suffix).
1579
1589
 
1580
1590
  Example
1581
1591
  -------
1582
- >>> self._format_timestamp("2025-08-19-04:22:47.187574 UTC")
1583
- '2025-08-19-04:22:47.18 UTC'
1592
+ >>> self._format_timestamp("2025-10-27-19:31:20.187574 UTC")
1593
+ '2025:10:27 19:31:20'
1584
1594
  """
1585
1595
 
1586
- # Convert numeric timestamps to the expected string representation first
1596
+ # Convert numeric timestamps to datetime first
1587
1597
  if isinstance(timestamp, (int, float)):
1588
- timestamp = datetime.fromtimestamp(timestamp, timezone.utc).strftime(
1589
- '%Y-%m-%d-%H:%M:%S.%f UTC'
1590
- )
1598
+ dt = datetime.fromtimestamp(timestamp, timezone.utc)
1599
+ return dt.strftime('%Y:%m:%d %H:%M:%S')
1591
1600
 
1592
1601
  # Ensure we are working with a string from here on
1593
1602
  if not isinstance(timestamp, str):
1594
1603
  return str(timestamp)
1595
1604
 
1596
- # If there is no fractional component, simply return the original string
1597
- if '.' not in timestamp:
1598
- return timestamp
1599
-
1600
- # Split out the main portion (up to the decimal point)
1601
- main_part, fractional_and_suffix = timestamp.split('.', 1)
1602
-
1603
- # Separate fractional digits from the suffix (typically ' UTC')
1604
- if ' ' in fractional_and_suffix:
1605
- fractional_part, suffix = fractional_and_suffix.split(' ', 1)
1606
- suffix = ' ' + suffix # Re-attach the space removed by split
1607
- else:
1608
- fractional_part, suffix = fractional_and_suffix, ''
1609
-
1610
- # Guarantee exactly two digits for the fractional part
1611
- fractional_part = (fractional_part + '00')[:2]
1605
+ # Remove ' UTC' suffix if present
1606
+ timestamp_clean = timestamp.replace(' UTC', '').strip()
1612
1607
 
1613
- return f"{main_part}.{fractional_part}{suffix}"
1608
+ # Remove milliseconds if present (everything after the last dot)
1609
+ if '.' in timestamp_clean:
1610
+ timestamp_clean = timestamp_clean.split('.')[0]
1614
1611
 
1615
- def _format_timestamp_for_stream(self, timestamp: float) -> str:
1616
- """Format timestamp for streams (YYYY:MM:DD HH:MM:SS format)."""
1617
- dt = datetime.fromtimestamp(timestamp, tz=timezone.utc)
1618
- return dt.strftime('%Y:%m:%d %H:%M:%S')
1619
-
1620
- def _format_timestamp_for_video(self, timestamp: float) -> str:
1621
- """Format timestamp for video chunks (HH:MM:SS.ms format)."""
1622
- hours = int(timestamp // 3600)
1623
- minutes = int((timestamp % 3600) // 60)
1624
- seconds = round(float(timestamp % 60), 2)
1625
- return f"{hours:02d}:{minutes:02d}:{seconds:.1f}"
1612
+ # Parse the timestamp string and convert to desired format
1613
+ try:
1614
+ # Handle format: YYYY-MM-DD-HH:MM:SS
1615
+ if timestamp_clean.count('-') >= 2:
1616
+ # Replace first two dashes with colons for date part, third with space
1617
+ parts = timestamp_clean.split('-')
1618
+ if len(parts) >= 4:
1619
+ # parts = ['2025', '10', '27', '19:31:20']
1620
+ formatted = f"{parts[0]}:{parts[1]}:{parts[2]} {'-'.join(parts[3:])}"
1621
+ return formatted
1622
+ except Exception:
1623
+ pass
1624
+
1625
+ # If parsing fails, return the cleaned string as-is
1626
+ return timestamp_clean
1626
1627
 
1627
1628
  def _get_current_timestamp_str(self, stream_info: Optional[Dict[str, Any]], precision=False, frame_id: Optional[str]=None) -> str:
1628
1629
  """Get formatted current timestamp based on stream type."""
@@ -1671,23 +1672,57 @@ class LicensePlateMonitorUseCase(BaseProcessor):
1671
1672
 
1672
1673
  if precision:
1673
1674
  if self.start_timer is None:
1674
- self.start_timer = stream_info.get("input_settings", {}).get("stream_time", "NA")
1675
+ candidate = stream_info.get("input_settings", {}).get("stream_time")
1676
+ if not candidate or candidate == "NA":
1677
+ candidate = datetime.now(timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC")
1678
+ self.start_timer = candidate
1675
1679
  return self._format_timestamp(self.start_timer)
1676
1680
  elif stream_info.get("input_settings", {}).get("start_frame", "na") == 1:
1677
- self.start_timer = stream_info.get("input_settings", {}).get("stream_time", "NA")
1681
+ candidate = stream_info.get("input_settings", {}).get("stream_time")
1682
+ if not candidate or candidate == "NA":
1683
+ candidate = datetime.now(timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC")
1684
+ self.start_timer = candidate
1678
1685
  return self._format_timestamp(self.start_timer)
1679
1686
  else:
1680
1687
  return self._format_timestamp(self.start_timer)
1681
1688
 
1682
1689
  if self.start_timer is None:
1683
- self.start_timer = stream_info.get("input_settings", {}).get("stream_time", "NA")
1690
+ # Prefer direct input_settings.stream_time if available and not NA
1691
+ candidate = stream_info.get("input_settings", {}).get("stream_time")
1692
+ if not candidate or candidate == "NA":
1693
+ # Fallback to nested stream_info.stream_time used by current timestamp path
1694
+ stream_time_str = stream_info.get("input_settings", {}).get("stream_info", {}).get("stream_time", "")
1695
+ if stream_time_str:
1696
+ try:
1697
+ timestamp_str = stream_time_str.replace(" UTC", "")
1698
+ dt = datetime.strptime(timestamp_str, "%Y-%m-%d-%H:%M:%S.%f")
1699
+ self._tracking_start_time = dt.replace(tzinfo=timezone.utc).timestamp()
1700
+ candidate = datetime.fromtimestamp(self._tracking_start_time, timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC")
1701
+ except:
1702
+ candidate = datetime.now(timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC")
1703
+ else:
1704
+ candidate = datetime.now(timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC")
1705
+ self.start_timer = candidate
1684
1706
  return self._format_timestamp(self.start_timer)
1685
1707
  elif stream_info.get("input_settings", {}).get("start_frame", "na") == 1:
1686
- self.start_timer = stream_info.get("input_settings", {}).get("stream_time", "NA")
1708
+ candidate = stream_info.get("input_settings", {}).get("stream_time")
1709
+ if not candidate or candidate == "NA":
1710
+ stream_time_str = stream_info.get("input_settings", {}).get("stream_info", {}).get("stream_time", "")
1711
+ if stream_time_str:
1712
+ try:
1713
+ timestamp_str = stream_time_str.replace(" UTC", "")
1714
+ dt = datetime.strptime(timestamp_str, "%Y-%m-%d-%H:%M:%S.%f")
1715
+ ts = dt.replace(tzinfo=timezone.utc).timestamp()
1716
+ candidate = datetime.fromtimestamp(ts, timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC")
1717
+ except:
1718
+ candidate = datetime.now(timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC")
1719
+ else:
1720
+ candidate = datetime.now(timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC")
1721
+ self.start_timer = candidate
1687
1722
  return self._format_timestamp(self.start_timer)
1688
1723
 
1689
1724
  else:
1690
- if self.start_timer is not None:
1725
+ if self.start_timer is not None and self.start_timer != "NA":
1691
1726
  return self._format_timestamp(self.start_timer)
1692
1727
 
1693
1728
  if self._tracking_start_time is None:
@@ -1705,7 +1740,7 @@ class LicensePlateMonitorUseCase(BaseProcessor):
1705
1740
  dt = datetime.fromtimestamp(self._tracking_start_time, tz=timezone.utc)
1706
1741
  dt = dt.replace(minute=0, second=0, microsecond=0)
1707
1742
  return dt.strftime('%Y:%m:%d %H:%M:%S')
1708
-
1743
+
1709
1744
  def _get_tracking_start_time(self) -> str:
1710
1745
  """Get the tracking start time, formatted as a string."""
1711
1746
  if self._tracking_start_time is None:
@@ -437,50 +437,51 @@ class PeopleCountingUseCase(BaseProcessor):
437
437
  return f"{hours:02d}:{minutes:02d}:{seconds:.1f}"
438
438
 
439
439
  def _format_timestamp(self, timestamp: Any) -> str:
440
- """Format a timestamp so that exactly two digits follow the decimal point (milliseconds).
440
+ """Format a timestamp to match the current timestamp format: YYYY:MM:DD HH:MM:SS.
441
441
 
442
442
  The input can be either:
443
- 1. A numeric Unix timestamp (``float`` / ``int``) – it will first be converted to a
444
- string in the format ``YYYY-MM-DD-HH:MM:SS.ffffff UTC``.
445
- 2. A string already following the same layout.
443
+ 1. A numeric Unix timestamp (``float`` / ``int``) – it will be converted to datetime.
444
+ 2. A string in the format ``YYYY-MM-DD-HH:MM:SS.ffffff UTC``.
446
445
 
447
- The returned value preserves the overall format of the input but truncates or pads
448
- the fractional seconds portion to **exactly two digits**.
446
+ The returned value will be in the format: YYYY:MM:DD HH:MM:SS (no milliseconds, no UTC suffix).
449
447
 
450
448
  Example
451
449
  -------
452
- >>> self._format_timestamp("2025-08-19-04:22:47.187574 UTC")
453
- '2025-08-19-04:22:47.18 UTC'
450
+ >>> self._format_timestamp("2025-10-27-19:31:20.187574 UTC")
451
+ '2025:10:27 19:31:20'
454
452
  """
455
453
 
456
- # Convert numeric timestamps to the expected string representation first
454
+ # Convert numeric timestamps to datetime first
457
455
  if isinstance(timestamp, (int, float)):
458
- timestamp = datetime.fromtimestamp(timestamp, timezone.utc).strftime(
459
- '%Y-%m-%d-%H:%M:%S.%f UTC'
460
- )
456
+ dt = datetime.fromtimestamp(timestamp, timezone.utc)
457
+ return dt.strftime('%Y:%m:%d %H:%M:%S')
461
458
 
462
459
  # Ensure we are working with a string from here on
463
460
  if not isinstance(timestamp, str):
464
461
  return str(timestamp)
465
462
 
466
- # If there is no fractional component, simply return the original string
467
- if '.' not in timestamp:
468
- return timestamp
469
-
470
- # Split out the main portion (up to the decimal point)
471
- main_part, fractional_and_suffix = timestamp.split('.', 1)
463
+ # Remove ' UTC' suffix if present
464
+ timestamp_clean = timestamp.replace(' UTC', '').strip()
472
465
 
473
- # Separate fractional digits from the suffix (typically ' UTC')
474
- if ' ' in fractional_and_suffix:
475
- fractional_part, suffix = fractional_and_suffix.split(' ', 1)
476
- suffix = ' ' + suffix # Re-attach the space removed by split
477
- else:
478
- fractional_part, suffix = fractional_and_suffix, ''
479
-
480
- # Guarantee exactly two digits for the fractional part
481
- fractional_part = (fractional_part + '00')[:2]
466
+ # Remove milliseconds if present (everything after the last dot)
467
+ if '.' in timestamp_clean:
468
+ timestamp_clean = timestamp_clean.split('.')[0]
482
469
 
483
- return f"{main_part}.{fractional_part}{suffix}"
470
+ # Parse the timestamp string and convert to desired format
471
+ try:
472
+ # Handle format: YYYY-MM-DD-HH:MM:SS
473
+ if timestamp_clean.count('-') >= 2:
474
+ # Replace first two dashes with colons for date part, third with space
475
+ parts = timestamp_clean.split('-')
476
+ if len(parts) >= 4:
477
+ # parts = ['2025', '10', '27', '19:31:20']
478
+ formatted = f"{parts[0]}:{parts[1]}:{parts[2]} {'-'.join(parts[3:])}"
479
+ return formatted
480
+ except Exception:
481
+ pass
482
+
483
+ # If parsing fails, return the cleaned string as-is
484
+ return timestamp_clean
484
485
 
485
486
  def _get_current_timestamp_str(self, stream_info: Optional[Dict[str, Any]], precision=False, frame_id: Optional[str]=None) -> str:
486
487
  """Get formatted current timestamp based on stream type."""
@@ -759,11 +759,58 @@ class VehicleMonitoringUseCase(BaseProcessor):
759
759
  seconds = round(float(timestamp % 60), 2)
760
760
  return f"{hours:02d}:{minutes:02d}:{seconds:.1f}"
761
761
 
762
+ def _format_timestamp(self, timestamp: Any) -> str:
763
+ """Format a timestamp to match the current timestamp format: YYYY:MM:DD HH:MM:SS.
764
+
765
+ The input can be either:
766
+ 1. A numeric Unix timestamp (``float`` / ``int``) – it will be converted to datetime.
767
+ 2. A string in the format ``YYYY-MM-DD-HH:MM:SS.ffffff UTC``.
768
+
769
+ The returned value will be in the format: YYYY:MM:DD HH:MM:SS (no milliseconds, no UTC suffix).
770
+
771
+ Example
772
+ -------
773
+ >>> self._format_timestamp("2025-10-27-19:31:20.187574 UTC")
774
+ '2025:10:27 19:31:20'
775
+ """
776
+
777
+ # Convert numeric timestamps to datetime first
778
+ if isinstance(timestamp, (int, float)):
779
+ dt = datetime.fromtimestamp(timestamp, timezone.utc)
780
+ return dt.strftime('%Y:%m:%d %H:%M:%S')
781
+
782
+ # Ensure we are working with a string from here on
783
+ if not isinstance(timestamp, str):
784
+ return str(timestamp)
785
+
786
+ # Remove ' UTC' suffix if present
787
+ timestamp_clean = timestamp.replace(' UTC', '').strip()
788
+
789
+ # Remove milliseconds if present (everything after the last dot)
790
+ if '.' in timestamp_clean:
791
+ timestamp_clean = timestamp_clean.split('.')[0]
792
+
793
+ # Parse the timestamp string and convert to desired format
794
+ try:
795
+ # Handle format: YYYY-MM-DD-HH:MM:SS
796
+ if timestamp_clean.count('-') >= 2:
797
+ # Replace first two dashes with colons for date part, third with space
798
+ parts = timestamp_clean.split('-')
799
+ if len(parts) >= 4:
800
+ # parts = ['2025', '10', '27', '19:31:20']
801
+ formatted = f"{parts[0]}:{parts[1]}:{parts[2]} {'-'.join(parts[3:])}"
802
+ return formatted
803
+ except Exception:
804
+ pass
805
+
806
+ # If parsing fails, return the cleaned string as-is
807
+ return timestamp_clean
808
+
762
809
  def _get_current_timestamp_str(self, stream_info: Optional[Dict[str, Any]], precision=False, frame_id: Optional[str]=None) -> str:
763
810
  """Get formatted current timestamp based on stream type."""
811
+
764
812
  if not stream_info:
765
813
  return "00:00:00.00"
766
-
767
814
  if precision:
768
815
  if stream_info.get("input_settings", {}).get("start_frame", "na") != "na":
769
816
  if frame_id:
@@ -772,7 +819,6 @@ class VehicleMonitoringUseCase(BaseProcessor):
772
819
  start_time = stream_info.get("input_settings", {}).get("start_frame", 30)/stream_info.get("input_settings", {}).get("original_fps", 30)
773
820
  stream_time_str = self._format_timestamp_for_video(start_time)
774
821
 
775
-
776
822
  return self._format_timestamp(stream_info.get("input_settings", {}).get("stream_time", "NA"))
777
823
  else:
778
824
  return datetime.now(timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC")
@@ -784,7 +830,8 @@ class VehicleMonitoringUseCase(BaseProcessor):
784
830
  start_time = stream_info.get("input_settings", {}).get("start_frame", 30)/stream_info.get("input_settings", {}).get("original_fps", 30)
785
831
 
786
832
  stream_time_str = self._format_timestamp_for_video(start_time)
787
-
833
+
834
+
788
835
  return self._format_timestamp(stream_info.get("input_settings", {}).get("stream_time", "NA"))
789
836
  else:
790
837
  stream_time_str = stream_info.get("input_settings", {}).get("stream_info", {}).get("stream_time", "")
@@ -806,23 +853,57 @@ class VehicleMonitoringUseCase(BaseProcessor):
806
853
 
807
854
  if precision:
808
855
  if self.start_timer is None:
809
- self.start_timer = stream_info.get("input_settings", {}).get("stream_time", "NA")
856
+ candidate = stream_info.get("input_settings", {}).get("stream_time")
857
+ if not candidate or candidate == "NA":
858
+ candidate = datetime.now(timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC")
859
+ self.start_timer = candidate
810
860
  return self._format_timestamp(self.start_timer)
811
861
  elif stream_info.get("input_settings", {}).get("start_frame", "na") == 1:
812
- self.start_timer = stream_info.get("input_settings", {}).get("stream_time", "NA")
862
+ candidate = stream_info.get("input_settings", {}).get("stream_time")
863
+ if not candidate or candidate == "NA":
864
+ candidate = datetime.now(timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC")
865
+ self.start_timer = candidate
813
866
  return self._format_timestamp(self.start_timer)
814
867
  else:
815
868
  return self._format_timestamp(self.start_timer)
816
869
 
817
870
  if self.start_timer is None:
818
- self.start_timer = stream_info.get("input_settings", {}).get("stream_time", "NA")
871
+ # Prefer direct input_settings.stream_time if available and not NA
872
+ candidate = stream_info.get("input_settings", {}).get("stream_time")
873
+ if not candidate or candidate == "NA":
874
+ # Fallback to nested stream_info.stream_time used by current timestamp path
875
+ stream_time_str = stream_info.get("input_settings", {}).get("stream_info", {}).get("stream_time", "")
876
+ if stream_time_str:
877
+ try:
878
+ timestamp_str = stream_time_str.replace(" UTC", "")
879
+ dt = datetime.strptime(timestamp_str, "%Y-%m-%d-%H:%M:%S.%f")
880
+ self._tracking_start_time = dt.replace(tzinfo=timezone.utc).timestamp()
881
+ candidate = datetime.fromtimestamp(self._tracking_start_time, timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC")
882
+ except:
883
+ candidate = datetime.now(timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC")
884
+ else:
885
+ candidate = datetime.now(timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC")
886
+ self.start_timer = candidate
819
887
  return self._format_timestamp(self.start_timer)
820
888
  elif stream_info.get("input_settings", {}).get("start_frame", "na") == 1:
821
- self.start_timer = stream_info.get("input_settings", {}).get("stream_time", "NA")
889
+ candidate = stream_info.get("input_settings", {}).get("stream_time")
890
+ if not candidate or candidate == "NA":
891
+ stream_time_str = stream_info.get("input_settings", {}).get("stream_info", {}).get("stream_time", "")
892
+ if stream_time_str:
893
+ try:
894
+ timestamp_str = stream_time_str.replace(" UTC", "")
895
+ dt = datetime.strptime(timestamp_str, "%Y-%m-%d-%H:%M:%S.%f")
896
+ ts = dt.replace(tzinfo=timezone.utc).timestamp()
897
+ candidate = datetime.fromtimestamp(ts, timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC")
898
+ except:
899
+ candidate = datetime.now(timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC")
900
+ else:
901
+ candidate = datetime.now(timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC")
902
+ self.start_timer = candidate
822
903
  return self._format_timestamp(self.start_timer)
823
904
 
824
905
  else:
825
- if self.start_timer is not None:
906
+ if self.start_timer is not None and self.start_timer != "NA":
826
907
  return self._format_timestamp(self.start_timer)
827
908
 
828
909
  if self._tracking_start_time is None:
@@ -841,52 +922,6 @@ class VehicleMonitoringUseCase(BaseProcessor):
841
922
  dt = dt.replace(minute=0, second=0, microsecond=0)
842
923
  return dt.strftime('%Y:%m:%d %H:%M:%S')
843
924
 
844
- def _format_timestamp(self, timestamp: Any) -> str:
845
- """Format a timestamp so that exactly two digits follow the decimal point (milliseconds).
846
-
847
- The input can be either:
848
- 1. A numeric Unix timestamp (``float`` / ``int``) – it will first be converted to a
849
- string in the format ``YYYY-MM-DD-HH:MM:SS.ffffff UTC``.
850
- 2. A string already following the same layout.
851
-
852
- The returned value preserves the overall format of the input but truncates or pads
853
- the fractional seconds portion to **exactly two digits**.
854
-
855
- Example
856
- -------
857
- >>> self._format_timestamp("2025-08-19-04:22:47.187574 UTC")
858
- '2025-08-19-04:22:47.18 UTC'
859
- """
860
-
861
- # Convert numeric timestamps to the expected string representation first
862
- if isinstance(timestamp, (int, float)):
863
- timestamp = datetime.fromtimestamp(timestamp, timezone.utc).strftime(
864
- '%Y-%m-%d-%H:%M:%S.%f UTC'
865
- )
866
-
867
- # Ensure we are working with a string from here on
868
- if not isinstance(timestamp, str):
869
- return str(timestamp)
870
-
871
- # If there is no fractional component, simply return the original string
872
- if '.' not in timestamp:
873
- return timestamp
874
-
875
- # Split out the main portion (up to the decimal point)
876
- main_part, fractional_and_suffix = timestamp.split('.', 1)
877
-
878
- # Separate fractional digits from the suffix (typically ' UTC')
879
- if ' ' in fractional_and_suffix:
880
- fractional_part, suffix = fractional_and_suffix.split(' ', 1)
881
- suffix = ' ' + suffix # Re-attach the space removed by split
882
- else:
883
- fractional_part, suffix = fractional_and_suffix, ''
884
-
885
- # Guarantee exactly two digits for the fractional part
886
- fractional_part = (fractional_part + '00')[:2]
887
-
888
- return f"{main_part}.{fractional_part}{suffix}"
889
-
890
925
  def _count_categories(self, detections: list, config: VehicleMonitoringConfig) -> dict:
891
926
  counts = {}
892
927
  for det in detections:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: matrice_analytics
3
- Version: 0.1.43
3
+ Version: 0.1.45
4
4
  Summary: Common server utilities for Matrice.ai services
5
5
  Author-email: "Matrice.ai" <dipendra@matrice.ai>
6
6
  License-Expression: MIT
@@ -12,7 +12,7 @@ matrice_analytics/boundary_drawing_internal/usage/boundary_drawer_launcher.py,sh
12
12
  matrice_analytics/boundary_drawing_internal/usage/simple_boundary_launcher.py,sha256=jHPriRLorLuiC8km0MFNS96w121tKxd7t5GQl7I5kKE,3494
13
13
  matrice_analytics/post_processing/README.md,sha256=bDszazvqV5xbGhMM6hDaMctIyk5gox9bADo2IZZ9Goo,13368
14
14
  matrice_analytics/post_processing/__init__.py,sha256=dxGBUQaRCGndQmXpYAWqUhDeUZAcxU-_6HFnm3GRDRA,29417
15
- matrice_analytics/post_processing/config.py,sha256=V0s86qNapyDE6Q81ZS_1uzNqAjz-vc5L-9Tb33XaLEo,6771
15
+ matrice_analytics/post_processing/config.py,sha256=XQLl1bW3X0vqb--b_OAQcvSP0JKPGEcLUdHE8NRnqVs,6770
16
16
  matrice_analytics/post_processing/post_processor.py,sha256=F838_vc7p9tjcp-vTMgTbpHqQcLX94xhL9HM06Wvpo8,44384
17
17
  matrice_analytics/post_processing/advanced_tracker/README.md,sha256=RM8dynVoUWKn_hTbw9c6jHAbnQj-8hEAXnmuRZr2w1M,22485
18
18
  matrice_analytics/post_processing/advanced_tracker/__init__.py,sha256=tAPFzI_Yep5TLX60FDwKqBqppc-EbxSr0wNsQ9DGI1o,423
@@ -29,11 +29,11 @@ matrice_analytics/post_processing/core/config_utils.py,sha256=QuAS-_JKSoNOtfUWgr
29
29
  matrice_analytics/post_processing/face_reg/__init__.py,sha256=yntaiGlW9vdjBpPZQXNuovALihJPzRlFyUE88l3MhBA,1364
30
30
  matrice_analytics/post_processing/face_reg/compare_similarity.py,sha256=NlFc8b2a74k0PqSFAbuM_fUbA1BT3pr3VUgvSqRpJzQ,23396
31
31
  matrice_analytics/post_processing/face_reg/embedding_manager.py,sha256=qbh0df3-YbE0qvFDQvjpCg-JrsCZRJ5capjQ2LPOj1k,35619
32
- matrice_analytics/post_processing/face_reg/face_recognition.py,sha256=17RVs_5BtGw2ruksL2JLbpXljbs4-96tP-yKGX3hrOI,90232
33
- matrice_analytics/post_processing/face_reg/face_recognition_client.py,sha256=YaC73mN0yrHZYkjJ7nZIZen5YCVOwEX2gn6I1XpHTW0,27575
34
- matrice_analytics/post_processing/face_reg/people_activity_logging.py,sha256=NhJXy9jCy_mlZiDXv8IeGyrRN_TL0kKCe3ZOlaNbZUw,13676
32
+ matrice_analytics/post_processing/face_reg/face_recognition.py,sha256=iKblqR88bcvP6Vu-8_FEuBLgGSZs5nfJVSTY-U4Ws9c,92786
33
+ matrice_analytics/post_processing/face_reg/face_recognition_client.py,sha256=4H0I1fLmv6BaqWQXRWOHN12GIV5IsHk5xkDOUI-JGQ8,28242
34
+ matrice_analytics/post_processing/face_reg/people_activity_logging.py,sha256=xV5BliZf4As_sg4h-fqav5-KZeGi96fEfv8vLSliEk8,13835
35
35
  matrice_analytics/post_processing/ocr/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
36
- matrice_analytics/post_processing/ocr/easyocr_extractor.py,sha256=FwVoUATYdiZtfhSAoiyCo_9dgA786pFZfONx6tsQOfE,11403
36
+ matrice_analytics/post_processing/ocr/easyocr_extractor.py,sha256=RMrRoGb2gMcJEGouQn8U9cCgCLXPT7qRa8liI4LNxFM,11555
37
37
  matrice_analytics/post_processing/ocr/postprocessing.py,sha256=RILArp8I9WRH7bALVZ9wPGc-aR7YMdqV1ndOOIcOnGQ,12309
38
38
  matrice_analytics/post_processing/ocr/preprocessing.py,sha256=LZolyUw4syMU6Q0V6SQzvkITVSmlkvwu0p9cUNhkbgA,1790
39
39
  matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/__init__.py,sha256=OHd4VO0dI8daHojE5900DvreiDT6SGOSZF6VvxU0Tx4,184
@@ -80,6 +80,7 @@ matrice_analytics/post_processing/test_cases/test_customer_service.py,sha256=gfJ
80
80
  matrice_analytics/post_processing/test_cases/test_data_generators.py,sha256=RYeY1pCQdPSAsnevdMkqudBOCIDBrWS2-cZK-hcOSlU,20214
81
81
  matrice_analytics/post_processing/test_cases/test_people_counting.py,sha256=1QINfIvtQ5idIMHaojgilRcSiCCVqZPd1fyGOM08HiE,19194
82
82
  matrice_analytics/post_processing/test_cases/test_processor.py,sha256=Mi0dRkpszChMS1SOPVBHj2bgkRt93Xxl94mvpQ-5yws,19799
83
+ matrice_analytics/post_processing/test_cases/test_usecases.py,sha256=e09c9JaOhtiwO5_TXDV5V_dPsc_jJBG36egu-WM02mE,6331
83
84
  matrice_analytics/post_processing/test_cases/test_utilities.py,sha256=zUtBqwELjovkhQfhn1vM-y7aH04z9sFvt6LIpXXBFSE,13415
84
85
  matrice_analytics/post_processing/test_cases/test_utils.py,sha256=lgDX0vILylA6m8sG3_3kxAJ7TiDo8xkprJNfBrLoID4,29371
85
86
  matrice_analytics/post_processing/usecases/Histopathological_Cancer_Detection_img.py,sha256=bHDXxxG3QgWMFZbDuBaJWpkIvxTXsFMTqCPBCFm3SDs,30247
@@ -100,7 +101,7 @@ matrice_analytics/post_processing/usecases/cardiomegaly_classification.py,sha256
100
101
  matrice_analytics/post_processing/usecases/cell_microscopy_segmentation.py,sha256=eQ_s5u3Vnvja6-FmI6ZPxlNkaZtG-pVjTu8NuLjZJ5M,43714
101
102
  matrice_analytics/post_processing/usecases/chicken_pose_detection.py,sha256=-e8di7Am-E-FCQFrSY8qJTO1aWtdRAVJoE-VKBgcyyI,29291
102
103
  matrice_analytics/post_processing/usecases/child_monitoring.py,sha256=z3oymoqq4hDGwA8MkdEONZW_Vx5CAZmvzZaNLsqmCfw,39380
103
- matrice_analytics/post_processing/usecases/color_detection.py,sha256=gucKGrIexZ8fgARzehrXXPBHxNVw1u5-OqF2HysH-eo,90293
104
+ matrice_analytics/post_processing/usecases/color_detection.py,sha256=56oLhecGN8jaPcRLNpZ-AJYjhYqvcRq_BX7rjkPHW0s,92361
104
105
  matrice_analytics/post_processing/usecases/color_map_utils.py,sha256=SP-AEVcjLmL8rxblu-ixqUJC2fqlcr7ab4hWo4Fcr_k,2677
105
106
  matrice_analytics/post_processing/usecases/concrete_crack_detection.py,sha256=pxhOH_hG4hq9yytNepbGMdk2W_lTG8D1_2RAagaPBkg,40252
106
107
  matrice_analytics/post_processing/usecases/crop_weed_detection.py,sha256=Ao1k5fJDYU_f6yZ8VO-jW8-esECV0-zY5Q570c_fako,35674
@@ -115,7 +116,7 @@ matrice_analytics/post_processing/usecases/face_emotion.py,sha256=eRfqBdryB0uNoO
115
116
  matrice_analytics/post_processing/usecases/face_recognition.py,sha256=T5xAuv6b9OrkmTmoXgZs4LZ5XUsbvp9xCpeLBwdu7eI,40231
116
117
  matrice_analytics/post_processing/usecases/fashion_detection.py,sha256=f9gpzMDhIW-gyn46k9jgf8nY7YeoqAnTxGOzksabFbE,40457
117
118
  matrice_analytics/post_processing/usecases/field_mapping.py,sha256=JDwYX8pd2W-waDvBh98Y_o_uchJu7wEYbFxOliA4Iq4,39822
118
- matrice_analytics/post_processing/usecases/fire_detection.py,sha256=6ZwIWSweFZoeRk1CoOtOdlEV-CtYj3gWnhSQw6ONZxg,52294
119
+ matrice_analytics/post_processing/usecases/fire_detection.py,sha256=o57jhIrsVBRkpD4nzaUHyuBRA4YZb3yNvQz0Ri_xiVg,54613
119
120
  matrice_analytics/post_processing/usecases/flare_analysis.py,sha256=3nf4fUeUwlP_UII0h5fQkUGPXbr32ZnJjaM-dukNSP8,42680
120
121
  matrice_analytics/post_processing/usecases/flower_segmentation.py,sha256=4I7qMx9Ztxg_hy9KTVX-3qBhAN-QwDt_Yigf9fFjLus,52017
121
122
  matrice_analytics/post_processing/usecases/gas_leak_detection.py,sha256=KL2ft7fXvjTas-65-QgcJm3W8KBsrwF44qibSXjfaLc,40557
@@ -126,7 +127,7 @@ matrice_analytics/post_processing/usecases/leaf.py,sha256=cwgB1ZNxkQFtkk-thSJrkX
126
127
  matrice_analytics/post_processing/usecases/leaf_disease.py,sha256=bkiLccTdf4KUq3he4eCpBlKXb5exr-WBhQ_oWQ7os68,36225
127
128
  matrice_analytics/post_processing/usecases/leak_detection.py,sha256=oOCLLVMuXVeXPHyN8FUrD3U9JYJJwIz-5fcEMgvLdls,40531
128
129
  matrice_analytics/post_processing/usecases/license_plate_detection.py,sha256=dsavd92-wnyXCNrCzaRj24zH7BVvLSa09HkYsrOXYDM,50806
129
- matrice_analytics/post_processing/usecases/license_plate_monitoring.py,sha256=Mu8Hu1EqTwYQ1lqDyz3mioDiTDJddIT2F2HbLWeZSfc,89059
130
+ matrice_analytics/post_processing/usecases/license_plate_monitoring.py,sha256=hjc9tQkUzVwvWuNohw28P2SJNHb25P5ERZbNlRkSKOk,91379
130
131
  matrice_analytics/post_processing/usecases/litter_monitoring.py,sha256=XaHAUGRBDJg_iVbu8hRMjTR-5TqrLj6ZNCRkInbzZTY,33255
131
132
  matrice_analytics/post_processing/usecases/mask_detection.py,sha256=L_s6ZiT5zeXG-BsFcskb3HEG98DhLgqeMSDmCuwOteU,41501
132
133
  matrice_analytics/post_processing/usecases/natural_disaster.py,sha256=ehxdPBoYcZWGVDOVn_mHFoz4lIE8LrveAkuXQj0n9XE,44253
@@ -134,7 +135,7 @@ matrice_analytics/post_processing/usecases/parking.py,sha256=lqTGqcjUZZPFw3tu11H
134
135
  matrice_analytics/post_processing/usecases/parking_space_detection.py,sha256=xwhkJjGGKcT827URbasi3olYqhd95Sh0zsEIphwzcgY,39561
135
136
  matrice_analytics/post_processing/usecases/pcb_defect_detection.py,sha256=xH3q-WoR3TwMUeUvWw1W7vPLdCUfu_Kl_gQ9dZFf1SE,43006
136
137
  matrice_analytics/post_processing/usecases/pedestrian_detection.py,sha256=hPFtvpWXXEsbDavmuiXIhrosMNlOhGya--jukT-ZOHA,39288
137
- matrice_analytics/post_processing/usecases/people_counting.py,sha256=IeNu33nWhzRlbOPf8DPbzQp1QRdPSAIr4nO3A1wUr_k,35272
138
+ matrice_analytics/post_processing/usecases/people_counting.py,sha256=mlzezqESW2qifW2PmsygWMjHLw6v8I34p9L2RwbxZMo,35267
138
139
  matrice_analytics/post_processing/usecases/people_counting_bckp.py,sha256=WM9te7oYyhu5f_bIMye_D5BpEn6CwA-6Kz95IMLmSbs,82209
139
140
  matrice_analytics/post_processing/usecases/people_tracking.py,sha256=iXzGJgqKgWxvIVLqa1cFKkiF0DrHolwghSiJ2P8mDhc,90484
140
141
  matrice_analytics/post_processing/usecases/pipeline_detection.py,sha256=VsLTXMAqx0tRw7Olrxqx7SBLolZR7p2aFOrdSXLS-kE,30796
@@ -158,7 +159,7 @@ matrice_analytics/post_processing/usecases/theft_detection.py,sha256=Rs_zKn2z9YM
158
159
  matrice_analytics/post_processing/usecases/traffic_sign_monitoring.py,sha256=nDlEzHgMlUjy_VtJ7usnEzMcdSs-jouqaoJpJ8DYUMw,34351
159
160
  matrice_analytics/post_processing/usecases/underground_pipeline_defect_detection.py,sha256=W_2joZStsP0jl2zn89-jtdtqqGv3vJ0amsalbE5WKwo,37647
160
161
  matrice_analytics/post_processing/usecases/underwater_pollution_detection.py,sha256=jqP1ZKfDZe2-56Lyvgb2DxnbqRfvxm6pPL0Ck3esfBk,40356
161
- matrice_analytics/post_processing/usecases/vehicle_monitoring.py,sha256=iH0kqx8VwWldVLnWbDIMyjKBgOMLakbqMMHypXsxGdo,50529
162
+ matrice_analytics/post_processing/usecases/vehicle_monitoring.py,sha256=QsO-coozfy29rY6NszwA6A7nFBOGysfMz5S5VVY7Beg,52849
162
163
  matrice_analytics/post_processing/usecases/warehouse_object_segmentation.py,sha256=5uZXTJL_A3tUEN08T-_ZQpUoJ9gqbuuMc4z2mT4sMnQ,43753
163
164
  matrice_analytics/post_processing/usecases/waterbody_segmentation.py,sha256=JsCxDEMB8s4WDcezfJDr2zrjM-TCjB9hxOztzSvWmpY,45268
164
165
  matrice_analytics/post_processing/usecases/weapon_detection.py,sha256=AhbVpJaa2I3aRCEAdIxovY5xd9370dUY4JllCQ8tdT4,37185
@@ -166,9 +167,9 @@ matrice_analytics/post_processing/usecases/weld_defect_detection.py,sha256=b0dAJ
166
167
  matrice_analytics/post_processing/usecases/wildlife_monitoring.py,sha256=TMVHJ5GLezmqG7DywmqbLggqNXgpsb63MD7IR6kvDkk,43446
167
168
  matrice_analytics/post_processing/usecases/windmill_maintenance.py,sha256=G1eqo3Z-HYmGJ6oeZYrpZwhpvqQ9Lc_T-6S7BLBXHeA,40498
168
169
  matrice_analytics/post_processing/usecases/wound_segmentation.py,sha256=ehNX6VuWMB3xAnCySO3ra3Tf_5FUNg5LCSdq_91h374,38342
169
- matrice_analytics/post_processing/usecases/color/clip.py,sha256=RW3XdsQR2oAMZUTTCCUPBSdAldgG3w4zki_S-hb4tVA,27561
170
+ matrice_analytics/post_processing/usecases/color/clip.py,sha256=EN8z3XIwkkmuhGcSjTWV0Os7tSVwZOL-svsxkzT21e4,27586
170
171
  matrice_analytics/post_processing/usecases/color/color_map_utils.py,sha256=SP-AEVcjLmL8rxblu-ixqUJC2fqlcr7ab4hWo4Fcr_k,2677
171
- matrice_analytics/post_processing/usecases/color/color_mapper.py,sha256=nKPc28mJLlrl2HPua5EUUMweRRSI6WrrUBkeitTm7ms,17459
172
+ matrice_analytics/post_processing/usecases/color/color_mapper.py,sha256=aUbs5bGxxKEg0CFZUIKE1zbABLr02ZB81HRUb_wdPfk,17458
172
173
  matrice_analytics/post_processing/usecases/color/clip_processor/merges.txt,sha256=n9aR98gDkhDg_O0VhlRmxlgg0JtjmIsBdL_iXeKZBRo,524619
173
174
  matrice_analytics/post_processing/usecases/color/clip_processor/preprocessor_config.json,sha256=j7VHQDZW6QGbCYLSMQsEW-85udKoEaDJh1blLUjiXbY,504
174
175
  matrice_analytics/post_processing/usecases/color/clip_processor/special_tokens_map.json,sha256=LNs7gzGmDJL8HlWhPp_WH9IpPFpRJ1_czNYreABSUw4,588
@@ -188,8 +189,8 @@ matrice_analytics/post_processing/utils/format_utils.py,sha256=UTF7A5h9j0_S12xH9
188
189
  matrice_analytics/post_processing/utils/geometry_utils.py,sha256=BWfdM6RsdJTTLR1GqkWfdwpjMEjTCJyuBxA4zVGKdfk,9623
189
190
  matrice_analytics/post_processing/utils/smoothing_utils.py,sha256=78U-yucAcjUiZ0NIAc9NOUSIT0PWP1cqyIPA_Fdrjp0,14699
190
191
  matrice_analytics/post_processing/utils/tracking_utils.py,sha256=rWxuotnJ3VLMHIBOud2KLcu4yZfDp7hVPWUtNAq_2xw,8288
191
- matrice_analytics-0.1.43.dist-info/licenses/LICENSE.txt,sha256=_uQUZpgO0mRYL5-fPoEvLSbNnLPv6OmbeEDCHXhK6Qc,1066
192
- matrice_analytics-0.1.43.dist-info/METADATA,sha256=XEb8jt0ezecP_Phg22fCGsMBFToMhI2iSHb5FAahQhE,14378
193
- matrice_analytics-0.1.43.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
194
- matrice_analytics-0.1.43.dist-info/top_level.txt,sha256=STAPEU-e-rWTerXaspdi76T_eVRSrEfFpURSP7_Dt8E,18
195
- matrice_analytics-0.1.43.dist-info/RECORD,,
192
+ matrice_analytics-0.1.45.dist-info/licenses/LICENSE.txt,sha256=_uQUZpgO0mRYL5-fPoEvLSbNnLPv6OmbeEDCHXhK6Qc,1066
193
+ matrice_analytics-0.1.45.dist-info/METADATA,sha256=cejPbTOU7QL_WuBVD_azQBNbihZX1FM-aLK9QrM7x58,14378
194
+ matrice_analytics-0.1.45.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
195
+ matrice_analytics-0.1.45.dist-info/top_level.txt,sha256=STAPEU-e-rWTerXaspdi76T_eVRSrEfFpURSP7_Dt8E,18
196
+ matrice_analytics-0.1.45.dist-info/RECORD,,