Outputs#

The Generic wpm model delivers Contributions as a composite output. Each valid entry describes one path from a ray’s emission origin, through one or more scene hits, to one receiver. It is raw path data for further processing.

Attribute meanings are on USD Attributes. Shared mapping, synchronization and ownership rules are on Sensor Outputs. This output uses Valid and its own parameters; it does not use the point-cloud Counts and Flags contract.

Requesting the output#

Request sourceName = "Contributions" in one RenderVar on the sensor’s RenderProduct. The output contains named tensor channels and CPU parameters.

Set token[] channels on the RenderVar to choose its channels. An empty array requests the twelve default channels below. An explicit list requests those channels plus Valid, which is always present. For example, channels = ["RoundTripDistanceM"] returns path distance and validity. Requesting either optional geometry channel enables both. Repeated names are deduplicated; unknown names are reported and ignored. Multiple Contributions RenderVars in one RenderProduct are rejected.

The example base authors /World/Sensor and requests its output. The USD Python tab assumes an existing USD stage. Complete its pattern as shown in Set Up a Sensor:

#usda 1.0
(
    defaultPrim = "World"
    metersPerUnit = 1
    upAxis = "Z"
    timeCodesPerSecond = 10
)

def Xform "World"
{
    def OmniGeneralSensor "Sensor" (
        prepend apiSchemas = ["OmniSensorGenericWpmAPI"]
    )
    {
        # Align the sensor's +X-forward basis with this Z-up world.
        float3 xformOp:rotateXYZ = (90, 0, -90)
        uniform token[] xformOpOrder = ["xformOp:rotateXYZ"]
        double2 omni:sensor:frameRate = (10, 1)
        uint omni:sensor:GenericWpm:maxBounces = 1
        float omni:sensor:GenericWpm:maxHitDistanceM = 100
        uint omni:sensor:GenericWpm:activeTxPatternIndex = 0
        uint omni:sensor:GenericWpm:activeRxPatternIndex = 0
        uint omni:sensor:GenericWpm:activeMappingPageIndex = 0
    }
}

def Scope "Render"
{
    def RenderProduct "GenericWpm"
    {
        rel camera = </World/Sensor>
        rel orderedVars = [</Render/GenericWpm/Contributions>]
        def RenderVar "Contributions"
        {
            uniform string sourceName = "Contributions"
            # Empty selects the default contribution channels.
            token[] channels = []
        }
    }
}
from pxr import Sdf, UsdGeom, UsdRender
UsdGeom.SetStageMetersPerUnit(stage, 1)
UsdGeom.SetStageUpAxis(stage, UsdGeom.Tokens.z)
stage.SetTimeCodesPerSecond(10)
world = UsdGeom.Xform.Define(stage, "/World").GetPrim()
stage.SetDefaultPrim(world)
sensor = stage.DefinePrim("/World/Sensor", "OmniGeneralSensor")
sensor.AddAppliedSchema("OmniSensorGenericWpmAPI")
# Align the sensor's +X-forward basis with this Z-up world.
sensor.CreateAttribute("xformOp:rotateXYZ", Sdf.ValueTypeNames.Float3, custom=False).Set((90, 0, -90))
sensor.CreateAttribute(
    "xformOpOrder", Sdf.ValueTypeNames.TokenArray, custom=False, variability=Sdf.VariabilityUniform
).Set(["xformOp:rotateXYZ"])
sensor.CreateAttribute("omni:sensor:frameRate", Sdf.ValueTypeNames.Double2, custom=False).Set((10, 1))
ns = "omni:sensor:GenericWpm:"
sensor.CreateAttribute(ns + "maxBounces", Sdf.ValueTypeNames.UInt, custom=False).Set(1)
sensor.CreateAttribute(ns + "maxHitDistanceM", Sdf.ValueTypeNames.Float, custom=False).Set(100)
for name in ("activeTxPatternIndex", "activeRxPatternIndex", "activeMappingPageIndex"):
    sensor.CreateAttribute(ns + name, Sdf.ValueTypeNames.UInt, custom=False).Set(0)

UsdGeom.Scope.Define(stage, "/Render")
product = UsdRender.Product.Define(stage, "/Render/GenericWpm")
product.CreateCameraRel().SetTargets(["/World/Sensor"])
product.CreateOrderedVarsRel().SetTargets(["/Render/GenericWpm/Contributions"])
output = UsdRender.Var.Define(stage, "/Render/GenericWpm/Contributions")
output.CreateSourceNameAttr("Contributions")
# Empty selects the default contribution channels.
output.GetPrim().CreateAttribute("channels", Sdf.ValueTypeNames.TokenArray, custom=False).Set([])

The base setup authors the output request alongside the sensor. Request only the channels your consumer needs; allocation follows that selection.

Paths and contributions#

One emitted ray can produce several contributions: at different bounces and for different receivers. Each contribution follows the emitted path as far as one scene hit, then connects that hit to one receiver selected by the mapping. The receiver connection and the continuation to the next scene hit are different parts of the calculation.

An emitted path from Tx visits four numbered scene hits. Dashed contribution connections lead from each hit to Rx. The path through the first two hits to Rx is highlighted and divided into segments d1, d2 and d3.

The highlighted path forms one contribution: Tx to hit 1, hit 1 to hit 2, then hit 2 to Rx. Its total distance is d₁ + d₂ + d₃. The connection from hit 1 to Rx describes a separate contribution from the same emitted ray. Schematic, not to scale.#

With multiple connected receivers, the same scene hit can lead to a separate contribution for each receiver. Their final path segments, total distances and receiver-relative hit coordinates can differ. Increasing the number of receivers therefore changes the possible contributions without adding emitted rays. The receiver mapping determines those connections.

The record’s geometry describes the hit where that contribution branches toward the receiver; it is not a list of every vertex on the path. Use RoundTripDistanceM for the full path length and the hit-position channels for that hit’s location. DeltaTimeNs identifies when the ray was fired, not when a signal arrives at the receiver. The channel reference defines their units and frames.

Contribution layout#

A Contributions output has CPU parameters and aligned channel tensors. Five dimensions order slots by tile, transmitter, ray, bounce and receiver slot. A Valid row selects which payload rows may be read; duplicate receiver IDs retain separate slots.

Contribution slots share the same order across every channel. Receiver slot varies fastest. The example receiver group has duplicate receiver 2 in two distinct slots. Schematic values illustrate ordering, not a capture.#

The logical output dimensions are [tile, tx, rayInTx, bounce, receiverSlot]. The number of allocated slots is N = numTiles * numTx * raysPerTx * maxBounces * groupSize. These dimensions describe the active patterns, not the total number of stored alternatives. Receiver slot varies fastest, followed by bounce, ray, transmitter and tile. Scalar channels have shape [N], position channels [N, 3] and triangle vertices [N, 3, 3].

For example, one tile with two transmitters, three rays per transmitter, two bounce levels and two receivers in nton mode has 1 * 2 * 3 * 2 * 2 = 24 allocated slots. Six rays are emitted, and each has four possible slots: two receivers at each of two bounces. The allocation describes where contributions can be recorded, not how many paths the scene produces.

A receiver slot is a position in the selected receiver group, not a receiver ID. For example, the ordered group [2, 0, 2] produces three slots, including two separate slots targeting receiver 2. Blooming preserves its square neighborhood layout at grid edges; clipped neighbors remain allocated but invalid.

Read a payload only when its corresponding Valid value is nonzero. A scene hit alone does not guarantee a valid contribution: the resulting return power must be positive and the receiver must be visible. Invalid slots have unspecified payloads; they are not zero-valued measurements. The first bounce has axis index 0 but reports NumBounces = 1. That value counts the emitted path’s hits through the current hit. Material evaluations on the final hit-to-receiver connection do not increase it; their separate limit is described under propagation controls.

Channels#

The following channels are enabled by default:

Channel

Type

Shape

Unit

Meaning

HitPosWorld

float32

[N, 3]

world units

Hit in translated-world coordinates and renderer/world units. Not necessarily absolute world coordinates or meters.

HitPosSensorM

float32

[N, 3]

m

Hit in the sensor frame sampled using the firing offset, in meters. Motion sampling follows the firing-time limits.

RxRelativePosM

float32

[N, 3]

m

Hit in the selected receiver’s frame, in meters.

RoundTripDistanceM

float32

[N]

m

Total path length from the emission origin through the scene hits to the receiver, in meters.

RoundTripVelocityMps

float32

[N]

m/s

Path-length change rate in meters/second. Positive means increasing path length; this is not the object’s speed.

NumBounces

uint32

[N]

count

One-based emitted-path hit count through the current hit. Excludes material evaluations on the final receiver connection.

MaterialId

uint32

[N]

ID

Non-visual material ID at the hit, not an object or instance ID.

CosAngle

float32

[N]

unitless

Absolute cosine between the shading normal and the incoming direction toward the previous path vertex.

AccumBeamRangeDivergenceM

float32

[N]

m

Accumulated beam range/divergence quantity in meters. It is not the Gaussian sampling spread or a range measurement.

DeltaTimeNs

uint64

[N]

ns

Firing offset from frameStartTimeStampNs, constant along the path. Not propagation or arrival time; see Firing times.

Power

float32

[N]

relative scalar power

Scalar return-transport power relative to unit source power, after propagation to the receiver. Not calibrated detector power or intensity.

Valid

uint32

[N]

validity

1 for a written positive-power contribution; 0 otherwise.

The optional geometry channels describe the triangle at the current hit:

Channel

Type

Shape

Unit

Meaning

HitVerticesM

float32

[N, 3, 3]

m

Three triangle vertices in the receiver frame, in meters.

GeometryAreaM2

float32

[N]

m²

Triangle area in square meters.

When triangle geometry is unavailable, both geometry channels contain zero even if the contribution is valid. There is no separate geometry-valid channel.

Path length and range#

Half of RoundTripDistanceM is range only for a direct return whose ray-emission and receiver origins coincide. Matching transmitter and receiver positions is insufficient if the ray has a nonzero transmitter-relative origin. For bistatic layouts and multi-bounce paths, use the total path distance or the appropriate hit-position channel instead.

Output transforms and delivery#

  • Patterns and selection set the transmitter/ray axes and geometry. Receiver mapping sets ordered receiver slots.

  • Propagation controls set bounce capacity, path search limits and scalar transport. Wavelength and wave type affect the response used for Power.

  • Firing times set DeltaTimeNs and the motion sample used for hit positions and path velocity.

Params#

CPU parameters describe the accepted configuration and frame. Scalar parameters have shape [1]; positions and orientations use the shapes shown below.

Param

Type

Shape

Value

numTiles, numTx, raysPerTx, maxBounces, groupSize

uint32

[1]

The five logical output dimensions.

numRx

uint32

[1]

Number of active receivers; it can differ from the receiver-slot count.

maxContributions

uint32

[1]

Allocated slot capacity, not the number of valid returns.

activeTxPatternIndex, activeRxPatternIndex, activeMappingPageIndex

uint32

[1]

Accepted selectors. Absent until the first valid configuration.

frameId

uint64

[1]

Renderer frame identifier.

timestampNs, frameStartTimeStampNs, frameEndTimeStampNs

uint64

[1]

Frame motion interval in nanoseconds. timestampNs equals its end.

frameStartPosM, frameEndPosM

float32

[3]

Sensor start/end positions in meters, for tile 0.

frameStartOrientation, frameEndOrientation

float32

[4]

Sensor start/end xyzw orientations, for tile 0.

gridShape:<tx|rx|ray>:<role-pattern-index>

uint32

[6]

Selected grid descriptor: start, cell count, rows, columns, scan order and column wrap.

numRays

uint32

[1]

Same value as raysPerTx.

numTxPatterns, numRxPatterns, numRayPatterns, numRxGroupMaps

uint32

[1]

Each is one for the generated selection.

numGroups, numTargetRxGroups

uint32

[1]

numTx for 1to1; one for map or nton.

wrapCols, rxFlatIndexEqualsTxFlatIndex

uint32

[1]

Boolean layout hints, not selectors.

Selected grids also provide gridShape:<tx|rx|ray>:<role-pattern-index> as uint32 [6]: [0, cellCount, numRows, numCols, scanOrder, wrapCols]. Scan order 0 means row-major and 1 means column-major.

Reading the output#

Wait for a completed output, read its dimensions and filter by Valid. Before the first valid configuration, output is EMPTY: channel names are present, but their leading dimensions are zero. Pipeline warmup can also produce zero-length channels even when accepted dimensions are available. Do not reshape these empty tensors using the capacity parameters. A completed capture whose allocated slots all have Valid = 0 is different: it rendered but produced no positive-power contributions.

The Python example and C example show mapping, ownership and synchronization. CPU parameters remain separate from the channel payloads.

Read the parameters before the payloads. For a nonempty capture, preserve logical slot coordinates before filtering so that transmitter, ray, bounce and receiver-slot identities remain available. The paired Python/C capture example shows the complete step, map, copy and filtering sequence.

The following routines consume owning CPU copies of the named channels and parameters. The capture example shows the preceding step, wait, map and copy operations and their lifetime rules:

main.py, from the runnable Python example.#
import numpy as np

# These are owned copies from capture(), not borrowed GPU or mapped CPU data.
shape = tuple(int(params[name].item()) for name in ("numTiles", "numTx", "raysPerTx", "maxBounces", "groupSize"))
valid = tensors["Valid"].reshape(shape) != 0
# Keep logical slot coordinates before compacting the valid payloads.
slot_indices = np.argwhere(valid)
hit_samples = tensors["HitPosWorld"].reshape(*shape, 3)[valid]
path_lengths = tensors["RoundTripDistanceM"].reshape(shape)[valid]
main.cpp, from the runnable C example.#
for (size_t slot = 0; slot < captured.valid.size(); ++slot) {
    if (!captured.valid[slot]) {
        continue; // Dense holes are not returns.
    }
    size_t index = slot;
    std::array<size_t, 5> coordinates{};
    for (int axis = 4; axis >= 0; --axis) {
        coordinates[axis] = index % captured.shape[axis];
        index /= captured.shape[axis];
    }
    // Coordinates are tile, transmitter, ray, bounce, receiver slot
    // (fastest).
    csv << ordinal << ',' << slot;
    for (auto coordinate : coordinates) {
        csv << ',' << coordinate;
    }
    csv << ',' << captured.distance[slot];
    for (int axis = 0; axis < 3; ++axis) {
        csv << ',' << captured.hit_world[3 * slot + axis];
    }
    csv << ',';
    // Only the colocated, one-bounce depth recipe has range = round-trip
    // / 2.
    if (depth) {
        csv << captured.distance[slot] / 2;
    }
    csv << '\n';
}

For GPU consumers, request linear CUDA memory with the synchronization described under mapping outputs. Contributions is not a CUDA image array. Valid is a validity value, not a completion signal.

Tiled captures#

Target several sensor prims from one RenderProduct to create pose-only tiles. The first sensor supplies the complete configuration for every tile; attributes on later sensors do not independently configure those tiles. Each tile uses the same active pattern with its own pose. numTx is the count per tile, and numTiles is the leading output dimension. Frame pose parameters refer to tile 0.

Use separate RenderProducts when sensors need independent pattern or propagation settings. Refer to independent products.

Allocated capacity#

Storage grows with all five dimensions, including invalid slots. Default channels use 76 bytes per slot; the geometry pair adds 40 bytes. Distance plus mandatory Valid uses 8 bytes per slot. These figures exclude pattern buffers, allocation overhead and multiple in-flight outputs.

Warning

A capture is limited to 4,194,304 slots. At that capacity, default channel payloads occupy 304 MiB, or 464 MiB with the geometry pair. Reduce the active pattern sizes, ray count, bounce count or receiver-group size to stay within this bound.