Set Up a Sensor#

Create a sensor, select a complete pattern and read its first capture. Start with a simple direct-return layout so each valid contribution has a clear geometric interpretation before adding more rays, bounces or receivers.

Before you start. Read Your First Lidar Sensor for the shared workflow and have the Python example or C example available.

Author the sensor and output#

The sensor prim carries the trace definition: the propagation controls and transmitter, receiver and ray patterns, plus their receiver mappings. The RenderProduct selects that sensor, and its RenderVar requests the Contributions output.

The base layer establishes the sensor and its RenderProduct. The 10 Hz frame rate is configured through the shared omni:sensor:frameRate attribute. One bounce keeps the examples’ path interpretation simple. The rotation aligns the sensor’s +X-forward basis with world +X in this Z-up, meter-unit scene. The USD Python snippets use an existing USD stage; keep these imports for the pattern recipes that follow. The base deliberately leaves patterns for those recipes to define:

#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([])

Choose a complete pattern#

The base layer leaves the transmitter, receiver and ray arrays empty. Complete it with Sample a Depth Grid or one of the other configuration recipes. A complete recipe makes four choices:

  1. Place the transmitter elements relative to the sensor.

  2. Assign a ray bundle to the stored transmitter pattern. Every element of that pattern emits the bundle in its own transmitter frame.

  3. Place the receiver elements relative to the sensor. Their poses are authored separately from the transmitter poses.

  4. Choose which receivers are evaluated for each transmitter’s paths through the receiver mapping.

The depth recipe starts with matching transmitter and receiver grids, one ray per transmitter and paired receivers. Refer to patterns and selection for the relationship between stored patterns and the active capture.

Author the stage units, up-axis and time-code rate on the root layer; sublayers do not supply stage metadata. Keep the referenced layers together.

The runnable examples compose the recipe with a steel enclosure. Its walls are 11 m from the sensor along world X/Z and 12 m along world Y. The scene uses the steel label. Refer to Non-Visual Sensor Materials for authoring labels.

Render and read a capture#

Load the composed root into an attached stage, complete its writes, and step its RenderProduct. The examples’ one-tile, one-bounce configuration allocates numTx * raysPerTx * groupSize slots. Read the output dimensions and Valid before consuming their payloads. A slot identifies one transmitter, one emitted ray and one receiver connection at the single allowed bounce. Only a valid slot supplies a contribution to your application’s processing.

The sensor returns paths rather than a completed detector result. For the depth recipe, the first useful processing step is to derive range from a valid direct path whose emission and receiver origins coincide. The conditions for that calculation are in Sample a Depth Grid and the channel descriptions.

Refer to the Python example and C example for the complete applications and their run commands. Attribute meanings are on USD Attributes; complete types and defaults are in Sensor API Schemas.

Verification#

  1. Run the depth recipe and inspect the accepted dimensions and nonempty Valid tensor.

  2. Read valid path distances as described in Sample a Depth Grid.

  3. A zero-length tensor is an empty output, not an allocated capture with no valid returns.

Potential issues#

Use the Generic wpm checks for confirming checks and recovery.

  • No capture. Required patterns may be empty or the output request may be missing. Refer to USD Attributes and Outputs.