Python DepthImage

A depth image backed by shared memory for zero-copy inter-process communication. Supports F32 (meters) and U16 (millimeters) formats. Use for stereo cameras, structured light sensors (RealSense, Kinect), and ToF cameras.

When to Use

Use DepthImage when you need per-pixel depth values with typed access (get_depth() returns meters, depth_statistics() gives min/max/mean). For raw 16-bit depth transport without typed access, use Image with "depth16" encoding instead.

ROS2 equivalent: sensor_msgs/Image with encoding=32FC1 — but HORUS adds typed depth access methods and statistics.

Constructor

from horus import DepthImage

# F32 depth image (meters) — most common
depth = DepthImage(height=480, width=640, dtype="float32")

# U16 depth image (millimeters) — RealSense raw output
depth_u16 = DepthImage(height=480, width=640, dtype="uint16")

Parameters:

  • height: int — Image height in pixels
  • width: int — Image width in pixels
  • dtype: str"float32" (meters) or "uint16" (millimeters). Default: "float32"

Factory Methods

# From NumPy — shape (H, W), dtype auto-detected
import numpy as np
depth_data = np.random.uniform(0.5, 5.0, (480, 640)).astype(np.float32)
depth = DepthImage.from_numpy(depth_data)

# From PyTorch tensor
import torch
depth = DepthImage.from_torch(torch.randn(480, 640))

Properties

PropertyTypeDescription
heightintImage height
widthintImage width
dtypestr"float32" or "uint16"
nbytesintTotal data size in bytes
frame_idstrCamera coordinate frame
timestamp_nsintTimestamp in nanoseconds
depth_scalefloatScale factor (1.0 for meters, 0.001 for mm→m)

Format Queries

depth.is_meters()       # True if F32 (float32)
depth.is_millimeters()  # True if U16 (uint16)

Methods

Depth Access

# Get depth at pixel — always returns meters as float
d = depth.get_depth(320, 240)
print(f"Center depth: {d:.3f}m")

# Set depth at pixel — value in meters
depth.set_depth(100, 100, 1.5)

# Statistics (min, max, mean) — None if all pixels are invalid/zero
stats = depth.depth_statistics()
if stats:
    min_d, max_d, mean_d = stats
    print(f"Range: {min_d:.2f}–{max_d:.2f}m, mean: {mean_d:.2f}m")
MethodSignatureDescription
get_depth(x, y)(int, int) -> floatDepth in meters at pixel
set_depth(x, y, val)(int, int, float) -> NoneSet depth in meters
depth_statistics()() -> Optional[tuple[float, float, float]](min, max, mean) in meters

Framework Conversions

# To NumPy — zero-copy, shape (H, W)
np_depth = depth.to_numpy()

# To PyTorch — zero-copy via DLPack
torch_depth = depth.to_torch()

# To JAX — zero-copy via DLPack
jax_depth = depth.to_jax()

Metadata

depth.set_frame_id("depth_camera")
depth.set_timestamp_ns(horus.timestamp_ns())

Complete Example

import horus
from horus import DepthImage, Topic
import numpy as np

depth_topic = Topic(DepthImage)

def depth_camera_tick(node):
    # Simulate depth camera (0.3m–10m range)
    raw = np.random.uniform(0.3, 10.0, (480, 640)).astype(np.float32)
    depth = DepthImage.from_numpy(raw)
    depth.set_frame_id("realsense_depth")
    depth_topic.send(depth)

def safety_tick(node):
    depth = depth_topic.recv()
    if depth:
        stats = depth.depth_statistics()
        if stats:
            min_d, _, _ = stats
            if min_d < 0.5:
                node.log_warning(f"Object at {min_d:.2f}m — too close!")

        # Check specific region (center pixel)
        center_d = depth.get_depth(320, 240)
        if center_d < 1.0:
            node.log_error(f"Collision risk: {center_d:.2f}m ahead")

camera = horus.Node(name="depth_cam", tick=depth_camera_tick, rate=30, order=0, pubs=["depth"])
safety = horus.Node(name="safety", tick=safety_tick, rate=30, order=1, subs=["depth"])
horus.run(camera, safety)

Design Decisions

Why separate DepthImage instead of Image with depth encoding? Image with "depth16" encoding gives you raw 16-bit values with no unit semantics. DepthImage adds typed depth access: get_depth() always returns meters (auto-converting from mm for U16), and depth_statistics() computes min/max/mean. Use Image for transport, DepthImage for processing.

Why F32 and U16 but not F64 or U32? F32 (meters) gives 7 decimal digits of precision — sub-millimeter accuracy up to 1 km. More than any depth sensor provides. U16 (millimeters) covers 0–65.5m range, matching RealSense/Kinect native output. F64 and U32 would double memory usage with no practical benefit.


See Also