Sample a Depth Grid#
Choose angular, planar or cylindrical sampling for direct-return distances.
Before you start. Create the base sensor and output in Set Up a Sensor. The USD Python tabs below assume that base has been authored on stage.
Choose a grid geometry#
Choose whether samples vary in direction, origin or both. The modes below generate element poses before the pattern’s position and orientation are applied. The recipes use them for matching transmitter and receiver grids:
Dots mark element origins and arrows mark their local +X axes. For a transmitter emitting one local +X ray, each arrow is an emission direction; for a receiver, it is a coordinate-frame axis. The illustrated 3 by 3 patterns are schematic, not to scale.#
Choose
SPHERICALto sample elevation and azimuth from one origin. Row and column bounds are angles in degrees.Choose
PLANARto sample parallel paths from origins spread across the YZ plane. Row and column bounds are Z and Y positions in meters.Choose
CYLINDRICALto sample azimuth at several heights. Row bounds are heights along Z in meters; column bounds are azimuths in degrees.
The grid attributes define
bounds, element order and the pattern pose. Keep receiver connectivity separate
from this geometry choice: the recipes below explicitly pair matching flat
indices using 1to1.
Angular sampling#
Use matching 3 by 5 spherical transmitter and receiver grids to sample elevation
from -10 to 10 degrees and azimuth from -20 to 20 degrees. One Free ray at the
transmitter origin follows each transmitter’s orientation. The same single-ray
bundle is reused by all 15 transmitter elements; it is not a separate bundle
for each direction. 1to1 with zero bloom connects each transmitter to the
matching receiver.
This gives 15 emitted rays and 15 slots. All origins coincide, so half the direct-return path distance is range. Uniform angular spacing is useful for range sampling, but does not represent calibrated pinhole-camera pixels. The layer below defines the two grids independently so their index pairing is explicit:
#usda 1.0
(
defaultPrim = "World"
metersPerUnit = 1
upAxis = "Z"
timeCodesPerSecond = 10
subLayers = [@generic_wpm_base.usda@]
)
over "World"
{
over "Sensor"
{
token[] omni:sensor:GenericWpm:tx:patternTypes = ["grid"]
token[] omni:sensor:GenericWpm:rx:patternTypes = ["grid"]
token[] omni:sensor:GenericWpm:ray:patternTypes = ["free"]
uint[] omni:sensor:GenericWpm:tx:rayPatternIndices = [0]
token[] omni:sensor:GenericWpm:tx:mappingModes = ["1to1"]
uint[] omni:sensor:GenericWpm:tx:mappingBloomFactors = []
# A 3-by-5 angular grid includes both nonperiodic endpoints.
token[] omni:sensor:GenericWpm:gridPatterns:tx:modes = ["SPHERICAL"]
uint[] omni:sensor:GenericWpm:gridPatterns:tx:numRows = [3]
uint[] omni:sensor:GenericWpm:gridPatterns:tx:numCols = [5]
float[] omni:sensor:GenericWpm:gridPatterns:tx:startRowUnits = [-10]
float[] omni:sensor:GenericWpm:gridPatterns:tx:endRowUnits = [10]
float[] omni:sensor:GenericWpm:gridPatterns:tx:startColUnits = [-20]
float[] omni:sensor:GenericWpm:gridPatterns:tx:endColUnits = [20]
token[] omni:sensor:GenericWpm:gridPatterns:tx:layouts = ["rowMajor"]
float3[] omni:sensor:GenericWpm:gridPatterns:tx:positions = [(0, 0, 0)]
float4[] omni:sensor:GenericWpm:gridPatterns:tx:orientations = [(0, 0, 0, 1)]
token[] omni:sensor:GenericWpm:gridPatterns:rx:modes = ["SPHERICAL"]
uint[] omni:sensor:GenericWpm:gridPatterns:rx:numRows = [3]
uint[] omni:sensor:GenericWpm:gridPatterns:rx:numCols = [5]
float[] omni:sensor:GenericWpm:gridPatterns:rx:startRowUnits = [-10]
float[] omni:sensor:GenericWpm:gridPatterns:rx:endRowUnits = [10]
float[] omni:sensor:GenericWpm:gridPatterns:rx:startColUnits = [-20]
float[] omni:sensor:GenericWpm:gridPatterns:rx:endColUnits = [20]
token[] omni:sensor:GenericWpm:gridPatterns:rx:layouts = ["rowMajor"]
float3[] omni:sensor:GenericWpm:gridPatterns:rx:positions = [(0, 0, 0)]
float4[] omni:sensor:GenericWpm:gridPatterns:rx:orientations = [(0, 0, 0, 1)]
# One transmitter-relative +X ray follows each transmitter's orientation.
uint[] omni:sensor:GenericWpm:freePatterns:ray:numElementsPerPattern = [1]
float3[] omni:sensor:GenericWpm:freePatterns:ray:positions = [(0, 0, 0)]
float4[] omni:sensor:GenericWpm:freePatterns:ray:orientations = [(0, 0, 0, 1)]
}
}
sensor = stage.GetPrimAtPath("/World/Sensor")
ns = "omni:sensor:GenericWpm:"
for role in ("tx", "rx"):
sensor.CreateAttribute(ns + role + ":patternTypes", Sdf.ValueTypeNames.TokenArray, custom=False).Set(["grid"])
grid = ns + "gridPatterns:" + role + ":"
sensor.CreateAttribute(grid + "modes", Sdf.ValueTypeNames.TokenArray, custom=False).Set(["SPHERICAL"])
sensor.CreateAttribute(grid + "numRows", Sdf.ValueTypeNames.UIntArray, custom=False).Set([3])
sensor.CreateAttribute(grid + "numCols", Sdf.ValueTypeNames.UIntArray, custom=False).Set([5])
sensor.CreateAttribute(grid + "startRowUnits", Sdf.ValueTypeNames.FloatArray, custom=False).Set([-10])
sensor.CreateAttribute(grid + "endRowUnits", Sdf.ValueTypeNames.FloatArray, custom=False).Set([10])
sensor.CreateAttribute(grid + "startColUnits", Sdf.ValueTypeNames.FloatArray, custom=False).Set([-20])
sensor.CreateAttribute(grid + "endColUnits", Sdf.ValueTypeNames.FloatArray, custom=False).Set([20])
sensor.CreateAttribute(grid + "layouts", Sdf.ValueTypeNames.TokenArray, custom=False).Set(["rowMajor"])
sensor.CreateAttribute(grid + "positions", Sdf.ValueTypeNames.Float3Array, custom=False).Set([(0, 0, 0)])
sensor.CreateAttribute(grid + "orientations", Sdf.ValueTypeNames.Float4Array, custom=False).Set([(0, 0, 0, 1)])
sensor.CreateAttribute(ns + "ray:patternTypes", Sdf.ValueTypeNames.TokenArray, custom=False).Set(["free"])
sensor.CreateAttribute(ns + "tx:rayPatternIndices", Sdf.ValueTypeNames.UIntArray, custom=False).Set([0])
sensor.CreateAttribute(ns + "tx:mappingModes", Sdf.ValueTypeNames.TokenArray, custom=False).Set(["1to1"])
sensor.CreateAttribute(ns + "tx:mappingBloomFactors", Sdf.ValueTypeNames.UIntArray, custom=False).Set([])
# One transmitter-relative +X ray follows each transmitter's orientation.
ray = ns + "freePatterns:ray:"
sensor.CreateAttribute(ray + "numElementsPerPattern", Sdf.ValueTypeNames.UIntArray, custom=False).Set([1])
sensor.CreateAttribute(ray + "positions", Sdf.ValueTypeNames.Float3Array, custom=False).Set([(0, 0, 0)])
sensor.CreateAttribute(ray + "orientations", Sdf.ValueTypeNames.Float4Array, custom=False).Set([(0, 0, 0, 1)])
The central sample is 11 m from the front wall. Oblique rays travel farther to reach that same plane:
Range derived from valid direct-return contributions. A flat wall does not produce constant radial range under angular sampling.#
Planar and cylindrical variants#
Use a planar grid when origins should be spaced across a surface and rays should remain parallel. This layer replaces both grids with YZ-plane origins facing +X. The dimensions and pairing stay the same, so this changes the sampling geometry without changing the 15-slot capacity. Every direct return from the front wall is then 11 m:
#usda 1.0
(
defaultPrim = "World"
metersPerUnit = 1
upAxis = "Z"
timeCodesPerSecond = 10
subLayers = [@generic_wpm_depth.usda@]
)
over "World"
{
over "Sensor"
{
# Rows are Z positions and columns are Y positions, in meters.
# Every cell points along +X; this is not a perspective camera.
token[] omni:sensor:GenericWpm:gridPatterns:tx:modes = ["PLANAR"]
float[] omni:sensor:GenericWpm:gridPatterns:tx:startRowUnits = [-1]
float[] omni:sensor:GenericWpm:gridPatterns:tx:endRowUnits = [1]
float[] omni:sensor:GenericWpm:gridPatterns:tx:startColUnits = [-2]
float[] omni:sensor:GenericWpm:gridPatterns:tx:endColUnits = [2]
token[] omni:sensor:GenericWpm:gridPatterns:rx:modes = ["PLANAR"]
float[] omni:sensor:GenericWpm:gridPatterns:rx:startRowUnits = [-1]
float[] omni:sensor:GenericWpm:gridPatterns:rx:endRowUnits = [1]
float[] omni:sensor:GenericWpm:gridPatterns:rx:startColUnits = [-2]
float[] omni:sensor:GenericWpm:gridPatterns:rx:endColUnits = [2]
}
}
sensor = stage.GetPrimAtPath("/World/Sensor")
for role in ("tx", "rx"):
grid = "omni:sensor:GenericWpm:gridPatterns:" + role + ":"
# Rows are Z positions and columns are Y positions, in meters.
sensor.GetAttribute(grid + "modes").Set(["PLANAR"])
sensor.GetAttribute(grid + "startRowUnits").Set([-1])
sensor.GetAttribute(grid + "endRowUnits").Set([1])
sensor.GetAttribute(grid + "startColUnits").Set([-2])
sensor.GetAttribute(grid + "endColUnits").Set([2])
Use a cylindrical grid when each row should have a different height while columns scan azimuth. This keeps 15 slots but changes rows from angular elevation to metric height. Origins move along Z, not around a cylinder:
#usda 1.0
(
defaultPrim = "World"
metersPerUnit = 1
upAxis = "Z"
timeCodesPerSecond = 10
subLayers = [@generic_wpm_depth.usda@]
)
over "World"
{
over "Sensor"
{
# Rows span heights in meters; columns retain -20..20 degree azimuths.
token[] omni:sensor:GenericWpm:gridPatterns:tx:modes = ["CYLINDRICAL"]
float[] omni:sensor:GenericWpm:gridPatterns:tx:startRowUnits = [-1]
float[] omni:sensor:GenericWpm:gridPatterns:tx:endRowUnits = [1]
token[] omni:sensor:GenericWpm:gridPatterns:rx:modes = ["CYLINDRICAL"]
float[] omni:sensor:GenericWpm:gridPatterns:rx:startRowUnits = [-1]
float[] omni:sensor:GenericWpm:gridPatterns:rx:endRowUnits = [1]
}
}
sensor = stage.GetPrimAtPath("/World/Sensor")
for role in ("tx", "rx"):
grid = "omni:sensor:GenericWpm:gridPatterns:" + role + ":"
# Rows span heights in meters; columns retain -20..20 degree azimuths.
sensor.GetAttribute(grid + "modes").Set(["CYLINDRICAL"])
sensor.GetAttribute(grid + "startRowUnits").Set([-1])
sensor.GetAttribute(grid + "endRowUnits").Set([1])
Attribute meanings are on USD Attributes; complete types and defaults are in Sensor API Schemas.
Verification#
Read the accepted dimensions, then compare only nonzero
Validslots.The angular recipe has capacity 15 and its central direct return has range 11 m in the example scene.
Oblique angular samples have longer ranges to the same front plane.
The planar recipe keeps 15 slots with parallel rays and 11 m direct-return ranges.
Compare these geometric relationships, rather than requiring an arbitrary valid-count value in a different scene.
Potential issues#
Use the Generic wpm checks for confirming checks and recovery.
Range cells do not match camera pixels. The grid samples angles or ray origins, without a calibrated camera projection. Refer to model limits.
Some slots are invalid. An intersection alone does not establish a positive-power path visible to a receiver. Refer to Outputs.