Outputs#
The Acoustic generic wpm model delivers a SignalWays composite output
containing six channels and seven parameters. Each signal way holds the sampled
echo envelope for one receiving mount on one channel id. The array topology
determines the number and order of signal ways; USD Attributes describes
how to configure it.
SignalWays has no
Counts channel, no per-point VALID flag, no coordinates, no
coordinate-type, frame-of-reference or pose params, and no material identifier:
the frame’s extent comes from its params, and nothing in it is a position. Use the layout and validity rules below when reading it.
Sensor Outputs describes the shared output container.
Requesting the output#
Request SignalWays with a single RenderVar whose sourceName is
SignalWays. This composite output includes all six channels and the frame
header. The model ignores the RenderVar’s channels list; individual
channels cannot be selected.
Channel tensors are stored in device memory; parameters are stored on the host. Map the output to CPU memory to read it with NumPy, or pass the device tensors to a CUDA-aware consumer. See Sensor Outputs for both approaches and the C API and Python API for the container definitions.
Set Up an Acoustic Sensor shows the complete sensor setup. Output Configuration explains RenderProducts and RenderVars.
The acoustic example scene authors this request:
#usda 1.0
# The acoustic model publishes one composite RenderVar, "SignalWays". It
# carries every per-sample channel plus the frame header as parameters, so
# the product requests a single var rather than one per channel.
def "Render"
{
def "Products"
{
def RenderProduct "AcousticProduct"
{
rel camera = </World/Acoustic>
rel orderedVars = [<../../Vars/SignalWays>]
}
}
def "Vars"
{
def RenderVar "SignalWays"
{
uniform string sourceName = "SignalWays"
}
}
}
from pxr import Gf, Sdf
world = stage.DefinePrim("/World", "Xform")
render = stage.DefinePrim("/World/Render", "")
products = stage.DefinePrim("/World/Render/Products", "")
acoustic_product = stage.DefinePrim("/World/Render/Products/AcousticProduct", "RenderProduct")
acoustic_product.CreateRelationship("camera", custom=False).SetTargets(["/World/Acoustic"])
acoustic_product.CreateRelationship("orderedVars", custom=False).SetTargets(
["/World/Render/Vars/SignalWays"]
)
vars = stage.DefinePrim("/World/Render/Vars", "")
signal_ways = stage.DefinePrim("/World/Render/Vars/SignalWays", "RenderVar")
signal_ways.CreateAttribute(
"sourceName", Sdf.ValueTypeNames.String, custom=False, variability=Sdf.VariabilityUniform
).Set("SignalWays")
The model takes its frame rate from the shared omni:sensor:frameRate
attribute, which the example scene sets to 10 Hz, and it declares no
partial-output mode. How the renderer schedules captures against your
simulation steps is on Sensor Scheduling. The model also needs the
renderer’s motion BVH active. Select AUTO or ENABLE when creating the
renderer; the
switch and its modes are under
Motion BVH.
Signal ways#
A signal way contains the envelope recorded by one receiving mount on one channel id, plus transmitter, receiver, channel and firing-time metadata. Each firing event transmits from one mount to a receiver group. Receivers that detect an echo populate their corresponding signal ways. For example:
A firing event with |
Produces |
|---|---|
Transmitter: sensor mount Receiver group: Channel id: |
Three signal ways, one per receiver: 0 to 0, 0 to 1 and 0 to 2, all
on channel id |
Events in the same firing sequence share a signal way when they use the same
receiver and channel id, even if their transmitters differ. Their echoes
combine, while TransmitterIds and TimeOffsetNs can identify only one
event. Assign separate channel ids when events must remain distinguishable;
see USD Attributes and Lay Out a Transducer Array.
A channel id is a firing event’s integer channel label. A channel is a
named tensor in the SignalWays output. See the
glossary for other uses.
Signal-way layout#
Each frame reserves numSignalWays = mountCount * channelCount slots,
one for every combination of mount and distinct channel id used across all
firing sequences. Slots are allocated even for combinations that receive no
echo.
The slot index is receiverIndex * channelCount + channelId:
receiverIndexis the mount’s zero-based position in instance order.channelCountis the number of distinct channel ids across all firing sequences.channelIdis the firing event’s channel id. Channel ids must be contiguous from zero.
Each frame contains echoes from one firing sequence. A slot’s Flags entry
has the VALID bit (0x40) set when its receiver received an echo in that
frame. numSignalWays describes the allocated layout, not the number of
valid echoes.
Reshape Amplitudes to [numSignalWays, numSamplesPerSignalWay] to get
one envelope per row. The five metadata channels describe those rows; their
entry at index w describes signal way w. See Channels for tensor
shapes and read bounds, and the
attribute reference
for configuration limits.
Three mounts and two channel ids allocate six signal ways, shown here with
320 samples per envelope. Metadata entries correspond to envelope rows;
Flags identifies rows that received echoes. The range axis uses the
default sample duration.#
Channels#
Every channel is a flat tensor of E entries, where E is the
numElements param: signal ways times samples per signal way. Only
Amplitudes uses all of them; the other five describe a signal way as a whole.
Channel |
Type |
Shape |
Unit |
Meaning |
|---|---|---|---|---|
|
|
|
unitless |
Index of the transmitting mount, the firing event’s |
|
|
|
unitless |
Index of the receiving mount for the signal way at this index: a mount index, not a receiver-group index. One entry per signal way. |
|
|
|
unitless |
Channel id of the firing event that produced the signal way, as authored
in the firing sequence’s |
|
|
|
unitless |
Echo amplitude of one envelope sample. The one genuinely per-sample
channel: sample s of signal way w is entry
|
|
|
|
ns |
Firing time of the signal way’s event within its firing sequence, the
authored |
|
|
|
unitless |
The |
In both operating modes, TransmitterIds, ReceiverIds, ChannelIds,
TimeOffsetNs and Flags use one entry per signal way. Entry w
describes row w of the reshaped Amplitudes tensor. Entries beyond
numSignalWays are zero. A signal way with no echo also has zero metadata,
so use Flags to distinguish it from a valid echo whose identifiers and
time offset are all zero; see Validity.
TransmitterIds, ReceiverIds and ChannelIds are float32 tensors.
Cast their values to integers before using them as indices, as shown in
Reading an envelope.
Params#
Every frame includes the following seven CPU-resident parameters as tensors
of shape [1], including frames that were not traced.
Param |
Type |
Meaning |
|---|---|---|
|
|
The model’s own frame counter. It advances with every frame the renderer does not discard, traced or not, and a live reconfiguration does not reset it. |
|
|
Signal ways the frame is laid out for: one for every pairing of a mount,
as receiver, with a channel id used anywhere in the firing sequences. It
counts signal ways with no echo too; the |
|
|
Envelope samples per signal way. A model constant, 320, not derived from
|
|
|
|
|
|
Zero-based index of the firing sequence this frame traced, in the
instance-name order of USD Attributes, so |
|
|
The authored |
|
|
The authored |
Use numSignalWays and numSamplesPerSignalWay to reshape the
numElements samples. Read these dimensions from the output params.
Count valid signal ways from Flags; no parameter reports this count.
In basic mode, sampleDuration controls range spacing. Mounts and distinct
firing-sequence channel ids determine numSignalWays; receiver groups
determine which signal ways receive echoes. The 320-sample length and layout
are fixed. See USD Attributes for these controls and the
enableInterference mode selector, and Validity for the basic-mode
noise bounds noiseMin and noiseMax.
Validity#
Read the first numSignalWays entries of Flags. Signal way w
received an echo when Flags[w] & 0x40 is nonzero. In both operating modes,
the model sets only this VALID bit. It applies to the whole envelope,
not individual samples. Bound amplitude reads by numElements.
See Sensor Outputs for validity-bit handling.
Basic mode adds uniform noise even to envelopes with no echo. Nonzero
amplitude therefore does not establish validity; use Flags.
noiseMin and noiseMax
set the noise bounds.
Startup frames#
The first frames after sensor startup contain no echo data and have no identifying parameter. Discard a few frames before reading envelopes; the example takes three warm-up steps (Set Up an Acoustic Sensor). Renderer warm-up for texture streaming and path-tracing convergence is described in Output Configuration. If no frame contains echo data, see the Symptom Index.
Reading an envelope#
Map SignalWays to CPU memory. Reshape Amplitudes using
numSignalWays and numSamplesPerSignalWay, then read the first
numSignalWays identity entries and cast them to integers. This Python
example handles frames with no signal ways before reshaping. The acoustic
example has no C counterpart:
def read_acoustic_signal_ways(frame) -> dict:
"""Map the SignalWays composite tensor to CPU and return one frame of echoes."""
with frame.render_vars[SIGNAL_WAYS_RENDER_VAR].map(device=ovrtx.Device.CPU) as signal_ways:
def param(name: str) -> float:
return float(np.from_dlpack(signal_ways.params[name])[0])
num_signal_ways = int(param("numSignalWays"))
num_samples = int(param("numSamplesPerSignalWay"))
if num_signal_ways == 0 or num_samples == 0:
return {"num_signal_ways": 0, "num_samples": 0}
num_elements = num_signal_ways * num_samples
def channel(name: str) -> np.ndarray:
return np.from_dlpack(signal_ways[name])[:num_elements]
return {
"num_signal_ways": num_signal_ways,
"num_samples": num_samples,
"sample_duration_s": param("sampleDurationUs") * 1.0e-6,
"center_frequency_hz": param("centerFrequencyHz"),
"cycle_index": int(param("cycleIndex")),
"envelopes": channel("Amplitudes").reshape(num_signal_ways, num_samples).copy(),
# The identifier channels carry one entry per signal way rather than one per
# element, so only the first num_signal_ways entries are meaningful.
"transmitter_ids": channel("TransmitterIds")[:num_signal_ways].astype(int),
"receiver_ids": channel("ReceiverIds")[:num_signal_ways].astype(int),
"channel_ids": channel("ChannelIds")[:num_signal_ways].astype(int),
}
The function also carries sampleDurationUs out of the params, converted to
seconds, because the next step needs it. Mapping, synchronization and the
lifetime of a mapped output are the same for every composite output and are
explained on Sensor Outputs.
Sample ranges#
In basic mode (enableInterference false, the default), bin s covers
round-trip times from (s + 1) Δt up to (s + 2) Δt, excluding the upper
bound. Here Δt is sampleDurationUs converted to seconds. Multiply either
time bound by 343.21 m/s and divide by two to obtain its range bound.
Read the duration from the frame parameters because it can change at runtime.
The example plots each sample at the lower bound of its range bin:
import numpy as np
def sample_ranges_m(frame_data: dict) -> np.ndarray:
"""Return the lower range bound, in meters, of each envelope bin."""
sample_index = np.arange(frame_data["num_samples"], dtype=np.float64)
# Bin s holds round-trip times [(s + 1) dt, (s + 2) dt).
return (sample_index + 1) * frame_data["sample_duration_s"] * 343.21 / 2.0
At the default sampleDuration of 102.4 µs, samples are about 1.8 cm apart
in range, and the 320-sample envelope covers about 5.6 m. Selecting the peak
sample gives a range estimate at that spacing.
Increasing sampleDuration extends the range covered by the envelope but
increases the spacing between samples. Decreasing it gives finer spacing over
a shorter range. Coverage remains subject to the model’s
fixed propagation limits.
Multiple propagation paths can produce several peaks from one pulse. Ground or secondary-surface reflections add longer paths; different points on an extended reflector also have different path lengths. The strongest sample can therefore lie beyond the nearest surface. When estimating one range from the peak, account for this ambiguity and the sample spacing. In the example, the strongest sample identifies the concrete wall 3.0 m ahead of the sensor.