Python Memory Types

Pool-backed types for sharing large sensor data between nodes with zero-copy IPC. Only a small descriptor (64-168 bytes) travels through the ring buffer; actual data stays in shared memory.

TypeUse caseCreateSee
TensorCustom data: costmaps, feature maps, state vectorsTensor([1000, 1000])Full reference
ImageCamera frames (RGB, BGR, grayscale, Bayer)Image(480, 640, "rgb8")Full reference
PointCloudLiDAR scans, 3D data (XYZ, XYZI, XYZRGB)PointCloud(10000, 3)Full reference
DepthImageDepth maps (F32 meters, U16 millimeters)DepthImage(480, 640)Full reference

All four types support zero-copy conversion to NumPy, and bridge into PyTorch or JAX through the NumPy view:

# simplified
np_array = img.to_numpy()                        # zero-copy
torch_tensor = torch.from_numpy(img.to_numpy())  # zero-copy for CPU tensors
jax_array = jnp.asarray(img.to_numpy())          # via NumPy view

to_numpy() and .as_tensor() methods are zero-copy (~3 us). from_*() methods copy once into shared memory.

Tensor Bridge: .as_tensor()

Every domain type can be converted to a Tensor for full Pythonic operations. This is zero-copy -- the Tensor shares the same shared memory:

# simplified
img = Image(480, 640, "rgb8")
t = img.as_tensor()           # shape=[480, 640, 3], dtype=uint8
t[0:10] += 128                # brighten top rows (writes to SHM)
bright = img + 50             # arithmetic returns Tensor
pt = torch.from_numpy(t.numpy())  # to PyTorch (zero-copy for CPU tensors)

cloud = PointCloud(10000)
t = cloud.as_tensor()         # shape=[10000, 3], dtype=float32
cloud[0]                      # first point (direct indexing)
len(cloud)                    # 10000

depth = DepthImage(480, 640)
t = depth.as_tensor()         # shape=[480, 640], dtype=float32

Design Decisions

Why pool-backed instead of heap-allocated? Pool-backed memory enables cross-process sharing. A heap-allocated NumPy array must be serialized for IPC. Pool-backed types live in shared memory from the start, so topic.send() copies only the descriptor, not megabytes of pixel data.

Why bridge PyTorch/JAX through NumPy? to_numpy() returns a view into the shared-memory pool, and torch.from_numpy() / jnp.asarray() wrap that view without copying (zero-copy for CPU tensors). Using the NumPy view as the bridge means every framework that speaks NumPy works with no extra protocol.

Why from_numpy() copies but to_numpy() doesn't? Publishing requires placing data at a specific pool slot. NumPy arrays at arbitrary heap addresses can't be shared. So from_numpy() copies once into the pool. to_numpy() returns a view into the already-shared memory -- no copy.

Thread safety: Pool-backed types use atomic reference counting. NumPy/PyTorch views should not outlive the source object -- when the HORUS type is dropped, the pool slot may be reclaimed.

See Also

  • Tensor -- General-purpose tensor with Pythonic API
  • Image -- Camera images with encoding support
  • PointCloud -- 3D point clouds with format queries
  • DepthImage -- Depth maps with typed access
  • ML Utilities -- ML framework integration
Spotted an error on this page?