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:

Three grid geometries: spherical elements share an origin and vary direction, planar origins span YZ with parallel forward axes, and cylindrical origins vary in Z with azimuth directions.

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 SPHERICAL to sample elevation and azimuth from one origin. Row and column bounds are angles in degrees.

  • Choose PLANAR to sample parallel paths from origins spread across the YZ plane. Row and column bounds are Z and Y positions in meters.

  • Choose CYLINDRICAL to 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:

Three by five direct-return ranges from a steel wall 11 meters ahead, with longer oblique ranges than the center sample.

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#

  1. Read the accepted dimensions, then compare only nonzero Valid slots.

  2. The angular recipe has capacity 15 and its central direct return has range 11 m in the example scene.

  3. Oblique angular samples have longer ranges to the same front plane.

  4. The planar recipe keeps 15 slots with parallel rays and 11 m direct-return ranges.

  5. 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.