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.
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#
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 |
|---|---|---|---|---|
|
|
|
world units |
Hit in translated-world coordinates and renderer/world units. Not necessarily absolute world coordinates or meters. |
|
|
|
m |
Hit in the sensor frame sampled using the firing offset, in meters. Motion sampling follows the firing-time limits. |
|
|
|
m |
Hit in the selected receiver’s frame, in meters. |
|
|
|
m |
Total path length from the emission origin through the scene hits to the receiver, in meters. |
|
|
|
m/s |
Path-length change rate in meters/second. Positive means increasing path length; this is not the object’s speed. |
|
|
|
count |
One-based emitted-path hit count through the current hit. Excludes material evaluations on the final receiver connection. |
|
|
|
ID |
Non-visual material ID at the hit, not an object or instance ID. |
|
|
|
unitless |
Absolute cosine between the shading normal and the incoming direction toward the previous path vertex. |
|
|
|
m |
Accumulated beam range/divergence quantity in meters. It is not the Gaussian sampling spread or a range measurement. |
|
|
|
ns |
Firing offset from |
|
|
|
relative scalar power |
Scalar return-transport power relative to unit source power, after propagation to the receiver. Not calibrated detector power or intensity. |
|
|
|
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 |
|---|---|---|---|---|
|
|
|
m |
Three triangle vertices in the receiver frame, in meters. |
|
|
|
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
DeltaTimeNsand 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 |
|---|---|---|---|
|
|
|
The five logical output dimensions. |
|
|
|
Number of active receivers; it can differ from the receiver-slot count. |
|
|
|
Allocated slot capacity, not the number of valid returns. |
|
|
|
Accepted selectors. Absent until the first valid configuration. |
|
|
|
Renderer frame identifier. |
|
|
|
Frame motion interval in nanoseconds. |
|
|
|
Sensor start/end positions in meters, for tile 0. |
|
|
|
Sensor start/end xyzw orientations, for tile 0. |
|
|
|
Selected grid descriptor: start, cell count, rows, columns, scan order and column wrap. |
|
|
|
Same value as |
|
|
|
Each is one for the generated selection. |
|
|
|
|
|
|
|
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]
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.