plexus-python-common 1.0.62__tar.gz → 1.0.64__tar.gz

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.
Files changed (93) hide show
  1. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/PKG-INFO +1 -1
  2. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/src/plexus/common/carto/OSMFile.py +1 -1
  3. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/src/plexus/common/carto/OSMNode.py +1 -1
  4. plexus_python_common-1.0.62/src/plexus/common/proj.py → plexus_python_common-1.0.64/src/plexus/common/utils/gisutils.py +102 -1
  5. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/src/plexus/common/utils/tagutils.py +53 -0
  6. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/src/plexus_python_common.egg-info/PKG-INFO +1 -1
  7. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/src/plexus_python_common.egg-info/SOURCES.txt +2 -4
  8. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/test/plexus_tests/common/carto/osm_file_test.py +2 -2
  9. plexus_python_common-1.0.62/test/plexus_tests/common/proj_test.py → plexus_python_common-1.0.64/test/plexus_tests/common/utils/gisutils_test.py +64 -2
  10. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/test/plexus_tests/common/utils/tagutils_test.py +175 -0
  11. plexus_python_common-1.0.62/src/plexus/common/pose.py +0 -107
  12. plexus_python_common-1.0.62/test/plexus_tests/common/pose_test.py +0 -66
  13. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/.editorconfig +0 -0
  14. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/.github/workflows/pr.yml +0 -0
  15. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/.github/workflows/push.yml +0 -0
  16. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/.gitignore +0 -0
  17. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/MANIFEST.in +0 -0
  18. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/README.md +0 -0
  19. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/VERSION +0 -0
  20. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/pyproject.toml +0 -0
  21. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/resources/unittest/jsonutils/dummy.0.jsonl +0 -0
  22. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/resources/unittest/jsonutils/dummy.1.jsonl +0 -0
  23. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/resources/unittest/jsonutils/dummy.2.jsonl +0 -0
  24. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/resources/unittest/pathutils/0-dummy +0 -0
  25. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/resources/unittest/pathutils/1-dummy +0 -0
  26. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/resources/unittest/pathutils/2-dummy +0 -0
  27. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/resources/unittest/pathutils/dummy.0.0.jsonl +0 -0
  28. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/resources/unittest/pathutils/dummy.0.0.vol-0.jsonl +0 -0
  29. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/resources/unittest/pathutils/dummy.0.jsonl +0 -0
  30. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/resources/unittest/pathutils/dummy.1.1.jsonl +0 -0
  31. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/resources/unittest/pathutils/dummy.1.1.vol-1.jsonl +0 -0
  32. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/resources/unittest/pathutils/dummy.1.jsonl +0 -0
  33. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/resources/unittest/pathutils/dummy.2.2.jsonl +0 -0
  34. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/resources/unittest/pathutils/dummy.2.2.vol-2.jsonl +0 -0
  35. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/resources/unittest/pathutils/dummy.2.jsonl +0 -0
  36. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/resources/unittest/pathutils/dummy.csv.part0 +0 -0
  37. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/resources/unittest/pathutils/dummy.csv.part1 +0 -0
  38. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/resources/unittest/pathutils/dummy.csv.part2 +0 -0
  39. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/resources/unittest/pathutils/dummy.txt +0 -0
  40. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/resources/unittest/s3utils/dir.baz/file.bar.baz +0 -0
  41. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/resources/unittest/s3utils/dir.baz/file.foo.bar +0 -0
  42. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/resources/unittest/s3utils/dir.baz/file.foo.baz +0 -0
  43. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/resources/unittest/s3utils/dir.foo/dir.foo.bar/dir.foo.bar.baz/file.foo.bar.baz +0 -0
  44. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/resources/unittest/s3utils/dir.foo/dir.foo.bar/file.bar.baz +0 -0
  45. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/resources/unittest/s3utils/dir.foo/dir.foo.bar/file.foo.bar +0 -0
  46. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/resources/unittest/s3utils/dir.foo/dir.foo.bar/file.foo.baz +0 -0
  47. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/resources/unittest/s3utils/dir.foo/file.bar +0 -0
  48. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/resources/unittest/s3utils/dir.foo/file.baz +0 -0
  49. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/resources/unittest/s3utils/dir.foo/file.foo +0 -0
  50. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/resources/unittest/s3utils_archive/archive.compressed.zip +0 -0
  51. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/resources/unittest/s3utils_archive/archive.uncompressed.zip +0 -0
  52. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/setup.cfg +0 -0
  53. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/setup.py +0 -0
  54. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/src/plexus/common/__init__.py +0 -0
  55. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/src/plexus/common/carto/OSMTags.py +0 -0
  56. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/src/plexus/common/carto/OSMWay.py +0 -0
  57. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/src/plexus/common/carto/__init__.py +0 -0
  58. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/src/plexus/common/resources/__init__.py +0 -0
  59. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/src/plexus/common/resources/tags/__init__.py +0 -0
  60. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/src/plexus/common/resources/tags/unittest-1.0.0.tagset.yaml +0 -0
  61. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/src/plexus/common/resources/tags/universal-1.0.0.tagset.yaml +0 -0
  62. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/src/plexus/common/utils/__init__.py +0 -0
  63. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/src/plexus/common/utils/apiutils.py +0 -0
  64. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/src/plexus/common/utils/bagutils.py +0 -0
  65. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/src/plexus/common/utils/config.py +0 -0
  66. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/src/plexus/common/utils/datautils.py +0 -0
  67. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/src/plexus/common/utils/dockerutils.py +0 -0
  68. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/src/plexus/common/utils/jsonutils.py +0 -0
  69. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/src/plexus/common/utils/ormutils.py +0 -0
  70. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/src/plexus/common/utils/pathutils.py +0 -0
  71. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/src/plexus/common/utils/s3utils.py +0 -0
  72. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/src/plexus/common/utils/sqlutils.py +0 -0
  73. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/src/plexus/common/utils/strutils.py +0 -0
  74. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/src/plexus/common/utils/testutils.py +0 -0
  75. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/src/plexus_python_common.egg-info/dependency_links.txt +0 -0
  76. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/src/plexus_python_common.egg-info/not-zip-safe +0 -0
  77. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/src/plexus_python_common.egg-info/requires.txt +0 -0
  78. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/src/plexus_python_common.egg-info/top_level.txt +0 -0
  79. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/test/plexus_tests/__init__.py +0 -0
  80. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/test/plexus_tests/common/__init__.py +0 -0
  81. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/test/plexus_tests/common/carto/__init__.py +0 -0
  82. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/test/plexus_tests/common/carto/osm_tags_test.py +0 -0
  83. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/test/plexus_tests/common/utils/__init__.py +0 -0
  84. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/test/plexus_tests/common/utils/bagutils_test.py +0 -0
  85. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/test/plexus_tests/common/utils/datautils_test.py +0 -0
  86. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/test/plexus_tests/common/utils/dockerutils_test.py +0 -0
  87. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/test/plexus_tests/common/utils/jsonutils_test.py +0 -0
  88. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/test/plexus_tests/common/utils/ormutils_test.py +0 -0
  89. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/test/plexus_tests/common/utils/pathutils_test.py +0 -0
  90. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/test/plexus_tests/common/utils/s3utils_test.py +0 -0
  91. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/test/plexus_tests/common/utils/strutils_test.py +0 -0
  92. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/test/plexus_tests/common/utils/testutils_test.py +0 -0
  93. {plexus_python_common-1.0.62 → plexus_python_common-1.0.64}/test/testenv.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plexus-python-common
3
- Version: 1.0.62
3
+ Version: 1.0.64
4
4
  Classifier: Programming Language :: Python :: 3
5
5
  Classifier: Programming Language :: Python :: 3.12
6
6
  Classifier: Programming Language :: Python :: 3.13
@@ -5,7 +5,7 @@ import lxml.etree
5
5
  from plexus.common.carto.OSMNode import OSMNode
6
6
  from plexus.common.carto.OSMTags import OSMTags
7
7
  from plexus.common.carto.OSMWay import OSMWay
8
- from plexus.common.proj import Coord, Proj
8
+ from plexus.common.utils.gisutils import Coord, Proj
9
9
 
10
10
 
11
11
  class OSMFile(object):
@@ -1,7 +1,7 @@
1
1
  from iker.common.utils.strutils import repr_data
2
2
 
3
3
  from plexus.common.carto.OSMTags import OSMTags
4
- from plexus.common.proj import Coord
4
+ from plexus.common.utils.gisutils import Coord
5
5
 
6
6
 
7
7
  class OSMNode(object):
@@ -5,10 +5,111 @@ from typing import Annotated, Self
5
5
  import numpy as np
6
6
  import pydantic as pdt
7
7
  import pyproj
8
+ import pyquaternion as pyquat
8
9
  from iker.common.utils.funcutils import singleton
9
10
  from iker.common.utils.strutils import parse_params_string, repr_data, str_conv
10
11
 
11
- from plexus.common.pose import Pose
12
+
13
+ class Pose(object):
14
+ @classmethod
15
+ def from_numbers(
16
+ cls,
17
+ px: float,
18
+ py: float,
19
+ pz: float,
20
+ qx: float,
21
+ qy: float,
22
+ qz: float,
23
+ qw: float,
24
+ ts: float = 0,
25
+ ) -> Self:
26
+ """
27
+ Constructs a pose from numbers representing position and orientation
28
+ """
29
+ return Pose(ts, np.array([px, py, pz], dtype=np.float64), np.array([qw, qx, qy, qz], dtype=np.float64))
30
+
31
+ @classmethod
32
+ def add(cls, x: Self, d: Self) -> Self:
33
+ """
34
+ Performs pose SE3 addition, as x + d = y
35
+ """
36
+ xq = pyquat.Quaternion(x.q)
37
+ dq = pyquat.Quaternion(d.q)
38
+
39
+ yp = x.p + xq.rotate(d.p)
40
+ yq = xq * dq
41
+
42
+ return Pose(0, yp, yq.normalised.elements)
43
+
44
+ @classmethod
45
+ def sub(cls, y: Self, x: Self) -> Self:
46
+ """
47
+ Performs pose SE3 subtraction, as x + d = y => d = y - x
48
+ """
49
+ xq = pyquat.Quaternion(x.q)
50
+ yq = pyquat.Quaternion(y.q)
51
+
52
+ dp = xq.inverse.rotate(y.p - x.p)
53
+ dq = xq.inverse * yq
54
+
55
+ return Pose(0, dp, dq.normalised.elements)
56
+
57
+ @classmethod
58
+ def interpolate(cls, a: Self, b: Self, t: float) -> Self:
59
+ """
60
+ Interpolates between two given poses, as a * t + b * (1 - t)
61
+
62
+ :return: interpolated pose
63
+ """
64
+ ts = a.ts + (b.ts - a.ts) * t
65
+ p = a.p + (b.p - a.p) * t
66
+ q = pyquat.Quaternion.slerp(pyquat.Quaternion(a.q), pyquat.Quaternion(b.q), t)
67
+ return Pose(ts, p, q.normalised.elements)
68
+
69
+ def __init__(self, ts: float, p: np.ndarray, q: np.ndarray):
70
+ """
71
+ Represents a pose
72
+
73
+ :param ts: timestamp
74
+ :param p: position vector
75
+ :param q: orientation quaternion
76
+ """
77
+ self.ts = ts
78
+ self.p = p
79
+ self.q = q
80
+
81
+ def matrix(self) -> np.ndarray:
82
+ """
83
+ Returns the transformation matrix of this pose
84
+
85
+ :return: transformation matrix
86
+ """
87
+ r = pyquat.Quaternion(self.q).rotation_matrix
88
+ t = self.p[:, None]
89
+ return np.block([[r, t], [np.zeros((1, 3), dtype=np.float64), np.ones((1, 1), dtype=np.float64)]])
90
+
91
+ def translate(self, v: np.ndarray) -> np.ndarray:
92
+ """
93
+ Translate the given vector with the pose position
94
+
95
+ :param v: the vector to be translated
96
+
97
+ :return: translated vector
98
+ """
99
+ return self.p + v
100
+
101
+ def rotate(self, v: np.ndarray) -> np.ndarray:
102
+ """
103
+ Rotates the given vector with the pose orientation
104
+
105
+ :param v: the vector to be rotated
106
+
107
+ :return: rotated vector
108
+ """
109
+ return pyquat.Quaternion(self.q).rotate(v)
110
+
111
+ def __str__(self):
112
+ return repr_data(self)
12
113
 
13
114
 
14
115
  class Proj(ABC):
@@ -1,6 +1,7 @@
1
1
  import contextlib
2
2
  import dataclasses
3
3
  import datetime
4
+ import math
4
5
  import os
5
6
  import pathlib
6
7
  import textwrap
@@ -16,6 +17,7 @@ import sqlalchemy as sa
16
17
  import sqlalchemy.dialects.sqlite as sa_sqlite
17
18
  import sqlalchemy.orm as sa_orm
18
19
  from iker.common.utils.dbutils import ConnectionMaker
20
+ from iker.common.utils.dtutils import dt_from_ts_us, dt_to_ts_us
19
21
  from iker.common.utils.funcutils import memorized, singleton
20
22
  from iker.common.utils.iterutils import batched, head_or_none
21
23
  from iker.common.utils.iterutils import dicttree
@@ -48,6 +50,8 @@ __all__ = [
48
50
  "tag_cache_file_path",
49
51
  "TagCache",
50
52
  "tag_cache",
53
+ "standard_clip_duration_us",
54
+ "populate_clip_ranges",
51
55
  ]
52
56
 
53
57
 
@@ -1093,3 +1097,52 @@ def tag_cache(*, identifier: str | None = None, file_path: str | None = None) ->
1093
1097
  if file_path is not None:
1094
1098
  return TagCache(file_path=file_path)
1095
1099
  return TagCache(file_path=tag_cache_file_path())
1100
+
1101
+
1102
+
1103
+ @singleton
1104
+ def standard_clip_duration_us() -> int:
1105
+ """Duration of clips to split samples into, in microseconds, which is fixed to 20 seconds for now."""
1106
+ return 20 * 1_000_000 # 20 seconds
1107
+
1108
+
1109
+
1110
+ def populate_clip_ranges(
1111
+ data_begin_dt: datetime.datetime,
1112
+ data_end_dt: datetime.datetime,
1113
+ *,
1114
+ clip_duration_us: int = None
1115
+ ) -> Generator[tuple[datetime.datetime, datetime.datetime, datetime.datetime, datetime.datetime]]:
1116
+ """
1117
+ Generate clip begin datetime, clip data begin datetime, and clip data end datetime for each clip slot that overlaps
1118
+ with the given data begin and end datetimes.
1119
+ The clip begin datetime is the beginning of the clip slot, the clip data begin datetime is the maximum of the clip
1120
+ begin datetime and the data begin datetime, and the clip data end datetime is the minimum of the clip end datetime
1121
+ and the data end datetime.
1122
+
1123
+ :param data_begin_dt: The beginning datetime of the data.
1124
+ :param data_end_dt: The end datetime of the data.
1125
+ :param clip_duration_us: The duration of each clip in microseconds. If not provided,
1126
+ the standard clip duration will be used.
1127
+ :return: A generator of tuples containing the clip begin datetime, clip data begin datetime,
1128
+ and clip data end datetime
1129
+ """
1130
+ clip_duration_us = clip_duration_us or standard_clip_duration_us()
1131
+
1132
+ if data_begin_dt == data_end_dt:
1133
+ clip_slot = math.floor(dt_to_ts_us(data_begin_dt) / clip_duration_us)
1134
+ clip_begin_dt = dt_from_ts_us(clip_slot * clip_duration_us)
1135
+ clip_end_dt = dt_from_ts_us((clip_slot + 1) * clip_duration_us)
1136
+ yield clip_begin_dt, clip_end_dt, data_begin_dt, data_end_dt
1137
+ return
1138
+
1139
+ begin_clip_slot = math.floor(dt_to_ts_us(data_begin_dt) / clip_duration_us)
1140
+ end_clip_slot = math.ceil(dt_to_ts_us(data_end_dt) / clip_duration_us)
1141
+
1142
+ for clip_slot in range(begin_clip_slot, end_clip_slot):
1143
+ clip_begin_dt = dt_from_ts_us(clip_slot * clip_duration_us)
1144
+ clip_end_dt = dt_from_ts_us((clip_slot + 1) * clip_duration_us)
1145
+ clip_data_begin_dt = max(clip_begin_dt, data_begin_dt)
1146
+ clip_data_end_dt = min(clip_end_dt, data_end_dt)
1147
+
1148
+ yield clip_begin_dt, clip_end_dt, clip_data_begin_dt, clip_data_end_dt
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plexus-python-common
3
- Version: 1.0.62
3
+ Version: 1.0.64
4
4
  Classifier: Programming Language :: Python :: 3
5
5
  Classifier: Programming Language :: Python :: 3.12
6
6
  Classifier: Programming Language :: Python :: 3.13
@@ -39,8 +39,6 @@ resources/unittest/s3utils/dir.foo/dir.foo.bar/dir.foo.bar.baz/file.foo.bar.baz
39
39
  resources/unittest/s3utils_archive/archive.compressed.zip
40
40
  resources/unittest/s3utils_archive/archive.uncompressed.zip
41
41
  src/plexus/common/__init__.py
42
- src/plexus/common/pose.py
43
- src/plexus/common/proj.py
44
42
  src/plexus/common/carto/OSMFile.py
45
43
  src/plexus/common/carto/OSMNode.py
46
44
  src/plexus/common/carto/OSMTags.py
@@ -56,6 +54,7 @@ src/plexus/common/utils/bagutils.py
56
54
  src/plexus/common/utils/config.py
57
55
  src/plexus/common/utils/datautils.py
58
56
  src/plexus/common/utils/dockerutils.py
57
+ src/plexus/common/utils/gisutils.py
59
58
  src/plexus/common/utils/jsonutils.py
60
59
  src/plexus/common/utils/ormutils.py
61
60
  src/plexus/common/utils/pathutils.py
@@ -73,8 +72,6 @@ src/plexus_python_common.egg-info/top_level.txt
73
72
  test/testenv.py
74
73
  test/plexus_tests/__init__.py
75
74
  test/plexus_tests/common/__init__.py
76
- test/plexus_tests/common/pose_test.py
77
- test/plexus_tests/common/proj_test.py
78
75
  test/plexus_tests/common/carto/__init__.py
79
76
  test/plexus_tests/common/carto/osm_file_test.py
80
77
  test/plexus_tests/common/carto/osm_tags_test.py
@@ -82,6 +79,7 @@ test/plexus_tests/common/utils/__init__.py
82
79
  test/plexus_tests/common/utils/bagutils_test.py
83
80
  test/plexus_tests/common/utils/datautils_test.py
84
81
  test/plexus_tests/common/utils/dockerutils_test.py
82
+ test/plexus_tests/common/utils/gisutils_test.py
85
83
  test/plexus_tests/common/utils/jsonutils_test.py
86
84
  test/plexus_tests/common/utils/ormutils_test.py
87
85
  test/plexus_tests/common/utils/pathutils_test.py
@@ -4,8 +4,8 @@ import unittest.mock
4
4
  import lxml.etree
5
5
 
6
6
  from plexus.common.carto import OSMFile
7
- from plexus.common.proj import Coord
8
- from plexus.common.proj import make_proj
7
+ from plexus.common.utils.gisutils import Coord
8
+ from plexus.common.utils.gisutils import make_proj
9
9
 
10
10
 
11
11
  class MockedTree(object):
@@ -1,11 +1,73 @@
1
+ import math
1
2
  import random
2
3
  import unittest
3
4
 
4
5
  import ddt
6
+ import numpy as np
5
7
  import pydantic as pdt
8
+ import pyquaternion as pyquat
9
+ from iker.common.utils.randutils import randomizer
6
10
 
7
- from plexus.common.proj import Coord, EQDCProj, UTMProj, WebMercProj
8
- from plexus.common.proj import make_proj
11
+ from plexus.common.utils.gisutils import Coord, EQDCProj, UTMProj, WebMercProj
12
+ from plexus.common.utils.gisutils import Pose
13
+ from plexus.common.utils.gisutils import make_proj
14
+
15
+
16
+ @ddt.ddt
17
+ class PoseTest(unittest.TestCase):
18
+
19
+ @staticmethod
20
+ def random_pose() -> Pose:
21
+ q = pyquat.Quaternion(axis=randomizer().random_unit_vector(3),
22
+ angle=math.pi * randomizer().next_float(0.0, 0.5))
23
+ return Pose(0, np.array(randomizer().random_unit_vector(3)), q.normalised.elements)
24
+
25
+ def assert_array_almost_equal(self, xs: np.ndarray, ys: np.ndarray, delta: float):
26
+ self.assertEqual(len(xs), len(ys))
27
+ for x, y in zip(xs, ys):
28
+ self.assertAlmostEqual(x, y, delta=delta)
29
+
30
+ def test_builtin_init(self):
31
+ for _ in range(0, 10000):
32
+ expect = PoseTest.random_pose()
33
+ actual = Pose(0, expect.p, expect.q)
34
+
35
+ self.assert_array_almost_equal(expect.p, actual.p, delta=1e-9)
36
+ self.assert_array_almost_equal(expect.q, actual.q, delta=1e-9)
37
+
38
+ def test_add_sub(self):
39
+ for _ in range(0, 10000):
40
+ pose = PoseTest.random_pose()
41
+ delta = PoseTest.random_pose()
42
+
43
+ result = Pose.sub(Pose.add(pose, delta), pose)
44
+
45
+ self.assert_array_almost_equal(delta.p, result.p, delta=1e-9)
46
+ self.assert_array_almost_equal(delta.q, result.q, delta=1e-9)
47
+
48
+ def test_interpolate(self):
49
+ for _ in range(0, 10000):
50
+ t = randomizer().next_float()
51
+
52
+ pose1 = PoseTest.random_pose()
53
+ pose2 = PoseTest.random_pose()
54
+
55
+ inter1 = Pose.interpolate(pose1, pose2, t)
56
+ inter2 = Pose.interpolate(pose2, pose1, 1.0 - t)
57
+
58
+ self.assert_array_almost_equal(inter1.p, inter2.p, delta=1e-9)
59
+ self.assert_array_almost_equal(inter1.q, inter2.q, delta=1e-9)
60
+
61
+ def test_matrix(self):
62
+ for _ in range(0, 10000):
63
+ point = np.array(randomizer().random_unit_vector(3))
64
+
65
+ pose = PoseTest.random_pose()
66
+
67
+ expect = pose.translate(pose.rotate(point))
68
+ result = np.matmul(pose.matrix(), np.array([*point, 1.0])[:, None]).transpose()[0][:3]
69
+
70
+ self.assert_array_almost_equal(expect, result, delta=1e-9)
9
71
 
10
72
 
11
73
  @ddt.ddt
@@ -10,6 +10,7 @@ from iker.common.utils.iterutils import last
10
10
  from iker.common.utils.jsonutils import JsonObject
11
11
 
12
12
  from plexus.common.utils.tagutils import MutableTagset, Tag, TagCache, Tagset
13
+ from plexus.common.utils.tagutils import populate_clip_ranges
13
14
  from plexus.common.utils.tagutils import predefined_tagsets, render_tagset_markdown_readme
14
15
  from plexus.common.utils.tagutils import tag_cache_file_path
15
16
 
@@ -497,3 +498,177 @@ class TagUtilsTest(unittest.TestCase):
497
498
  tag_records_count * 2 * (tags_count - tagset_tags_count) // tags_count)
498
499
  self.assertEqual(len(list(dst_target_cache.iter_tags(tagsets=[tagset], tagset_inverted=False))),
499
500
  tag_records_count * 2 * tagset_tags_count // tags_count)
501
+
502
+ data_populate_clip_ranges = [
503
+ (
504
+ dt_parse_iso("2023-01-01T00:00:00.000000+00:00"),
505
+ dt_parse_iso("2023-01-01T00:00:20.000000+00:00"),
506
+ [
507
+ (dt_parse_iso("2023-01-01T00:00:00.000000+00:00"),
508
+ dt_parse_iso("2023-01-01T00:00:20.000000+00:00"),
509
+ dt_parse_iso("2023-01-01T00:00:00.000000+00:00"),
510
+ dt_parse_iso("2023-01-01T00:00:20.000000+00:00")),
511
+ ],
512
+ ),
513
+ (
514
+ dt_parse_iso("2023-01-01T00:00:10.000000+00:00"),
515
+ dt_parse_iso("2023-01-01T00:00:50.000000+00:00"),
516
+ [
517
+ (dt_parse_iso("2023-01-01T00:00:00.000000+00:00"),
518
+ dt_parse_iso("2023-01-01T00:00:20.000000+00:00"),
519
+ dt_parse_iso("2023-01-01T00:00:10.000000+00:00"),
520
+ dt_parse_iso("2023-01-01T00:00:20.000000+00:00")),
521
+ (dt_parse_iso("2023-01-01T00:00:20.000000+00:00"),
522
+ dt_parse_iso("2023-01-01T00:00:40.000000+00:00"),
523
+ dt_parse_iso("2023-01-01T00:00:20.000000+00:00"),
524
+ dt_parse_iso("2023-01-01T00:00:40.000000+00:00")),
525
+ (dt_parse_iso("2023-01-01T00:00:40.000000+00:00"),
526
+ dt_parse_iso("2023-01-01T00:01:00.000000+00:00"),
527
+ dt_parse_iso("2023-01-01T00:00:40.000000+00:00"),
528
+ dt_parse_iso("2023-01-01T00:00:50.000000+00:00")),
529
+ ],
530
+ ),
531
+ (
532
+ dt_parse_iso("2023-01-01T00:00:10.000000+00:00"),
533
+ dt_parse_iso("2023-01-01T00:00:15.000000+00:00"),
534
+ [
535
+ (dt_parse_iso("2023-01-01T00:00:00.000000+00:00"),
536
+ dt_parse_iso("2023-01-01T00:00:20.000000+00:00"),
537
+ dt_parse_iso("2023-01-01T00:00:10.000000+00:00"),
538
+ dt_parse_iso("2023-01-01T00:00:15.000000+00:00")),
539
+ ],
540
+ ),
541
+ (
542
+ dt_parse_iso("2023-01-01T00:00:00.000000+00:00"),
543
+ dt_parse_iso("2023-01-01T00:00:30.000000+00:00"),
544
+ [
545
+ (dt_parse_iso("2023-01-01T00:00:00.000000+00:00"),
546
+ dt_parse_iso("2023-01-01T00:00:20.000000+00:00"),
547
+ dt_parse_iso("2023-01-01T00:00:00.000000+00:00"),
548
+ dt_parse_iso("2023-01-01T00:00:20.000000+00:00")),
549
+ (dt_parse_iso("2023-01-01T00:00:20.000000+00:00"),
550
+ dt_parse_iso("2023-01-01T00:00:40.000000+00:00"),
551
+ dt_parse_iso("2023-01-01T00:00:20.000000+00:00"),
552
+ dt_parse_iso("2023-01-01T00:00:30.000000+00:00")),
553
+ ],
554
+ ),
555
+ (
556
+ dt_parse_iso("2023-01-01T00:00:00.000000+00:00"),
557
+ dt_parse_iso("2023-01-01T00:00:00.000000+00:00"),
558
+ [
559
+ (dt_parse_iso("2023-01-01T00:00:00.000000+00:00"),
560
+ dt_parse_iso("2023-01-01T00:00:20.000000+00:00"),
561
+ dt_parse_iso("2023-01-01T00:00:00.000000+00:00"),
562
+ dt_parse_iso("2023-01-01T00:00:00.000000+00:00")),
563
+ ],
564
+ ),
565
+ (
566
+ dt_parse_iso("2023-01-01T00:00:05.000000+00:00"),
567
+ dt_parse_iso("2023-01-01T00:00:05.000000+00:00"),
568
+ [
569
+ (dt_parse_iso("2023-01-01T00:00:00.000000+00:00"),
570
+ dt_parse_iso("2023-01-01T00:00:20.000000+00:00"),
571
+ dt_parse_iso("2023-01-01T00:00:05.000000+00:00"),
572
+ dt_parse_iso("2023-01-01T00:00:05.000000+00:00")),
573
+ ],
574
+ )
575
+ ]
576
+
577
+ @ddt.idata(data_populate_clip_ranges)
578
+ @ddt.unpack
579
+ def test_populate_clip_ranges(self, data_begin_dt, data_end_dt, expected):
580
+ result = list(populate_clip_ranges(data_begin_dt, data_end_dt))
581
+ self.assertEqual(result, expected)
582
+
583
+ data_populate_clip_ranges__customized_duration = [
584
+ (
585
+ dt_parse_iso("2023-01-01T00:00:00.000000+00:00"),
586
+ dt_parse_iso("2023-01-01T00:00:20.000000+00:00"),
587
+ [
588
+ (dt_parse_iso("2023-01-01T00:00:00.000000+00:00"),
589
+ dt_parse_iso("2023-01-01T00:00:10.000000+00:00"),
590
+ dt_parse_iso("2023-01-01T00:00:00.000000+00:00"),
591
+ dt_parse_iso("2023-01-01T00:00:10.000000+00:00")),
592
+ (dt_parse_iso("2023-01-01T00:00:10.000000+00:00"),
593
+ dt_parse_iso("2023-01-01T00:00:20.000000+00:00"),
594
+ dt_parse_iso("2023-01-01T00:00:10.000000+00:00"),
595
+ dt_parse_iso("2023-01-01T00:00:20.000000+00:00")),
596
+ ],
597
+ ),
598
+ (
599
+ dt_parse_iso("2023-01-01T00:00:10.000000+00:00"),
600
+ dt_parse_iso("2023-01-01T00:00:50.000000+00:00"),
601
+ [
602
+ (dt_parse_iso("2023-01-01T00:00:10.000000+00:00"),
603
+ dt_parse_iso("2023-01-01T00:00:20.000000+00:00"),
604
+ dt_parse_iso("2023-01-01T00:00:10.000000+00:00"),
605
+ dt_parse_iso("2023-01-01T00:00:20.000000+00:00")),
606
+ (dt_parse_iso("2023-01-01T00:00:20.000000+00:00"),
607
+ dt_parse_iso("2023-01-01T00:00:30.000000+00:00"),
608
+ dt_parse_iso("2023-01-01T00:00:20.000000+00:00"),
609
+ dt_parse_iso("2023-01-01T00:00:30.000000+00:00")),
610
+ (dt_parse_iso("2023-01-01T00:00:30.000000+00:00"),
611
+ dt_parse_iso("2023-01-01T00:00:40.000000+00:00"),
612
+ dt_parse_iso("2023-01-01T00:00:30.000000+00:00"),
613
+ dt_parse_iso("2023-01-01T00:00:40.000000+00:00")),
614
+ (dt_parse_iso("2023-01-01T00:00:40.000000+00:00"),
615
+ dt_parse_iso("2023-01-01T00:00:50.000000+00:00"),
616
+ dt_parse_iso("2023-01-01T00:00:40.000000+00:00"),
617
+ dt_parse_iso("2023-01-01T00:00:50.000000+00:00")),
618
+ ],
619
+ ),
620
+ (
621
+ dt_parse_iso("2023-01-01T00:00:10.000000+00:00"),
622
+ dt_parse_iso("2023-01-01T00:00:15.000000+00:00"),
623
+ [
624
+ (dt_parse_iso("2023-01-01T00:00:10.000000+00:00"),
625
+ dt_parse_iso("2023-01-01T00:00:20.000000+00:00"),
626
+ dt_parse_iso("2023-01-01T00:00:10.000000+00:00"),
627
+ dt_parse_iso("2023-01-01T00:00:15.000000+00:00")),
628
+ ],
629
+ ),
630
+ (
631
+ dt_parse_iso("2023-01-01T00:00:00.000000+00:00"),
632
+ dt_parse_iso("2023-01-01T00:00:30.000000+00:00"),
633
+ [
634
+ (dt_parse_iso("2023-01-01T00:00:00.000000+00:00"),
635
+ dt_parse_iso("2023-01-01T00:00:10.000000+00:00"),
636
+ dt_parse_iso("2023-01-01T00:00:00.000000+00:00"),
637
+ dt_parse_iso("2023-01-01T00:00:10.000000+00:00")),
638
+ (dt_parse_iso("2023-01-01T00:00:10.000000+00:00"),
639
+ dt_parse_iso("2023-01-01T00:00:20.000000+00:00"),
640
+ dt_parse_iso("2023-01-01T00:00:10.000000+00:00"),
641
+ dt_parse_iso("2023-01-01T00:00:20.000000+00:00")),
642
+ (dt_parse_iso("2023-01-01T00:00:20.000000+00:00"),
643
+ dt_parse_iso("2023-01-01T00:00:30.000000+00:00"),
644
+ dt_parse_iso("2023-01-01T00:00:20.000000+00:00"),
645
+ dt_parse_iso("2023-01-01T00:00:30.000000+00:00")),
646
+ ],
647
+ ),
648
+ (
649
+ dt_parse_iso("2023-01-01T00:00:00.000000+00:00"),
650
+ dt_parse_iso("2023-01-01T00:00:00.000000+00:00"),
651
+ [
652
+ (dt_parse_iso("2023-01-01T00:00:00.000000+00:00"),
653
+ dt_parse_iso("2023-01-01T00:00:10.000000+00:00"),
654
+ dt_parse_iso("2023-01-01T00:00:00.000000+00:00"),
655
+ dt_parse_iso("2023-01-01T00:00:00.000000+00:00")),
656
+ ],
657
+ ),
658
+ (
659
+ dt_parse_iso("2023-01-01T00:00:05.000000+00:00"),
660
+ dt_parse_iso("2023-01-01T00:00:05.000000+00:00"),
661
+ [
662
+ (dt_parse_iso("2023-01-01T00:00:00.000000+00:00"),
663
+ dt_parse_iso("2023-01-01T00:00:10.000000+00:00"),
664
+ dt_parse_iso("2023-01-01T00:00:05.000000+00:00"),
665
+ dt_parse_iso("2023-01-01T00:00:05.000000+00:00")),
666
+ ],
667
+ )
668
+ ]
669
+
670
+ @ddt.idata(data_populate_clip_ranges__customized_duration)
671
+ @ddt.unpack
672
+ def test_populate_clip_ranges__customized_duration(self, data_begin_dt, data_end_dt, expected):
673
+ result = list(populate_clip_ranges(data_begin_dt, data_end_dt, clip_duration_us=10_000_000))
674
+ self.assertEqual(result, expected)
@@ -1,107 +0,0 @@
1
- from typing import Self
2
-
3
- import numpy as np
4
- import pyquaternion as pyquat
5
- from iker.common.utils.strutils import repr_data
6
-
7
-
8
- class Pose(object):
9
- @classmethod
10
- def from_numbers(
11
- cls,
12
- px: float,
13
- py: float,
14
- pz: float,
15
- qx: float,
16
- qy: float,
17
- qz: float,
18
- qw: float,
19
- ts: float = 0,
20
- ) -> Self:
21
- """
22
- Constructs a pose from numbers representing position and orientation
23
- """
24
- return Pose(ts, np.array([px, py, pz], dtype=np.float64), np.array([qw, qx, qy, qz], dtype=np.float64))
25
-
26
- @classmethod
27
- def add(cls, x: Self, d: Self) -> Self:
28
- """
29
- Performs pose SE3 addition, as x + d = y
30
- """
31
- xq = pyquat.Quaternion(x.q)
32
- dq = pyquat.Quaternion(d.q)
33
-
34
- yp = x.p + xq.rotate(d.p)
35
- yq = xq * dq
36
-
37
- return Pose(0, yp, yq.normalised.elements)
38
-
39
- @classmethod
40
- def sub(cls, y: Self, x: Self) -> Self:
41
- """
42
- Performs pose SE3 subtraction, as x + d = y => d = y - x
43
- """
44
- xq = pyquat.Quaternion(x.q)
45
- yq = pyquat.Quaternion(y.q)
46
-
47
- dp = xq.inverse.rotate(y.p - x.p)
48
- dq = xq.inverse * yq
49
-
50
- return Pose(0, dp, dq.normalised.elements)
51
-
52
- @classmethod
53
- def interpolate(cls, a: Self, b: Self, t: float) -> Self:
54
- """
55
- Interpolates between two given poses, as a * t + b * (1 - t)
56
-
57
- :return: interpolated pose
58
- """
59
- ts = a.ts + (b.ts - a.ts) * t
60
- p = a.p + (b.p - a.p) * t
61
- q = pyquat.Quaternion.slerp(pyquat.Quaternion(a.q), pyquat.Quaternion(b.q), t)
62
- return Pose(ts, p, q.normalised.elements)
63
-
64
- def __init__(self, ts: float, p: np.ndarray, q: np.ndarray):
65
- """
66
- Represents a pose
67
-
68
- :param ts: timestamp
69
- :param p: position vector
70
- :param q: orientation quaternion
71
- """
72
- self.ts = ts
73
- self.p = p
74
- self.q = q
75
-
76
- def matrix(self) -> np.ndarray:
77
- """
78
- Returns the transformation matrix of this pose
79
-
80
- :return: transformation matrix
81
- """
82
- r = pyquat.Quaternion(self.q).rotation_matrix
83
- t = self.p[:, None]
84
- return np.block([[r, t], [np.zeros((1, 3), dtype=np.float64), np.ones((1, 1), dtype=np.float64)]])
85
-
86
- def translate(self, v: np.ndarray) -> np.ndarray:
87
- """
88
- Translate the given vector with the pose position
89
-
90
- :param v: the vector to be translated
91
-
92
- :return: translated vector
93
- """
94
- return self.p + v
95
-
96
- def rotate(self, v: np.ndarray) -> np.ndarray:
97
- """
98
- Rotates the given vector with the pose orientation
99
-
100
- :param v: the vector to be rotated
101
-
102
- :return: rotated vector
103
- """
104
- return pyquat.Quaternion(self.q).rotate(v)
105
-
106
- def __str__(self):
107
- return repr_data(self)
@@ -1,66 +0,0 @@
1
- import math
2
- import unittest
3
-
4
- import ddt
5
- import numpy as np
6
- import pyquaternion as pyquat
7
- from iker.common.utils.randutils import randomizer
8
-
9
- from plexus.common.pose import Pose
10
-
11
-
12
- @ddt.ddt
13
- class PoseTest(unittest.TestCase):
14
-
15
- @staticmethod
16
- def random_pose() -> Pose:
17
- q = pyquat.Quaternion(axis=randomizer().random_unit_vector(3),
18
- angle=math.pi * randomizer().next_float(0.0, 0.5))
19
- return Pose(0, np.array(randomizer().random_unit_vector(3)), q.normalised.elements)
20
-
21
- def assert_array_almost_equal(self, xs: np.ndarray, ys: np.ndarray, delta: float):
22
- self.assertEqual(len(xs), len(ys))
23
- for x, y in zip(xs, ys):
24
- self.assertAlmostEqual(x, y, delta=delta)
25
-
26
- def test_builtin_init(self):
27
- for _ in range(0, 10000):
28
- expect = PoseTest.random_pose()
29
- actual = Pose(0, expect.p, expect.q)
30
-
31
- self.assert_array_almost_equal(expect.p, actual.p, delta=1e-9)
32
- self.assert_array_almost_equal(expect.q, actual.q, delta=1e-9)
33
-
34
- def test_add_sub(self):
35
- for _ in range(0, 10000):
36
- pose = PoseTest.random_pose()
37
- delta = PoseTest.random_pose()
38
-
39
- result = Pose.sub(Pose.add(pose, delta), pose)
40
-
41
- self.assert_array_almost_equal(delta.p, result.p, delta=1e-9)
42
- self.assert_array_almost_equal(delta.q, result.q, delta=1e-9)
43
-
44
- def test_interpolate(self):
45
- for _ in range(0, 10000):
46
- t = randomizer().next_float()
47
-
48
- pose1 = PoseTest.random_pose()
49
- pose2 = PoseTest.random_pose()
50
-
51
- inter1 = Pose.interpolate(pose1, pose2, t)
52
- inter2 = Pose.interpolate(pose2, pose1, 1.0 - t)
53
-
54
- self.assert_array_almost_equal(inter1.p, inter2.p, delta=1e-9)
55
- self.assert_array_almost_equal(inter1.q, inter2.q, delta=1e-9)
56
-
57
- def test_matrix(self):
58
- for _ in range(0, 10000):
59
- point = np.array(randomizer().random_unit_vector(3))
60
-
61
- pose = PoseTest.random_pose()
62
-
63
- expect = pose.translate(pose.rotate(point))
64
- result = np.matmul(pose.matrix(), np.array([*point, 1.0])[:, None]).transpose()[0][:3]
65
-
66
- self.assert_array_almost_equal(expect, result, delta=1e-9)