antioch-py 2.0.6__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 antioch-py might be problematic. Click here for more details.
- antioch/__init__.py +0 -0
- antioch/message.py +87 -0
- antioch/module/__init__.py +53 -0
- antioch/module/clock.py +62 -0
- antioch/module/execution.py +278 -0
- antioch/module/input.py +127 -0
- antioch/module/module.py +218 -0
- antioch/module/node.py +357 -0
- antioch/module/token.py +42 -0
- antioch/session/__init__.py +150 -0
- antioch/session/ark.py +504 -0
- antioch/session/asset.py +65 -0
- antioch/session/error.py +80 -0
- antioch/session/record.py +158 -0
- antioch/session/scene.py +1521 -0
- antioch/session/session.py +220 -0
- antioch/session/task.py +323 -0
- antioch/session/views/__init__.py +40 -0
- antioch/session/views/animation.py +189 -0
- antioch/session/views/articulation.py +245 -0
- antioch/session/views/basis_curve.py +186 -0
- antioch/session/views/camera.py +92 -0
- antioch/session/views/collision.py +75 -0
- antioch/session/views/geometry.py +74 -0
- antioch/session/views/ground_plane.py +63 -0
- antioch/session/views/imu.py +73 -0
- antioch/session/views/joint.py +64 -0
- antioch/session/views/light.py +175 -0
- antioch/session/views/pir_sensor.py +140 -0
- antioch/session/views/radar.py +73 -0
- antioch/session/views/rigid_body.py +282 -0
- antioch/session/views/xform.py +119 -0
- antioch_py-2.0.6.dist-info/METADATA +115 -0
- antioch_py-2.0.6.dist-info/RECORD +99 -0
- antioch_py-2.0.6.dist-info/WHEEL +5 -0
- antioch_py-2.0.6.dist-info/entry_points.txt +2 -0
- antioch_py-2.0.6.dist-info/top_level.txt +2 -0
- common/__init__.py +0 -0
- common/ark/__init__.py +60 -0
- common/ark/ark.py +128 -0
- common/ark/hardware.py +121 -0
- common/ark/kinematics.py +31 -0
- common/ark/module.py +85 -0
- common/ark/node.py +94 -0
- common/ark/scheduler.py +439 -0
- common/ark/sim.py +33 -0
- common/assets/__init__.py +3 -0
- common/constants.py +47 -0
- common/core/__init__.py +52 -0
- common/core/agent.py +296 -0
- common/core/auth.py +305 -0
- common/core/registry.py +331 -0
- common/core/task.py +36 -0
- common/message/__init__.py +59 -0
- common/message/annotation.py +89 -0
- common/message/array.py +500 -0
- common/message/base.py +517 -0
- common/message/camera.py +91 -0
- common/message/color.py +139 -0
- common/message/frame.py +50 -0
- common/message/image.py +171 -0
- common/message/imu.py +14 -0
- common/message/joint.py +47 -0
- common/message/log.py +31 -0
- common/message/pir.py +16 -0
- common/message/point.py +109 -0
- common/message/point_cloud.py +63 -0
- common/message/pose.py +148 -0
- common/message/quaternion.py +273 -0
- common/message/radar.py +58 -0
- common/message/types.py +37 -0
- common/message/vector.py +786 -0
- common/rome/__init__.py +9 -0
- common/rome/client.py +430 -0
- common/rome/error.py +16 -0
- common/session/__init__.py +54 -0
- common/session/environment.py +31 -0
- common/session/sim.py +240 -0
- common/session/views/__init__.py +263 -0
- common/session/views/animation.py +73 -0
- common/session/views/articulation.py +184 -0
- common/session/views/basis_curve.py +102 -0
- common/session/views/camera.py +147 -0
- common/session/views/collision.py +59 -0
- common/session/views/geometry.py +102 -0
- common/session/views/ground_plane.py +41 -0
- common/session/views/imu.py +66 -0
- common/session/views/joint.py +81 -0
- common/session/views/light.py +96 -0
- common/session/views/pir_sensor.py +115 -0
- common/session/views/radar.py +82 -0
- common/session/views/rigid_body.py +236 -0
- common/session/views/viewport.py +21 -0
- common/session/views/xform.py +39 -0
- common/utils/__init__.py +4 -0
- common/utils/comms.py +571 -0
- common/utils/logger.py +123 -0
- common/utils/time.py +42 -0
- common/utils/usd.py +12 -0
common/message/pose.py
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
from pydantic import Field, field_validator, model_validator
|
|
7
|
+
|
|
8
|
+
from common.message.base import Message
|
|
9
|
+
from common.message.vector import Vector3
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Pose(Message):
|
|
13
|
+
"""
|
|
14
|
+
A pose representing position and orientation in 3D space.
|
|
15
|
+
|
|
16
|
+
Serializes position as [x, y, z] and orientation as [roll, pitch, yaw] in radians.
|
|
17
|
+
Orientation is stored as RPY (Roll-Pitch-Yaw) Euler angles.
|
|
18
|
+
|
|
19
|
+
Automatically converts from common Python types:
|
|
20
|
+
- Lists/tuples: Pose(position=[1.0, 2.0, 3.0], orientation=[0.0, 0.0, 1.57])
|
|
21
|
+
- Dicts: Pose(position={"x": 1.0, "y": 2.0, "z": 3.0}, orientation={"x": 0.0, "y": 0.0, "z": 1.57})
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
_type = "antioch/pose"
|
|
25
|
+
position: Vector3 = Field(default_factory=lambda: Vector3.zeros())
|
|
26
|
+
orientation: Vector3 = Field(default_factory=lambda: Vector3.zeros())
|
|
27
|
+
|
|
28
|
+
def __add__(self, other: Vector3) -> Pose:
|
|
29
|
+
"""
|
|
30
|
+
Translate the pose by a vector (adds to position).
|
|
31
|
+
|
|
32
|
+
:param other: A Vector3 representing the translation.
|
|
33
|
+
:return: The translated pose.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
return Pose(position=self.position + other, orientation=self.orientation)
|
|
37
|
+
|
|
38
|
+
def __sub__(self, other: Vector3) -> Pose:
|
|
39
|
+
"""
|
|
40
|
+
Translate the pose by a negative vector (subtracts from position).
|
|
41
|
+
|
|
42
|
+
:param other: A Vector3 representing the translation.
|
|
43
|
+
:return: The translated pose.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
return Pose(position=self.position - other, orientation=self.orientation)
|
|
47
|
+
|
|
48
|
+
@model_validator(mode="before")
|
|
49
|
+
@classmethod
|
|
50
|
+
def convert_nested_types(cls, data: Any) -> Any:
|
|
51
|
+
"""
|
|
52
|
+
Allow passing position and orientation as lists/tuples that auto-convert to Vector3/Quaternion.
|
|
53
|
+
Orientation can be 3 values (RPY) or 4 values (quaternion) - always stored as quaternion.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
# Already a Pose instance
|
|
57
|
+
if isinstance(data, cls):
|
|
58
|
+
return data.model_dump()
|
|
59
|
+
|
|
60
|
+
# Dict format - let Pydantic and nested validators handle it
|
|
61
|
+
if isinstance(data, dict):
|
|
62
|
+
return data
|
|
63
|
+
|
|
64
|
+
return data
|
|
65
|
+
|
|
66
|
+
@field_validator("position", mode="before")
|
|
67
|
+
@classmethod
|
|
68
|
+
def validate_position(cls, v: Any) -> Any:
|
|
69
|
+
"""
|
|
70
|
+
Convert position from list/tuple/array to Vector3 format before type validation.
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
if isinstance(v, (list, tuple, np.ndarray)):
|
|
74
|
+
return Vector3.convert_iterables.__func__(Vector3, v)
|
|
75
|
+
return v
|
|
76
|
+
|
|
77
|
+
@field_validator("orientation", mode="before")
|
|
78
|
+
@classmethod
|
|
79
|
+
def validate_orientation(cls, v: Any) -> Any:
|
|
80
|
+
"""
|
|
81
|
+
Convert orientation from list/tuple/array to Vector3 format before type validation.
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
if isinstance(v, (list, tuple, np.ndarray)):
|
|
85
|
+
return Vector3.convert_iterables.__func__(Vector3, v)
|
|
86
|
+
return v
|
|
87
|
+
|
|
88
|
+
@classmethod
|
|
89
|
+
def from_any(cls, data: Any) -> Pose:
|
|
90
|
+
"""
|
|
91
|
+
Create Pose from a dict or Pose instance.
|
|
92
|
+
|
|
93
|
+
:param data: Dict with position/orientation fields, or a Pose instance.
|
|
94
|
+
:return: A Pose instance.
|
|
95
|
+
:raises ValueError: If conversion fails.
|
|
96
|
+
"""
|
|
97
|
+
|
|
98
|
+
# Already a Pose - return as-is
|
|
99
|
+
if isinstance(data, cls):
|
|
100
|
+
return data
|
|
101
|
+
|
|
102
|
+
try:
|
|
103
|
+
if isinstance(data, dict):
|
|
104
|
+
return cls(**data)
|
|
105
|
+
raise ValueError("Pose requires a dict with position and orientation fields")
|
|
106
|
+
except Exception as e:
|
|
107
|
+
raise ValueError(f"Cannot convert to Pose: {e}") from None
|
|
108
|
+
|
|
109
|
+
@classmethod
|
|
110
|
+
def identity(cls) -> Pose:
|
|
111
|
+
"""
|
|
112
|
+
Create an identity pose at origin with zero rotation.
|
|
113
|
+
|
|
114
|
+
:return: Identity pose.
|
|
115
|
+
"""
|
|
116
|
+
|
|
117
|
+
return cls(position=Vector3.zeros(), orientation=Vector3.zeros())
|
|
118
|
+
|
|
119
|
+
@classmethod
|
|
120
|
+
def from_position(cls, position: Vector3) -> Pose:
|
|
121
|
+
"""
|
|
122
|
+
Create a pose from a position with zero rotation.
|
|
123
|
+
|
|
124
|
+
:param position: The position.
|
|
125
|
+
:return: The pose.
|
|
126
|
+
"""
|
|
127
|
+
|
|
128
|
+
return cls(position=position, orientation=Vector3.zeros())
|
|
129
|
+
|
|
130
|
+
def translate(self, offset: Vector3) -> Pose:
|
|
131
|
+
"""
|
|
132
|
+
Create a new pose with the position translated by an offset.
|
|
133
|
+
|
|
134
|
+
:param offset: The translation offset.
|
|
135
|
+
:return: The translated pose.
|
|
136
|
+
"""
|
|
137
|
+
|
|
138
|
+
return Pose(position=self.position + offset, orientation=self.orientation)
|
|
139
|
+
|
|
140
|
+
def rotate(self, rotation: Vector3) -> Pose:
|
|
141
|
+
"""
|
|
142
|
+
Create a new pose with additional rotation (in RPY).
|
|
143
|
+
|
|
144
|
+
:param rotation: Additional rotation in Roll-Pitch-Yaw.
|
|
145
|
+
:return: The rotated pose.
|
|
146
|
+
"""
|
|
147
|
+
|
|
148
|
+
return Pose(position=self.position, orientation=self.orientation + rotation)
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
from scipy.spatial.transform import Rotation
|
|
5
|
+
|
|
6
|
+
from common.message.base import Message
|
|
7
|
+
|
|
8
|
+
try:
|
|
9
|
+
from pxr import Gf # type: ignore
|
|
10
|
+
|
|
11
|
+
HAS_PXR = True
|
|
12
|
+
except ImportError:
|
|
13
|
+
HAS_PXR = False
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Quaternion(Message):
|
|
17
|
+
"""
|
|
18
|
+
A quaternion for 3D rotations.
|
|
19
|
+
|
|
20
|
+
Serializes as a tuple [w, x, y, z] for space efficiency where w is the scalar component.
|
|
21
|
+
|
|
22
|
+
Examples:
|
|
23
|
+
Quaternion.new(1.0, 0.0, 0.0, 0.0) # Create identity quaternion
|
|
24
|
+
Quaternion.from_rpy(0.0, 0.0, 1.57) # Create from RPY angles
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
_type = "antioch/quaternion"
|
|
28
|
+
data: tuple[float, float, float, float]
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def w(self) -> float:
|
|
32
|
+
"""
|
|
33
|
+
Get the w (scalar) component.
|
|
34
|
+
|
|
35
|
+
:return: The w (scalar) component.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
return self.data[0]
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def x(self) -> float:
|
|
42
|
+
"""
|
|
43
|
+
Get the x component.
|
|
44
|
+
|
|
45
|
+
:return: The x component.
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
return self.data[1]
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def y(self) -> float:
|
|
52
|
+
"""
|
|
53
|
+
Get the y component.
|
|
54
|
+
|
|
55
|
+
:return: The y component.
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
return self.data[2]
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def z(self) -> float:
|
|
62
|
+
"""
|
|
63
|
+
Get the z component.
|
|
64
|
+
|
|
65
|
+
:return: The z component.
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
return self.data[3]
|
|
69
|
+
|
|
70
|
+
def __len__(self) -> int:
|
|
71
|
+
"""
|
|
72
|
+
Get the length of the quaternion (always 4).
|
|
73
|
+
|
|
74
|
+
:return: The length of the quaternion.
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
return 4
|
|
78
|
+
|
|
79
|
+
def __iter__(self):
|
|
80
|
+
"""
|
|
81
|
+
Iterate over quaternion components.
|
|
82
|
+
|
|
83
|
+
:return: Iterator over [w, x, y, z].
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
return iter(self.data)
|
|
87
|
+
|
|
88
|
+
def __getitem__(self, index: int) -> float:
|
|
89
|
+
"""
|
|
90
|
+
Get quaternion component by index.
|
|
91
|
+
|
|
92
|
+
:param index: Index (0=w, 1=x, 2=y, 3=z).
|
|
93
|
+
:return: The component value.
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
return self.data[index]
|
|
97
|
+
|
|
98
|
+
def __eq__(self, other: object) -> bool:
|
|
99
|
+
"""
|
|
100
|
+
Check equality with another Quaternion.
|
|
101
|
+
|
|
102
|
+
:param other: Another Quaternion.
|
|
103
|
+
:return: True if equal.
|
|
104
|
+
"""
|
|
105
|
+
|
|
106
|
+
if not isinstance(other, Quaternion):
|
|
107
|
+
return False
|
|
108
|
+
return self.data == other.data
|
|
109
|
+
|
|
110
|
+
def __repr__(self) -> str:
|
|
111
|
+
"""
|
|
112
|
+
Return a readable string representation.
|
|
113
|
+
|
|
114
|
+
:return: String representation.
|
|
115
|
+
"""
|
|
116
|
+
|
|
117
|
+
return f"Quaternion(w={self.w}, x={self.x}, y={self.y}, z={self.z})"
|
|
118
|
+
|
|
119
|
+
def __str__(self) -> str:
|
|
120
|
+
"""
|
|
121
|
+
Return a readable string representation.
|
|
122
|
+
|
|
123
|
+
:return: String representation.
|
|
124
|
+
"""
|
|
125
|
+
|
|
126
|
+
return f"Quaternion(w={self.w}, x={self.x}, y={self.y}, z={self.z})"
|
|
127
|
+
|
|
128
|
+
@classmethod
|
|
129
|
+
def new(cls, w: float, x: float, y: float, z: float) -> Quaternion:
|
|
130
|
+
"""
|
|
131
|
+
Create a new quaternion.
|
|
132
|
+
|
|
133
|
+
:param w: The w component.
|
|
134
|
+
:param x: The x component.
|
|
135
|
+
:param y: The y component.
|
|
136
|
+
:param z: The z component.
|
|
137
|
+
:return: A quaternion.
|
|
138
|
+
"""
|
|
139
|
+
|
|
140
|
+
return cls(data=(w, x, y, z))
|
|
141
|
+
|
|
142
|
+
@classmethod
|
|
143
|
+
def identity(cls) -> Quaternion:
|
|
144
|
+
"""
|
|
145
|
+
Create an identity quaternion (no rotation).
|
|
146
|
+
|
|
147
|
+
:return: An identity quaternion [1, 0, 0, 0].
|
|
148
|
+
"""
|
|
149
|
+
|
|
150
|
+
return cls(data=(1.0, 0.0, 0.0, 0.0))
|
|
151
|
+
|
|
152
|
+
@classmethod
|
|
153
|
+
def from_numpy(cls, array: np.ndarray) -> Quaternion:
|
|
154
|
+
"""
|
|
155
|
+
Create a quaternion from a numpy array [w, x, y, z].
|
|
156
|
+
|
|
157
|
+
:param array: The numpy array to create the quaternion from (must have 4 elements).
|
|
158
|
+
:return: A quaternion.
|
|
159
|
+
:raises ValueError: If the array does not have exactly 4 elements.
|
|
160
|
+
"""
|
|
161
|
+
|
|
162
|
+
if array.shape != (4,):
|
|
163
|
+
raise ValueError(f"Quaternion array must have shape (4,), got {array.shape}")
|
|
164
|
+
return cls(data=(float(array[0]), float(array[1]), float(array[2]), float(array[3])))
|
|
165
|
+
|
|
166
|
+
@classmethod
|
|
167
|
+
def from_rpy(cls, roll: float, pitch: float, yaw: float) -> Quaternion:
|
|
168
|
+
"""
|
|
169
|
+
Create a quaternion from roll-pitch-yaw angles (in radians).
|
|
170
|
+
|
|
171
|
+
:param roll: Roll angle in radians (rotation around x-axis).
|
|
172
|
+
:param pitch: Pitch angle in radians (rotation around y-axis).
|
|
173
|
+
:param yaw: Yaw angle in radians (rotation around z-axis).
|
|
174
|
+
:return: A quaternion.
|
|
175
|
+
"""
|
|
176
|
+
|
|
177
|
+
rotation = Rotation.from_euler("xyz", [roll, pitch, yaw])
|
|
178
|
+
quat_xyzw = rotation.as_quat()
|
|
179
|
+
return cls(
|
|
180
|
+
data=(
|
|
181
|
+
float(quat_xyzw[3]),
|
|
182
|
+
float(quat_xyzw[0]),
|
|
183
|
+
float(quat_xyzw[1]),
|
|
184
|
+
float(quat_xyzw[2]),
|
|
185
|
+
)
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
def to_numpy(self) -> np.ndarray:
|
|
189
|
+
"""
|
|
190
|
+
Convert the quaternion to a numpy array.
|
|
191
|
+
|
|
192
|
+
:return: The numpy array [w, x, y, z].
|
|
193
|
+
"""
|
|
194
|
+
|
|
195
|
+
return np.array(self.data, dtype=np.float32)
|
|
196
|
+
|
|
197
|
+
def to_list(self) -> list[float]:
|
|
198
|
+
"""
|
|
199
|
+
Convert the quaternion to a list.
|
|
200
|
+
|
|
201
|
+
:return: The list [w, x, y, z].
|
|
202
|
+
"""
|
|
203
|
+
|
|
204
|
+
return list(self.data)
|
|
205
|
+
|
|
206
|
+
def to_gf_quatf(self) -> Gf.Quatf:
|
|
207
|
+
"""
|
|
208
|
+
Convert the quaternion to a Gf.Quatf.
|
|
209
|
+
|
|
210
|
+
Note: Gf.Quatf expects (w, x, y, z) order.
|
|
211
|
+
|
|
212
|
+
:return: The Gf.Quatf.
|
|
213
|
+
:raises ImportError: If pxr is not installed.
|
|
214
|
+
"""
|
|
215
|
+
|
|
216
|
+
if not HAS_PXR:
|
|
217
|
+
raise ImportError("pxr is not installed")
|
|
218
|
+
return Gf.Quatf(self.w, self.x, self.y, self.z)
|
|
219
|
+
|
|
220
|
+
def to_gf_quatd(self) -> Gf.Quatd:
|
|
221
|
+
"""
|
|
222
|
+
Convert the quaternion to a Gf.Quatd (double precision).
|
|
223
|
+
|
|
224
|
+
Note: Gf.Quatd expects (w, x, y, z) order.
|
|
225
|
+
|
|
226
|
+
:return: The Gf.Quatd.
|
|
227
|
+
:raises ImportError: If pxr is not installed.
|
|
228
|
+
"""
|
|
229
|
+
|
|
230
|
+
if not HAS_PXR:
|
|
231
|
+
raise ImportError("pxr is not installed")
|
|
232
|
+
return Gf.Quatd(self.w, self.x, self.y, self.z)
|
|
233
|
+
|
|
234
|
+
def normalize(self) -> Quaternion:
|
|
235
|
+
"""
|
|
236
|
+
Return a normalized version of this quaternion.
|
|
237
|
+
|
|
238
|
+
:return: A normalized quaternion.
|
|
239
|
+
"""
|
|
240
|
+
|
|
241
|
+
arr = self.to_numpy()
|
|
242
|
+
norm = np.linalg.norm(arr)
|
|
243
|
+
if norm > 0:
|
|
244
|
+
normalized = arr / norm
|
|
245
|
+
return self.__class__(
|
|
246
|
+
data=(
|
|
247
|
+
float(normalized[0]),
|
|
248
|
+
float(normalized[1]),
|
|
249
|
+
float(normalized[2]),
|
|
250
|
+
float(normalized[3]),
|
|
251
|
+
)
|
|
252
|
+
)
|
|
253
|
+
return self.identity()
|
|
254
|
+
|
|
255
|
+
def to_rpy(self) -> tuple[float, float, float]:
|
|
256
|
+
"""
|
|
257
|
+
Convert the quaternion to roll-pitch-yaw angles (in radians).
|
|
258
|
+
|
|
259
|
+
:return: A tuple of (roll, pitch, yaw) angles in radians.
|
|
260
|
+
"""
|
|
261
|
+
|
|
262
|
+
rotation = Rotation.from_quat([self.x, self.y, self.z, self.w])
|
|
263
|
+
rpy = rotation.as_euler("xyz")
|
|
264
|
+
return (float(rpy[0]), float(rpy[1]), float(rpy[2]))
|
|
265
|
+
|
|
266
|
+
def as_array(self) -> list[float]:
|
|
267
|
+
"""
|
|
268
|
+
Convert to a list (alias for to_list for Rust compatibility).
|
|
269
|
+
|
|
270
|
+
:return: The list [w, x, y, z].
|
|
271
|
+
"""
|
|
272
|
+
|
|
273
|
+
return self.to_list()
|
common/message/radar.py
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from pydantic import Field
|
|
2
|
+
|
|
3
|
+
from common.message.base import Message
|
|
4
|
+
from common.message.point_cloud import PointCloud
|
|
5
|
+
from common.message.vector import Vector3
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class RadarDetection(Message):
|
|
9
|
+
"""
|
|
10
|
+
Single radar detection point with position and properties.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
_type = "antioch/radar_detection"
|
|
14
|
+
position: Vector3 = Field(description="3D position of detection in sensor frame")
|
|
15
|
+
range: float = Field(description="Range to target in meters")
|
|
16
|
+
azimuth: float = Field(description="Azimuth angle in radians")
|
|
17
|
+
elevation: float = Field(description="Elevation angle in radians")
|
|
18
|
+
velocity: float = Field(default=0.0, description="Radial velocity in m/s (positive = moving away)")
|
|
19
|
+
rcs: float = Field(default=0.0, description="Radar cross section in dBsm")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class RadarScan(Message):
|
|
23
|
+
"""
|
|
24
|
+
Radar scan data containing all detections from a single scan.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
_type = "antioch/radar_scan"
|
|
28
|
+
detections: list[RadarDetection] = Field(default_factory=list, description="List of radar detections")
|
|
29
|
+
|
|
30
|
+
def to_point_cloud(self, frame_id: str = "radar") -> PointCloud:
|
|
31
|
+
"""
|
|
32
|
+
Convert radar scan to a point cloud.
|
|
33
|
+
|
|
34
|
+
:param frame_id: Frame of reference for the point cloud.
|
|
35
|
+
:return: PointCloud with detection positions.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
if not self.detections:
|
|
39
|
+
return PointCloud(frame_id=frame_id, x=[], y=[], z=[])
|
|
40
|
+
|
|
41
|
+
return PointCloud(
|
|
42
|
+
frame_id=frame_id,
|
|
43
|
+
x=[d.position.x for d in self.detections],
|
|
44
|
+
y=[d.position.y for d in self.detections],
|
|
45
|
+
z=[d.position.z for d in self.detections],
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
@staticmethod
|
|
49
|
+
def combine(scans: list["RadarScan"]) -> "RadarScan":
|
|
50
|
+
"""
|
|
51
|
+
Combine multiple radar scans into a single scan.
|
|
52
|
+
|
|
53
|
+
:param scans: List of radar scans to combine.
|
|
54
|
+
:return: Combined radar scan with all detections.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
detections = sum((scan.detections for scan in scans), [])
|
|
58
|
+
return RadarScan(detections=detections)
|
common/message/types.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from common.message.base import Message
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Bool(Message):
|
|
5
|
+
"""
|
|
6
|
+
Boolean value message.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
_type = "antioch/bool"
|
|
10
|
+
value: bool
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Int(Message):
|
|
14
|
+
"""
|
|
15
|
+
Integer value message.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
_type = "antioch/int"
|
|
19
|
+
value: int
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Float(Message):
|
|
23
|
+
"""
|
|
24
|
+
Float value message.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
_type = "antioch/float"
|
|
28
|
+
value: float
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class String(Message):
|
|
32
|
+
"""
|
|
33
|
+
String value message.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
_type = "antioch/string"
|
|
37
|
+
value: str
|