Python PointCloud
A 3D point cloud backed by shared memory for zero-copy inter-process communication. Supports XYZ, XYZI (intensity), and XYZRGB (color) point formats with direct conversion to NumPy, PyTorch, and JAX.
When to Use
Use PointCloud when your robot has a LiDAR, depth camera, or stereo vision system producing 3D point data. A 100K-point cloud transfers between nodes in microseconds via shared memory — no serialization.
ROS2 equivalent: sensor_msgs/PointCloud2 — same concept, but HORUS uses shared memory pools instead of serialized byte arrays.
Constructor
from horus import PointCloud
# PointCloud(num_points, fields, dtype)
cloud = PointCloud(num_points=10000, fields=3, dtype="float32")
Parameters:
num_points: int— Number of points to allocatefields: int— Floats per point:3= XYZ,4= XYZI,6= XYZRGB (default:3)dtype: str— Data type (default:"float32")
Factory Methods
# From NumPy — shape (N, F) where F = fields per point
import numpy as np
points = np.random.randn(10000, 3).astype(np.float32)
cloud = PointCloud.from_numpy(points)
# From PyTorch tensor
import torch
tensor = torch.randn(10000, 3)
cloud = PointCloud.from_torch(tensor)
| Factory | Parameters | Use case |
|---|---|---|
PointCloud(n, f, dtype) | count, fields, dtype | Pre-allocate for filling |
PointCloud.from_numpy(arr) | ndarray shape (N, F) | LiDAR driver output |
PointCloud.from_torch(tensor) | Tensor shape (N, F) | ML model output |
Properties
| Property | Type | Description |
|---|---|---|
point_count | int | Number of points |
fields_per_point | int | Floats per point (3, 4, or 6) |
dtype | str | Data type string (e.g., "float32") |
nbytes | int | Total data size in bytes |
frame_id | str | Sensor coordinate frame (e.g., "lidar_front") |
timestamp_ns | int | Timestamp in nanoseconds |
Point Format Queries
cloud.is_xyz() # True if 3 fields (XYZ only)
cloud.has_intensity() # True if 4+ fields (XYZI)
cloud.has_color() # True if 6+ fields (XYZRGB)
Methods
Point Access
# Get i-th point as list of floats
point = cloud.point_at(0) # e.g., [1.0, 2.0, 3.0]
Framework Conversions
# To NumPy — zero-copy, shape (N, F)
np_points = cloud.to_numpy()
# To PyTorch — zero-copy via DLPack
torch_points = cloud.to_torch()
# To JAX — zero-copy via DLPack
jax_points = cloud.to_jax()
Metadata
cloud.set_frame_id("lidar_front")
cloud.set_timestamp_ns(horus.timestamp_ns())
Complete Example
import horus
from horus import PointCloud, Topic
import numpy as np
scan_topic = Topic(PointCloud)
def lidar_tick(node):
# Simulate LiDAR scan (10000 XYZ points)
points = np.random.randn(10000, 3).astype(np.float32)
cloud = PointCloud.from_numpy(points)
cloud.set_frame_id("lidar_front")
scan_topic.send(cloud)
def obstacle_tick(node):
cloud = scan_topic.recv()
if cloud:
pts = cloud.to_numpy() # Zero-copy (N, 3)
# Find points within 2m
distances = np.linalg.norm(pts, axis=1)
nearby = np.sum(distances < 2.0)
if nearby > 100:
node.log_warning(f"{nearby} points within 2m!")
lidar = horus.Node(name="lidar", tick=lidar_tick, rate=10, order=0, pubs=["scan"])
detector = horus.Node(name="obstacle", tick=obstacle_tick, rate=10, order=1, subs=["scan"])
horus.run(lidar, detector)
Design Decisions
Why fields_per_point instead of named fields? Point clouds in robotics use a small set of layouts: XYZ (3), XYZI (4), XYZRGB (6). A flat float array with a known field count is the fastest representation — no per-point struct overhead, direct memcpy to GPU, and trivial NumPy reshaping. Named fields would add indirection and prevent zero-copy to ML frameworks.
Why pool-backed? Same as Image — shared memory pools enable zero-copy IPC. A 100K-point XYZ cloud is 1.2 MB. Serializing it takes milliseconds; sharing a 64-byte descriptor takes microseconds.
See Also
- Image (Python) — Camera images
- DepthImage (Python) — Depth maps
- PointCloudBuffer — Incremental point cloud building
- LiDAR Obstacle Avoidance — Recipe using PointCloud