antioch-py 2.0.6__py3-none-any.whl → 3.0.12__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 +101 -0
- antioch/{module/execution.py → execution.py} +1 -1
- antioch/{module/input.py → input.py} +2 -4
- antioch/{module/module.py → module.py} +17 -34
- antioch/{module/node.py → node.py} +17 -16
- {antioch_py-2.0.6.dist-info → antioch_py-3.0.12.dist-info}/METADATA +8 -11
- antioch_py-3.0.12.dist-info/RECORD +61 -0
- {antioch_py-2.0.6.dist-info → antioch_py-3.0.12.dist-info}/WHEEL +1 -1
- antioch_py-3.0.12.dist-info/licenses/LICENSE +21 -0
- common/ark/__init__.py +6 -16
- common/ark/ark.py +23 -60
- common/ark/hardware.py +13 -37
- common/ark/kinematics.py +1 -1
- common/ark/module.py +22 -0
- common/ark/node.py +46 -3
- common/ark/scheduler.py +2 -29
- common/ark/sim.py +1 -1
- {antioch/module → common/ark}/token.py +17 -0
- common/assets/rigging.usd +0 -0
- common/constants.py +83 -4
- common/core/__init__.py +37 -24
- common/core/auth.py +87 -114
- common/core/container.py +261 -0
- common/core/registry.py +131 -152
- common/core/rome.py +251 -0
- common/core/telemetry.py +176 -0
- common/core/types.py +219 -0
- common/message/__init__.py +19 -3
- common/message/annotation.py +174 -23
- common/message/array.py +25 -1
- common/message/camera.py +23 -1
- common/message/color.py +32 -6
- common/message/detection.py +40 -0
- common/message/foxglove.py +20 -0
- common/message/frame.py +71 -7
- common/message/image.py +58 -9
- common/message/imu.py +24 -4
- common/message/joint.py +69 -10
- common/message/log.py +52 -7
- common/message/pir.py +22 -5
- common/message/plot.py +57 -0
- common/message/point.py +55 -6
- common/message/point_cloud.py +55 -19
- common/message/pose.py +59 -19
- common/message/quaternion.py +105 -92
- common/message/radar.py +195 -29
- common/message/twist.py +34 -0
- common/message/types.py +40 -5
- common/message/vector.py +180 -245
- common/sim/__init__.py +49 -0
- common/sim/objects.py +460 -0
- common/sim/state.py +11 -0
- common/utils/comms.py +30 -12
- common/utils/logger.py +26 -7
- antioch/message.py +0 -87
- antioch/module/__init__.py +0 -53
- antioch/session/__init__.py +0 -150
- antioch/session/ark.py +0 -504
- antioch/session/asset.py +0 -65
- antioch/session/error.py +0 -80
- antioch/session/record.py +0 -158
- antioch/session/scene.py +0 -1521
- antioch/session/session.py +0 -220
- antioch/session/task.py +0 -323
- antioch/session/views/__init__.py +0 -40
- antioch/session/views/animation.py +0 -189
- antioch/session/views/articulation.py +0 -245
- antioch/session/views/basis_curve.py +0 -186
- antioch/session/views/camera.py +0 -92
- antioch/session/views/collision.py +0 -75
- antioch/session/views/geometry.py +0 -74
- antioch/session/views/ground_plane.py +0 -63
- antioch/session/views/imu.py +0 -73
- antioch/session/views/joint.py +0 -64
- antioch/session/views/light.py +0 -175
- antioch/session/views/pir_sensor.py +0 -140
- antioch/session/views/radar.py +0 -73
- antioch/session/views/rigid_body.py +0 -282
- antioch/session/views/xform.py +0 -119
- antioch_py-2.0.6.dist-info/RECORD +0 -99
- antioch_py-2.0.6.dist-info/entry_points.txt +0 -2
- common/core/agent.py +0 -296
- common/core/task.py +0 -36
- common/rome/__init__.py +0 -9
- common/rome/client.py +0 -430
- common/rome/error.py +0 -16
- common/session/__init__.py +0 -54
- common/session/environment.py +0 -31
- common/session/sim.py +0 -240
- common/session/views/__init__.py +0 -263
- common/session/views/animation.py +0 -73
- common/session/views/articulation.py +0 -184
- common/session/views/basis_curve.py +0 -102
- common/session/views/camera.py +0 -147
- common/session/views/collision.py +0 -59
- common/session/views/geometry.py +0 -102
- common/session/views/ground_plane.py +0 -41
- common/session/views/imu.py +0 -66
- common/session/views/joint.py +0 -81
- common/session/views/light.py +0 -96
- common/session/views/pir_sensor.py +0 -115
- common/session/views/radar.py +0 -82
- common/session/views/rigid_body.py +0 -236
- common/session/views/viewport.py +0 -21
- common/session/views/xform.py +0 -39
- common/utils/usd.py +0 -12
- /antioch/{module/clock.py → clock.py} +0 -0
- {antioch_py-2.0.6.dist-info → antioch_py-3.0.12.dist-info}/top_level.txt +0 -0
- /common/message/{base.py → message.py} +0 -0
common/message/joint.py
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
from
|
|
1
|
+
from pydantic import Field
|
|
2
|
+
|
|
3
|
+
from common.message.message import Message
|
|
2
4
|
|
|
3
5
|
|
|
4
6
|
class JointState(Message):
|
|
@@ -7,12 +9,27 @@ class JointState(Message):
|
|
|
7
9
|
|
|
8
10
|
Represents the complete physical state of a joint including its position,
|
|
9
11
|
velocity, and measured effort (force/torque).
|
|
12
|
+
|
|
13
|
+
Example:
|
|
14
|
+
```python
|
|
15
|
+
from common.message import JointState
|
|
16
|
+
|
|
17
|
+
# Create a joint state
|
|
18
|
+
state = JointState(
|
|
19
|
+
name="shoulder_pan",
|
|
20
|
+
position=1.57,
|
|
21
|
+
velocity=0.1,
|
|
22
|
+
effort=5.0,
|
|
23
|
+
)
|
|
24
|
+
print(f"{state.name}: pos={state.position:.2f} rad")
|
|
25
|
+
```
|
|
10
26
|
"""
|
|
11
27
|
|
|
12
28
|
_type = "antioch/joint_state"
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
29
|
+
name: str = Field(description="Name of the joint")
|
|
30
|
+
position: float = Field(description="Joint position in radians")
|
|
31
|
+
velocity: float = Field(description="Joint velocity in radians per second")
|
|
32
|
+
effort: float = Field(description="Joint effort (force/torque) in Nm")
|
|
16
33
|
|
|
17
34
|
|
|
18
35
|
class JointTarget(Message):
|
|
@@ -20,28 +37,70 @@ class JointTarget(Message):
|
|
|
20
37
|
Control target for a single joint.
|
|
21
38
|
|
|
22
39
|
Specifies desired position, velocity, and/or effort targets for a joint's
|
|
23
|
-
PD controller.
|
|
40
|
+
PD controller. The name field is required to identify which joint is being
|
|
41
|
+
targeted. Position, velocity, and effort are optional - omitted values are
|
|
42
|
+
not controlled.
|
|
43
|
+
|
|
44
|
+
Example:
|
|
45
|
+
```python
|
|
46
|
+
from common.message import JointTarget
|
|
47
|
+
|
|
48
|
+
# Position control target
|
|
49
|
+
target = JointTarget(name="elbow", position=0.5)
|
|
50
|
+
|
|
51
|
+
# Velocity control target
|
|
52
|
+
vel_target = JointTarget(name="wrist", velocity=0.2)
|
|
53
|
+
|
|
54
|
+
# Combined position and velocity target
|
|
55
|
+
combined = JointTarget(name="shoulder", position=1.0, velocity=0.1)
|
|
56
|
+
```
|
|
24
57
|
"""
|
|
25
58
|
|
|
26
59
|
_type = "antioch/joint_target"
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
60
|
+
name: str = Field(description="Name of the joint to control")
|
|
61
|
+
position: float | None = Field(default=None, description="Target position in radians")
|
|
62
|
+
velocity: float | None = Field(default=None, description="Target velocity in radians per second")
|
|
63
|
+
effort: float | None = Field(default=None, description="Target effort (force/torque) in Nm")
|
|
30
64
|
|
|
31
65
|
|
|
32
66
|
class JointStates(Message):
|
|
33
67
|
"""
|
|
34
68
|
Collection of joint states for an actuator group.
|
|
69
|
+
|
|
70
|
+
Example:
|
|
71
|
+
```python
|
|
72
|
+
from common.message import JointStates, JointState
|
|
73
|
+
|
|
74
|
+
# Create states for a robot arm
|
|
75
|
+
states = JointStates(states=[
|
|
76
|
+
JointState(name="joint1", position=0.0, velocity=0.0, effort=0.0),
|
|
77
|
+
JointState(name="joint2", position=1.57, velocity=0.1, effort=2.5),
|
|
78
|
+
])
|
|
79
|
+
|
|
80
|
+
for state in states.states:
|
|
81
|
+
print(f"{state.name}: {state.position:.2f} rad")
|
|
82
|
+
```
|
|
35
83
|
"""
|
|
36
84
|
|
|
37
85
|
_type = "antioch/joint_states"
|
|
38
|
-
states: list[JointState]
|
|
86
|
+
states: list[JointState] = Field(description="List of joint states")
|
|
39
87
|
|
|
40
88
|
|
|
41
89
|
class JointTargets(Message):
|
|
42
90
|
"""
|
|
43
91
|
Collection of joint targets for an actuator group.
|
|
92
|
+
|
|
93
|
+
Example:
|
|
94
|
+
```python
|
|
95
|
+
from common.message import JointTargets, JointTarget
|
|
96
|
+
|
|
97
|
+
# Create targets for multiple joints
|
|
98
|
+
targets = JointTargets(targets=[
|
|
99
|
+
JointTarget(name="joint1", position=0.5),
|
|
100
|
+
JointTarget(name="joint2", position=1.0),
|
|
101
|
+
])
|
|
102
|
+
```
|
|
44
103
|
"""
|
|
45
104
|
|
|
46
105
|
_type = "antioch/joint_targets"
|
|
47
|
-
targets: list[JointTarget]
|
|
106
|
+
targets: list[JointTarget] = Field(description="List of joint targets")
|
common/message/log.py
CHANGED
|
@@ -1,14 +1,24 @@
|
|
|
1
1
|
import time
|
|
2
2
|
from enum import Enum
|
|
3
3
|
|
|
4
|
+
from foxglove.schemas import Log as FoxgloveLog, LogLevel as FoxgloveLogLevel
|
|
4
5
|
from pydantic import Field
|
|
5
6
|
|
|
6
|
-
from common.message.
|
|
7
|
+
from common.message.message import Message
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
class LogLevel(str, Enum):
|
|
10
11
|
"""
|
|
11
12
|
Log level.
|
|
13
|
+
|
|
14
|
+
Example:
|
|
15
|
+
```python
|
|
16
|
+
from common.message import LogLevel
|
|
17
|
+
|
|
18
|
+
level = LogLevel.INFO
|
|
19
|
+
if level == LogLevel.ERROR:
|
|
20
|
+
print("Error occurred")
|
|
21
|
+
```
|
|
12
22
|
"""
|
|
13
23
|
|
|
14
24
|
DEBUG = "debug"
|
|
@@ -20,12 +30,47 @@ class LogLevel(str, Enum):
|
|
|
20
30
|
class Log(Message):
|
|
21
31
|
"""
|
|
22
32
|
Log entry structure.
|
|
33
|
+
|
|
34
|
+
Example:
|
|
35
|
+
```python
|
|
36
|
+
from common.message import Log, LogLevel
|
|
37
|
+
|
|
38
|
+
# Create a log entry
|
|
39
|
+
log = Log(
|
|
40
|
+
let_us=1000000,
|
|
41
|
+
level=LogLevel.INFO,
|
|
42
|
+
message="Robot initialized successfully",
|
|
43
|
+
channel="robot.init",
|
|
44
|
+
)
|
|
45
|
+
```
|
|
23
46
|
"""
|
|
24
47
|
|
|
25
48
|
_type = "antioch/log"
|
|
26
|
-
timestamp_us: int = Field(default_factory=lambda: int(time.time_ns() // 1000))
|
|
27
|
-
let_us: int
|
|
28
|
-
level: LogLevel
|
|
29
|
-
message: str | None = None
|
|
30
|
-
channel: str | None = None
|
|
31
|
-
telemetry: bytes | None = None
|
|
49
|
+
timestamp_us: int = Field(default_factory=lambda: int(time.time_ns() // 1000), description="Wall clock timestamp in microseconds")
|
|
50
|
+
let_us: int = Field(description="Logical event time in microseconds")
|
|
51
|
+
level: LogLevel = Field(description="Log severity level")
|
|
52
|
+
message: str | None = Field(default=None, description="Log message text")
|
|
53
|
+
channel: str | None = Field(default=None, description="Log channel/category")
|
|
54
|
+
telemetry: bytes | None = Field(default=None, description="Optional serialized telemetry data")
|
|
55
|
+
|
|
56
|
+
def to_foxglove(self) -> FoxgloveLog:
|
|
57
|
+
"""
|
|
58
|
+
Convert to Foxglove Log for telemetry.
|
|
59
|
+
|
|
60
|
+
:return: Foxglove Log schema.
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
level_map = {
|
|
64
|
+
LogLevel.DEBUG: FoxgloveLogLevel.Debug,
|
|
65
|
+
LogLevel.INFO: FoxgloveLogLevel.Info,
|
|
66
|
+
LogLevel.WARNING: FoxgloveLogLevel.Warning,
|
|
67
|
+
LogLevel.ERROR: FoxgloveLogLevel.Error,
|
|
68
|
+
}
|
|
69
|
+
return FoxgloveLog(
|
|
70
|
+
timestamp=None,
|
|
71
|
+
level=level_map[self.level],
|
|
72
|
+
message=self.message or "",
|
|
73
|
+
name=self.channel or "",
|
|
74
|
+
file="",
|
|
75
|
+
line=0,
|
|
76
|
+
)
|
common/message/pir.py
CHANGED
|
@@ -1,16 +1,33 @@
|
|
|
1
1
|
from pydantic import Field
|
|
2
2
|
|
|
3
|
-
from common.message.
|
|
3
|
+
from common.message.message import Message
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
class PirStatus(Message):
|
|
7
7
|
"""
|
|
8
8
|
PIR sensor status containing detection state and signal information.
|
|
9
|
+
|
|
10
|
+
Passive Infrared (PIR) sensors detect motion by measuring changes in
|
|
11
|
+
infrared radiation in their field of view.
|
|
12
|
+
|
|
13
|
+
Example:
|
|
14
|
+
```python
|
|
15
|
+
from common.message import PirStatus
|
|
16
|
+
|
|
17
|
+
# Create a PIR status
|
|
18
|
+
status = PirStatus(
|
|
19
|
+
is_detected=True,
|
|
20
|
+
signal_strength=0.85,
|
|
21
|
+
threshold=0.5,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
# Check for motion
|
|
25
|
+
if status.is_detected:
|
|
26
|
+
print(f"Motion detected! Signal: {status.signal_strength}")
|
|
27
|
+
```
|
|
9
28
|
"""
|
|
10
29
|
|
|
11
30
|
_type = "antioch/pir_status"
|
|
12
31
|
is_detected: bool = Field(description="Whether motion is currently detected")
|
|
13
|
-
signal_strength: float = Field(description="
|
|
14
|
-
threshold: float = Field(description="
|
|
15
|
-
element_flux: list[float] = Field(default_factory=list, description="Raw accumulated IR flux per element")
|
|
16
|
-
element_signal: list[float] = Field(default_factory=list, description="Pyroelectric voltage signal per element (proportional to dT/dt)")
|
|
32
|
+
signal_strength: float = Field(description="Analog signal strength [0.0, 1.0]")
|
|
33
|
+
threshold: float = Field(description="Detection threshold [0.0, 1.0]")
|
common/message/plot.py
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from pydantic import Field
|
|
6
|
+
|
|
7
|
+
from common.message.message import Message
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class PlotData(Message):
|
|
11
|
+
"""
|
|
12
|
+
Simple X/Y plot data for Foxglove visualization.
|
|
13
|
+
|
|
14
|
+
Use with Foxglove's Plot panel by selecting:
|
|
15
|
+
- X: .x[:]
|
|
16
|
+
- Y: .y[:]
|
|
17
|
+
|
|
18
|
+
Example:
|
|
19
|
+
```python
|
|
20
|
+
from common.message import PlotData
|
|
21
|
+
import numpy as np
|
|
22
|
+
|
|
23
|
+
distances = np.linspace(0, 10, 128)
|
|
24
|
+
values = np.random.rand(128)
|
|
25
|
+
|
|
26
|
+
plot = PlotData.from_arrays(distances, values)
|
|
27
|
+
sim.logger.telemetry("radar/mti/radar_left", plot)
|
|
28
|
+
```
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
_type = "antioch/plot_data"
|
|
32
|
+
|
|
33
|
+
x: list[float] = Field(default_factory=list, description="X-axis values")
|
|
34
|
+
y: list[float] = Field(default_factory=list, description="Y-axis values")
|
|
35
|
+
|
|
36
|
+
@classmethod
|
|
37
|
+
def from_arrays(cls, x: Any, y: Any) -> PlotData:
|
|
38
|
+
"""
|
|
39
|
+
Create PlotData from arrays or lists.
|
|
40
|
+
|
|
41
|
+
:param x: X values (numpy array or list).
|
|
42
|
+
:param y: Y values (numpy array or list).
|
|
43
|
+
:return: PlotData instance.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
x_list = x.tolist() if hasattr(x, "tolist") else list(x)
|
|
47
|
+
y_list = y.tolist() if hasattr(y, "tolist") else list(y)
|
|
48
|
+
return cls(x=x_list, y=y_list)
|
|
49
|
+
|
|
50
|
+
def to_foxglove(self) -> dict[str, Any]:
|
|
51
|
+
"""
|
|
52
|
+
Convert to Foxglove-compatible dict.
|
|
53
|
+
|
|
54
|
+
:return: Dict with x and y arrays.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
return {"x": self.x, "y": self.y}
|
common/message/point.py
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from
|
|
3
|
+
from foxglove.schemas import Point2 as FoxglovePoint2, Point3 as FoxglovePoint3
|
|
4
|
+
from pydantic import Field
|
|
5
|
+
|
|
6
|
+
from common.message.message import Message
|
|
4
7
|
|
|
5
8
|
|
|
6
9
|
class Point2(Message):
|
|
@@ -8,11 +11,25 @@ class Point2(Message):
|
|
|
8
11
|
A point representing a position in 2D space.
|
|
9
12
|
|
|
10
13
|
Used in image annotations and 2D coordinate systems.
|
|
14
|
+
|
|
15
|
+
Example:
|
|
16
|
+
```python
|
|
17
|
+
from common.message import Point2
|
|
18
|
+
|
|
19
|
+
# Create a 2D point
|
|
20
|
+
point = Point2(x=100.0, y=200.0)
|
|
21
|
+
|
|
22
|
+
# Create using factory method
|
|
23
|
+
origin = Point2.zero()
|
|
24
|
+
|
|
25
|
+
# Convert to Foxglove for visualization
|
|
26
|
+
foxglove_point = point.to_foxglove()
|
|
27
|
+
```
|
|
11
28
|
"""
|
|
12
29
|
|
|
13
30
|
_type = "antioch/point2"
|
|
14
|
-
x: float
|
|
15
|
-
y: float
|
|
31
|
+
x: float = Field(description="X coordinate")
|
|
32
|
+
y: float = Field(description="Y coordinate")
|
|
16
33
|
|
|
17
34
|
def __repr__(self) -> str:
|
|
18
35
|
"""
|
|
@@ -54,18 +71,41 @@ class Point2(Message):
|
|
|
54
71
|
|
|
55
72
|
return cls(x=0.0, y=0.0)
|
|
56
73
|
|
|
74
|
+
def to_foxglove(self) -> FoxglovePoint2:
|
|
75
|
+
"""
|
|
76
|
+
Convert to Foxglove Point2 for telemetry.
|
|
77
|
+
|
|
78
|
+
:return: Foxglove Point2 schema.
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
return FoxglovePoint2(x=self.x, y=self.y)
|
|
82
|
+
|
|
57
83
|
|
|
58
84
|
class Point3(Message):
|
|
59
85
|
"""
|
|
60
86
|
A point representing a position in 3D space.
|
|
61
87
|
|
|
62
88
|
Used in 3D graphics and spatial coordinate systems.
|
|
89
|
+
|
|
90
|
+
Example:
|
|
91
|
+
```python
|
|
92
|
+
from common.message import Point3
|
|
93
|
+
|
|
94
|
+
# Create a 3D point
|
|
95
|
+
point = Point3(x=1.0, y=2.0, z=3.0)
|
|
96
|
+
|
|
97
|
+
# Create at origin
|
|
98
|
+
origin = Point3.zero()
|
|
99
|
+
|
|
100
|
+
# Create using factory method
|
|
101
|
+
p = Point3.new(x=0.5, y=1.0, z=0.0)
|
|
102
|
+
```
|
|
63
103
|
"""
|
|
64
104
|
|
|
65
105
|
_type = "antioch/point3"
|
|
66
|
-
x: float
|
|
67
|
-
y: float
|
|
68
|
-
z: float
|
|
106
|
+
x: float = Field(description="X coordinate")
|
|
107
|
+
y: float = Field(description="Y coordinate")
|
|
108
|
+
z: float = Field(description="Z coordinate")
|
|
69
109
|
|
|
70
110
|
def __repr__(self) -> str:
|
|
71
111
|
"""
|
|
@@ -107,3 +147,12 @@ class Point3(Message):
|
|
|
107
147
|
"""
|
|
108
148
|
|
|
109
149
|
return cls(x=0.0, y=0.0, z=0.0)
|
|
150
|
+
|
|
151
|
+
def to_foxglove(self) -> FoxglovePoint3:
|
|
152
|
+
"""
|
|
153
|
+
Convert to Foxglove Point3 for telemetry.
|
|
154
|
+
|
|
155
|
+
:return: Foxglove Point3 schema.
|
|
156
|
+
"""
|
|
157
|
+
|
|
158
|
+
return FoxglovePoint3(x=self.x, y=self.y, z=self.z)
|
common/message/point_cloud.py
CHANGED
|
@@ -1,25 +1,50 @@
|
|
|
1
1
|
import struct
|
|
2
2
|
|
|
3
|
+
from foxglove.schemas import (
|
|
4
|
+
PackedElementField,
|
|
5
|
+
PackedElementFieldNumericType as NumericType,
|
|
6
|
+
PointCloud as FoxglovePointCloud,
|
|
7
|
+
Pose as FoxglovePose,
|
|
8
|
+
Quaternion as FoxgloveQuaternion,
|
|
9
|
+
Vector3 as FoxgloveVector3,
|
|
10
|
+
)
|
|
3
11
|
from pydantic import Field, model_validator
|
|
4
12
|
|
|
5
|
-
from common.message.
|
|
13
|
+
from common.message.message import Message
|
|
6
14
|
|
|
7
15
|
|
|
8
16
|
class PointCloud(Message):
|
|
9
17
|
"""
|
|
10
18
|
A collection of 3D points.
|
|
11
19
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
:
|
|
20
|
+
Point clouds are used for representing 3D sensor data such as LiDAR
|
|
21
|
+
scans or depth camera point clouds.
|
|
22
|
+
|
|
23
|
+
Example:
|
|
24
|
+
```python
|
|
25
|
+
from common.message import PointCloud
|
|
26
|
+
|
|
27
|
+
# Create a point cloud
|
|
28
|
+
cloud = PointCloud(
|
|
29
|
+
frame_id="lidar_link",
|
|
30
|
+
x=[1.0, 2.0, 3.0],
|
|
31
|
+
y=[0.5, 1.0, 1.5],
|
|
32
|
+
z=[0.1, 0.2, 0.3],
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
# Convert to Foxglove for visualization
|
|
36
|
+
foxglove_cloud = cloud.to_foxglove()
|
|
37
|
+
|
|
38
|
+
# Get packed bytes for transmission
|
|
39
|
+
data = cloud.to_bytes()
|
|
40
|
+
```
|
|
16
41
|
"""
|
|
17
42
|
|
|
18
43
|
_type = "antioch/point_cloud"
|
|
19
|
-
frame_id: str = Field(default="", description="Frame of reference")
|
|
20
|
-
x: list[float] = Field(description="X coordinates of points")
|
|
21
|
-
y: list[float] = Field(description="Y coordinates of points")
|
|
22
|
-
z: list[float] = Field(description="Z coordinates of points")
|
|
44
|
+
frame_id: str = Field(default="", description="Frame of reference for the point cloud")
|
|
45
|
+
x: list[float] = Field(description="X coordinates of points in meters")
|
|
46
|
+
y: list[float] = Field(description="Y coordinates of points in meters")
|
|
47
|
+
z: list[float] = Field(description="Z coordinates of points in meters")
|
|
23
48
|
|
|
24
49
|
@model_validator(mode="after")
|
|
25
50
|
def validate_array_lengths(self) -> "PointCloud":
|
|
@@ -47,17 +72,28 @@ class PointCloud(Message):
|
|
|
47
72
|
|
|
48
73
|
return bytes(data)
|
|
49
74
|
|
|
50
|
-
|
|
51
|
-
def combine(point_clouds: list["PointCloud"], frame_id: str = "") -> "PointCloud":
|
|
75
|
+
def to_foxglove(self) -> FoxglovePointCloud:
|
|
52
76
|
"""
|
|
53
|
-
|
|
77
|
+
Convert to Foxglove PointCloud for telemetry.
|
|
54
78
|
|
|
55
|
-
:
|
|
56
|
-
:param frame_id: Frame of reference for the combined point cloud.
|
|
57
|
-
:return: Combined point cloud with all points.
|
|
79
|
+
:return: Foxglove PointCloud schema.
|
|
58
80
|
"""
|
|
59
81
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
return
|
|
82
|
+
# Pack point data as interleaved x, y, z floats
|
|
83
|
+
data = self.to_bytes()
|
|
84
|
+
|
|
85
|
+
return FoxglovePointCloud(
|
|
86
|
+
timestamp=None,
|
|
87
|
+
frame_id=self.frame_id,
|
|
88
|
+
pose=FoxglovePose(
|
|
89
|
+
position=FoxgloveVector3(x=0, y=0, z=0),
|
|
90
|
+
orientation=FoxgloveQuaternion(x=0, y=0, z=0, w=1),
|
|
91
|
+
),
|
|
92
|
+
point_stride=12, # 3 floats * 4 bytes
|
|
93
|
+
fields=[
|
|
94
|
+
PackedElementField(name="x", offset=0, type=NumericType.Float32),
|
|
95
|
+
PackedElementField(name="y", offset=4, type=NumericType.Float32),
|
|
96
|
+
PackedElementField(name="z", offset=8, type=NumericType.Float32),
|
|
97
|
+
],
|
|
98
|
+
data=data,
|
|
99
|
+
)
|
common/message/pose.py
CHANGED
|
@@ -3,9 +3,10 @@ from __future__ import annotations
|
|
|
3
3
|
from typing import Any
|
|
4
4
|
|
|
5
5
|
import numpy as np
|
|
6
|
+
from foxglove.schemas import Pose as FoxglovePose, Quaternion as FoxgloveQuaternion, Vector3 as FoxgloveVector3
|
|
6
7
|
from pydantic import Field, field_validator, model_validator
|
|
7
8
|
|
|
8
|
-
from common.message.
|
|
9
|
+
from common.message.message import Message
|
|
9
10
|
from common.message.vector import Vector3
|
|
10
11
|
|
|
11
12
|
|
|
@@ -19,11 +20,31 @@ class Pose(Message):
|
|
|
19
20
|
Automatically converts from common Python types:
|
|
20
21
|
- Lists/tuples: Pose(position=[1.0, 2.0, 3.0], orientation=[0.0, 0.0, 1.57])
|
|
21
22
|
- Dicts: Pose(position={"x": 1.0, "y": 2.0, "z": 3.0}, orientation={"x": 0.0, "y": 0.0, "z": 1.57})
|
|
23
|
+
|
|
24
|
+
Example:
|
|
25
|
+
```python
|
|
26
|
+
from common.message import Pose, Vector3
|
|
27
|
+
|
|
28
|
+
# Create a pose with position and orientation
|
|
29
|
+
pose = Pose(
|
|
30
|
+
position=[1.0, 2.0, 0.5],
|
|
31
|
+
orientation=[0.0, 0.0, 1.57], # 90 degrees yaw
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
# Create from identity
|
|
35
|
+
identity = Pose.identity()
|
|
36
|
+
|
|
37
|
+
# Translate a pose
|
|
38
|
+
translated = pose.translate(Vector3(x=1.0, y=0.0, z=0.0))
|
|
39
|
+
|
|
40
|
+
# Access position components
|
|
41
|
+
print(f"Position: ({pose.position.x}, {pose.position.y}, {pose.position.z})")
|
|
42
|
+
```
|
|
22
43
|
"""
|
|
23
44
|
|
|
24
45
|
_type = "antioch/pose"
|
|
25
|
-
position: Vector3 = Field(default_factory=lambda: Vector3.zeros())
|
|
26
|
-
orientation: Vector3 = Field(default_factory=lambda: Vector3.zeros())
|
|
46
|
+
position: Vector3 = Field(default_factory=lambda: Vector3.zeros(), description="Position in 3D space as (x, y, z)")
|
|
47
|
+
orientation: Vector3 = Field(default_factory=lambda: Vector3.zeros(), description="Orientation as (roll, pitch, yaw) in radians")
|
|
27
48
|
|
|
28
49
|
def __add__(self, other: Vector3) -> Pose:
|
|
29
50
|
"""
|
|
@@ -71,7 +92,7 @@ class Pose(Message):
|
|
|
71
92
|
"""
|
|
72
93
|
|
|
73
94
|
if isinstance(v, (list, tuple, np.ndarray)):
|
|
74
|
-
return Vector3.
|
|
95
|
+
return Vector3._convert_input.__func__(Vector3, v)
|
|
75
96
|
return v
|
|
76
97
|
|
|
77
98
|
@field_validator("orientation", mode="before")
|
|
@@ -82,29 +103,34 @@ class Pose(Message):
|
|
|
82
103
|
"""
|
|
83
104
|
|
|
84
105
|
if isinstance(v, (list, tuple, np.ndarray)):
|
|
85
|
-
return Vector3.
|
|
106
|
+
return Vector3._convert_input.__func__(Vector3, v)
|
|
86
107
|
return v
|
|
87
108
|
|
|
88
109
|
@classmethod
|
|
89
|
-
def from_any(cls,
|
|
110
|
+
def from_any(cls, value: Pose | dict | tuple | list | None) -> Pose:
|
|
90
111
|
"""
|
|
91
|
-
Create Pose from
|
|
112
|
+
Create Pose from any compatible type.
|
|
113
|
+
|
|
114
|
+
Accepts:
|
|
115
|
+
- Pose instance (returned as-is)
|
|
116
|
+
- Dict with 'position' and/or 'orientation' keys
|
|
117
|
+
- Tuple/list of 3 floats (treated as position only)
|
|
118
|
+
- None (returns identity pose)
|
|
92
119
|
|
|
93
|
-
:param
|
|
120
|
+
:param value: Any compatible input type.
|
|
94
121
|
:return: A Pose instance.
|
|
95
|
-
:raises ValueError: If conversion fails.
|
|
96
122
|
"""
|
|
97
123
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
124
|
+
if value is None:
|
|
125
|
+
return cls.identity()
|
|
126
|
+
if isinstance(value, cls):
|
|
127
|
+
return value
|
|
128
|
+
if isinstance(value, dict):
|
|
129
|
+
return cls.model_validate(value)
|
|
130
|
+
if isinstance(value, (tuple, list)):
|
|
131
|
+
# Treat as position only
|
|
132
|
+
return cls(position=Vector3.from_any(value))
|
|
133
|
+
raise ValueError(f"Cannot convert {type(value).__name__} to Pose")
|
|
108
134
|
|
|
109
135
|
@classmethod
|
|
110
136
|
def identity(cls) -> Pose:
|
|
@@ -146,3 +172,17 @@ class Pose(Message):
|
|
|
146
172
|
"""
|
|
147
173
|
|
|
148
174
|
return Pose(position=self.position, orientation=self.orientation + rotation)
|
|
175
|
+
|
|
176
|
+
def to_foxglove(self) -> FoxglovePose:
|
|
177
|
+
"""
|
|
178
|
+
Convert to Foxglove Pose for telemetry.
|
|
179
|
+
|
|
180
|
+
:return: Foxglove Pose schema.
|
|
181
|
+
"""
|
|
182
|
+
|
|
183
|
+
# Convert RPY orientation to quaternion for Foxglove
|
|
184
|
+
quat = self.orientation.to_quat()
|
|
185
|
+
return FoxglovePose(
|
|
186
|
+
position=FoxgloveVector3(x=self.position.x, y=self.position.y, z=self.position.z),
|
|
187
|
+
orientation=FoxgloveQuaternion(x=quat.x, y=quat.y, z=quat.z, w=quat.w),
|
|
188
|
+
)
|