antioch-py 2.2.4__py3-none-any.whl → 3.0.0__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.

Files changed (94) hide show
  1. antioch/__init__.py +101 -0
  2. antioch/{module/execution.py → execution.py} +1 -1
  3. antioch/{module/input.py → input.py} +2 -4
  4. antioch/{module/module.py → module.py} +17 -34
  5. antioch/{module/node.py → node.py} +17 -16
  6. {antioch_py-2.2.4.dist-info → antioch_py-3.0.0.dist-info}/METADATA +8 -11
  7. antioch_py-3.0.0.dist-info/RECORD +61 -0
  8. {antioch_py-2.2.4.dist-info → antioch_py-3.0.0.dist-info}/WHEEL +1 -1
  9. antioch_py-3.0.0.dist-info/licenses/LICENSE +21 -0
  10. common/ark/__init__.py +6 -16
  11. common/ark/ark.py +23 -62
  12. common/ark/hardware.py +1 -1
  13. common/ark/kinematics.py +1 -1
  14. common/ark/module.py +22 -0
  15. common/ark/node.py +46 -3
  16. common/ark/scheduler.py +2 -29
  17. common/ark/sim.py +1 -1
  18. {antioch/module → common/ark}/token.py +17 -0
  19. common/assets/rigging.usd +0 -0
  20. common/constants.py +63 -5
  21. common/core/__init__.py +37 -24
  22. common/core/auth.py +87 -112
  23. common/core/container.py +261 -0
  24. common/core/registry.py +131 -152
  25. common/core/rome.py +251 -0
  26. common/core/telemetry.py +176 -0
  27. common/core/types.py +219 -0
  28. common/message/__init__.py +19 -5
  29. common/message/annotation.py +174 -23
  30. common/message/array.py +25 -1
  31. common/message/camera.py +23 -1
  32. common/message/color.py +32 -6
  33. common/message/detection.py +40 -0
  34. common/message/foxglove.py +20 -0
  35. common/message/frame.py +71 -7
  36. common/message/image.py +58 -9
  37. common/message/imu.py +24 -4
  38. common/message/joint.py +69 -10
  39. common/message/log.py +52 -7
  40. common/message/pir.py +23 -8
  41. common/message/plot.py +57 -0
  42. common/message/point.py +55 -6
  43. common/message/point_cloud.py +55 -19
  44. common/message/pose.py +59 -19
  45. common/message/quaternion.py +105 -92
  46. common/message/radar.py +195 -29
  47. common/message/twist.py +34 -0
  48. common/message/types.py +40 -5
  49. common/message/vector.py +180 -245
  50. common/sim/__init__.py +49 -0
  51. common/{session/config.py → sim/objects.py} +97 -27
  52. common/sim/state.py +11 -0
  53. common/utils/comms.py +30 -12
  54. common/utils/logger.py +26 -7
  55. antioch/message.py +0 -87
  56. antioch/module/__init__.py +0 -53
  57. antioch/session/__init__.py +0 -152
  58. antioch/session/ark.py +0 -500
  59. antioch/session/asset.py +0 -65
  60. antioch/session/error.py +0 -80
  61. antioch/session/objects/__init__.py +0 -40
  62. antioch/session/objects/animation.py +0 -162
  63. antioch/session/objects/articulation.py +0 -180
  64. antioch/session/objects/basis_curve.py +0 -180
  65. antioch/session/objects/camera.py +0 -65
  66. antioch/session/objects/collision.py +0 -46
  67. antioch/session/objects/geometry.py +0 -58
  68. antioch/session/objects/ground_plane.py +0 -48
  69. antioch/session/objects/imu.py +0 -53
  70. antioch/session/objects/joint.py +0 -49
  71. antioch/session/objects/light.py +0 -123
  72. antioch/session/objects/pir_sensor.py +0 -102
  73. antioch/session/objects/radar.py +0 -62
  74. antioch/session/objects/rigid_body.py +0 -197
  75. antioch/session/objects/xform.py +0 -119
  76. antioch/session/record.py +0 -158
  77. antioch/session/scene.py +0 -1544
  78. antioch/session/session.py +0 -211
  79. antioch/session/task.py +0 -309
  80. antioch_py-2.2.4.dist-info/RECORD +0 -85
  81. antioch_py-2.2.4.dist-info/entry_points.txt +0 -2
  82. common/core/agent.py +0 -324
  83. common/core/task.py +0 -36
  84. common/message/velocity.py +0 -11
  85. common/rome/__init__.py +0 -9
  86. common/rome/client.py +0 -435
  87. common/rome/error.py +0 -16
  88. common/session/__init__.py +0 -31
  89. common/session/environment.py +0 -31
  90. common/session/sim.py +0 -129
  91. common/utils/usd.py +0 -12
  92. /antioch/{module/clock.py → clock.py} +0 -0
  93. {antioch_py-2.2.4.dist-info → antioch_py-3.0.0.dist-info}/top_level.txt +0 -0
  94. /common/message/{base.py → message.py} +0 -0
common/message/image.py CHANGED
@@ -3,13 +3,30 @@ from __future__ import annotations
3
3
  from enum import Enum
4
4
 
5
5
  import numpy as np
6
+ from foxglove.schemas import RawImage as FoxgloveRawImage
7
+ from pydantic import Field
6
8
 
7
- from common.message.base import Message
9
+ from common.message.message import Message
8
10
 
9
11
 
10
12
  class ImageEncoding(str, Enum):
11
13
  """
12
14
  Image encodings with associated metadata.
15
+
16
+ Example:
17
+ ```python
18
+ from common.message import ImageEncoding
19
+
20
+ # Get bytes per pixel for an encoding
21
+ encoding = ImageEncoding.RGB8
22
+ bpp = encoding.bytes_per_pixel # 3
23
+
24
+ # Get numpy dtype for an encoding
25
+ dtype = encoding.numpy_dtype # np.uint8
26
+
27
+ # Get number of channels
28
+ channels = encoding.channels # 3
29
+ ```
13
30
  """
14
31
 
15
32
  # Monochrome images
@@ -92,17 +109,31 @@ class Image(Message):
92
109
  """
93
110
  An image with raw data.
94
111
 
95
- :param encoding: The encoding of the image.
96
- :param width: The width of the image in pixels.
97
- :param height: The height of the image in pixels.
98
- :param data: The raw image data bytes.
112
+ Example:
113
+ ```python
114
+ from common.message import Image, ImageEncoding
115
+ import numpy as np
116
+
117
+ # Create an image from a numpy array
118
+ rgb_array = np.zeros((480, 640, 3), dtype=np.uint8)
119
+ rgb_array[:, :, 0] = 255 # Red channel
120
+ image = Image.from_numpy(rgb_array, ImageEncoding.RGB8)
121
+
122
+ # Convert back to numpy for processing
123
+ array = image.to_numpy()
124
+ print(f"Shape: {array.shape}") # (480, 640, 3)
125
+
126
+ # Create a depth image
127
+ depth_array = np.ones((480, 640), dtype=np.float32) * 2.5
128
+ depth_image = Image.from_numpy(depth_array, ImageEncoding.DEPTH_F32)
129
+ ```
99
130
  """
100
131
 
101
132
  _type = "antioch/image"
102
- encoding: ImageEncoding
103
- width: int
104
- height: int
105
- data: bytes
133
+ encoding: ImageEncoding = Field(description="Pixel encoding format of the image")
134
+ width: int = Field(description="Image width in pixels")
135
+ height: int = Field(description="Image height in pixels")
136
+ data: bytes = Field(description="Raw image data as bytes")
106
137
 
107
138
  @classmethod
108
139
  def from_numpy(cls, array: np.ndarray, encoding: ImageEncoding | None = None) -> Image:
@@ -169,3 +200,21 @@ class Image(Message):
169
200
  shape = (self.height, self.width) if self.encoding.channels == 1 else (self.height, self.width, self.encoding.channels)
170
201
  array = np.frombuffer(self.data, dtype=self.encoding.numpy_dtype)
171
202
  return array.reshape(shape)
203
+
204
+ def to_foxglove(self) -> FoxgloveRawImage:
205
+ """
206
+ Convert to Foxglove RawImage for telemetry.
207
+
208
+ :return: Foxglove RawImage schema.
209
+ """
210
+
211
+ step = self.width * self.encoding.bytes_per_pixel
212
+ return FoxgloveRawImage(
213
+ timestamp=None,
214
+ frame_id="",
215
+ width=self.width,
216
+ height=self.height,
217
+ encoding=self.encoding.value,
218
+ step=step,
219
+ data=self.data,
220
+ )
common/message/imu.py CHANGED
@@ -1,4 +1,6 @@
1
- from common.message.base import Message
1
+ from pydantic import Field
2
+
3
+ from common.message.message import Message
2
4
  from common.message.quaternion import Quaternion
3
5
  from common.message.vector import Vector3
4
6
 
@@ -6,9 +8,27 @@ from common.message.vector import Vector3
6
8
  class ImuSample(Message):
7
9
  """
8
10
  IMU sensor sample data.
11
+
12
+ Contains linear acceleration, angular velocity, and orientation from an
13
+ inertial measurement unit sensor.
14
+
15
+ Example:
16
+ ```python
17
+ from common.message import ImuSample, Vector3, Quaternion
18
+
19
+ # Create an IMU sample
20
+ sample = ImuSample(
21
+ linear_acceleration=Vector3(x=0.0, y=0.0, z=9.81), # Gravity
22
+ angular_velocity=Vector3(x=0.01, y=0.0, z=0.0),
23
+ orientation=Quaternion.identity(),
24
+ )
25
+
26
+ # Access acceleration components
27
+ accel_z = sample.linear_acceleration.z
28
+ ```
9
29
  """
10
30
 
11
31
  _type = "antioch/imu_sample"
12
- linear_acceleration: Vector3
13
- angular_velocity: Vector3
14
- orientation: Quaternion
32
+ linear_acceleration: Vector3 = Field(description="Linear acceleration in m/s² (x, y, z)")
33
+ angular_velocity: Vector3 = Field(description="Angular velocity in rad/s (x, y, z)")
34
+ orientation: Quaternion = Field(description="Orientation as quaternion (w, x, y, z)")
common/message/joint.py CHANGED
@@ -1,4 +1,6 @@
1
- from common.message.base import Message
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
- position: float
14
- velocity: float
15
- effort: float
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. All fields are optional - omitted values are not controlled.
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
- position: float | None = None
28
- velocity: float | None = None
29
- effort: float | None = None
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.base import 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,18 +1,33 @@
1
1
  from pydantic import Field
2
2
 
3
- from common.message.base import 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
- is_detected: bool = Field(description="Whether motion is currently detected (aggregated across all sensors)")
13
- signal_strength: float = Field(description="Max absolute signal strength across all sensors")
14
-
15
- # Multi-sensor status
16
- sensor_states: list[bool] = Field(default_factory=list, description="Detection state per sensor (Center, Left, Right)")
17
- sensor_signals: list[float] = Field(default_factory=list, description="Signal strength per sensor")
18
- sensor_thresholds: list[float] = Field(default_factory=list, description="Detection threshold per sensor")
31
+ is_detected: bool = Field(description="Whether motion is currently detected")
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 common.message.base import Message
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)
@@ -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.base import 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
- :param frame_id: Frame of reference.
13
- :param x: X coordinates of points.
14
- :param y: Y coordinates of points.
15
- :param z: Z coordinates of points.
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
- @staticmethod
51
- def combine(point_clouds: list["PointCloud"], frame_id: str = "") -> "PointCloud":
75
+ def to_foxglove(self) -> FoxglovePointCloud:
52
76
  """
53
- Combine multiple point clouds into a single point cloud.
77
+ Convert to Foxglove PointCloud for telemetry.
54
78
 
55
- :param point_clouds: List of point clouds to combine.
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
- all_x = sum((pc.x for pc in point_clouds), [])
61
- all_y = sum((pc.y for pc in point_clouds), [])
62
- all_z = sum((pc.z for pc in point_clouds), [])
63
- return PointCloud(frame_id=frame_id, x=all_x, y=all_y, z=all_z)
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
+ )