Python: SPG Composite AOV#

An SPG node whose input is a lidar rather than a camera. A lidar PointCloud is a composite AOV: several named channels published together under one render var, instead of the single image a camera AOV gives you. This node reads the Coordinates and Counts channels, counts the sweep’s returns by distance, and publishes a bar chart on its own AOV.

The scene the sensor sweeps, from a second render product with an ordinary camera. The orange post marks the lidar; the grey box is 6 m away and the tan box 12 m away and twice as wide:

The lidar scene, a sensor post and two boxes

What the node made of that sweep, one bar per box:

SPG composite AOV example output, a histogram of lidar returns by distance

Prerequisites#

  • Python 3.10-3.13

  • uv

  • An NVIDIA RTX-capable GPU and a supported driver

Running#

uv run main.py                                # CUDA
uv run main.py --scene lidar_scene_slang.usda # Slang

One renderer.step() fills two render products: the lidar and its histogram, and an ordinary camera view of the same scene. A successful run writes _output/histogram.png, the node’s chart with axes and labels drawn around it, and _output/scene.png, the camera’s view.

Binding the Channels#

Each channel binds like any other buffer:

outputs["Histogram"] = cuda.image(WIDTH, HEIGHT, cuda.uchar4)

return cuda.kernel({
    args = {
        cuda.int(WIDTH),                          -- -> int width
        cuda.int(HEIGHT),                         -- -> int height
        cuda.int(maxPoints),                      -- -> int maxPoints
        cuda.int(numBins),                        -- -> int numBins
        cuda.float(inputs["maxRange"]),           -- -> float maxRange
        cuda.float(inputs["maxCount"]),           -- -> float maxCount
        cuda.array(coordinates),                  -- -> const float* coordinates
        cuda.array(counts),                       -- -> const int* counts
        cuda.SurfaceObject(outputs["Histogram"]), -- -> cudaSurfaceObject_t histogram
    },
    -- One thread per output column, each counting its own bin.
    block = { THREADS },
    grid  = { math.ceil(WIDTH / THREADS) },
})

See Read a Sensor Composite for what a composite AOV is and how the channels are found.