Reading Sensor PointClouds#
Lidar and radar PointCloud RenderVars are composite render outputs. Mapping
one output exposes one named tensor per requested payload channel, model-added
Counts and Flags tensors, and CPU params that describe the output.
Counts[0] is the delivered-entry count; delivered indices are
[0, Counts[0]). When invalid entries may be present, use the Flags
VALID bit (Flags[i] & 0x40) to determine per-entry validity.
For the output container format, refer to Sensor Outputs. For sensor-specific channel meanings, refer to Lidar Sensors and Radar Sensors.
Python#
def read_lidar_pointcloud(frame) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
"""Map the PointCloud composite tensor to CPU and return valid point channels."""
with frame.render_vars[POINTCLOUD_RENDER_VAR].map(device=ovrtx.Device.CPU) as pointcloud:
coordinates = np.from_dlpack(pointcloud["Coordinates"])
counts = np.from_dlpack(pointcloud["Counts"])
intensity = np.from_dlpack(pointcloud["Intensity"])
time_offset_ns = np.from_dlpack(pointcloud["TimeOffsetNs"])
# Counts bounds the delivered entries. This scene sets
# includeInvalidPoints=false, so the delivered entries are all valid.
valid_count = int(counts[0])
points = np.asarray(coordinates[:, :valid_count].T)
return (
points.copy(),
np.asarray(intensity[:valid_count]).copy(),
np.asarray(time_offset_ns[:valid_count]).copy(),
)
def read_radar_pointcloud(frame) -> tuple[np.ndarray, np.ndarray]:
"""Map the PointCloud composite tensor to CPU and return valid detections."""
with frame.render_vars[POINTCLOUD_RENDER_VAR].map(device=ovrtx.Device.CPU) as pointcloud:
coordinates = np.from_dlpack(pointcloud["Coordinates"])
counts = np.from_dlpack(pointcloud["Counts"])
flags = np.from_dlpack(pointcloud["Flags"])
radial_velocity = np.from_dlpack(pointcloud["RadialVelocityMs"])
# Counts bounds delivered entries; Flags determines per-entry validity.
detection_count = int(counts[0])
valid = (np.asarray(flags[:detection_count]) & 0x40) != 0
points = np.asarray(coordinates[:, :detection_count].T)[valid]
return (
points.copy(),
np.asarray(radial_velocity[:detection_count])[valid].copy(),
)
In Python, frame.render_vars is keyed by the full RenderVar prim path, for
example frame.render_vars["/World/Render/Vars/PointCloud"] in the lidar and
radar examples. Mapping that output returns the composite PointCloud data.
Index by exact channel name, then pass the channel object to a DLPack consumer
such as NumPy. The mapping is consumer-owned: a held DLPack view keeps the data
valid past unmap, so copy only when data must outlive the last held view.
C#
// Read one mapped lidar PointCloud. Counts bounds the delivered entries. The
// scene sets includeInvalidPoints=false, so the delivered entries are valid.
bool print_pointcloud_summary(ovrtx_render_var_output_t const& output)
{
// The USD also requests Coordinates; this example only needs scalar and
// time channels for the printed statistics.
int32_t const* counts_data = cpu_tensor_data<int32_t>(output, "Counts");
float const* intensity_data = cpu_tensor_data<float>(output, "Intensity");
int32_t const* time_offset_data =
cpu_tensor_data<int32_t>(output, "TimeOffsetNs");
if (!counts_data || !intensity_data || !time_offset_data) {
return false;
}
// With invalid returns dropped by the scene, Counts is also the valid-point
// count.
int32_t const valid_point_count = counts_data[0];
if (valid_point_count <= 0) {
std::cerr << "Expected at least one valid lidar point, got "
<< valid_point_count << "\n";
return false;
}
double intensity_sum = 0.0;
int32_t max_time_offset_ns = time_offset_data[0];
// Only the first Counts entries are delivered in each per-point channel.
for (int32_t i = 0; i < valid_point_count; ++i) {
const float intensity = intensity_data[i];
if (!std::isfinite(intensity)) {
std::cerr << "Intensity tensor contained a non-finite value\n";
return false;
}
intensity_sum += intensity;
max_time_offset_ns = std::max(max_time_offset_ns, time_offset_data[i]);
}
const double mean_intensity =
intensity_sum / static_cast<double>(valid_point_count);
std::cout << "valid points=" << valid_point_count
<< ", mean intensity=" << mean_intensity
<< ", max point time offset to point cloud frame start=" << max_time_offset_ns << " ns\n";
return true;
}
// Read the mapped PointCloud tensors for one step. Counts bounds the delivered
// detection entries; Flags determines per-entry validity.
bool print_pointcloud_step(ovrtx_render_var_output_t const& output,
int step,
int* moving_detection_count,
float* max_abs_radial_velocity)
{
// The USD requests the payload channels; the model auto-enables Counts and
// Flags.
int32_t const* counts_data = cpu_tensor_data<int32_t>(output, "Counts");
uint8_t const* flags_data = cpu_tensor_data<uint8_t>(output, "Flags");
float const* rcs_data = cpu_tensor_data<float>(output, "RCS");
float const* radial_velocity_data =
cpu_tensor_data<float>(output, "RadialVelocityMs");
if (!counts_data || !flags_data || !rcs_data || !radial_velocity_data) {
return false;
}
// Counts is a scalar tensor containing the delivered detection count.
int32_t const detection_count = counts_data[0];
if (detection_count <= 0) {
std::cerr << "Expected at least one radar detection, got "
<< detection_count << "\n";
return false;
}
const size_t detection_entries = static_cast<size_t>(detection_count);
constexpr uint8_t kValidFlag = 0x40;
size_t valid_detection_count = 0;
for (size_t i = 0; i < detection_entries; ++i) {
if ((flags_data[i] & kValidFlag) != 0) {
++valid_detection_count;
}
}
if (valid_detection_count == 0) {
std::cerr << "Radar PointCloud contained no valid detections\n";
return false;
}
// RCS is the radar cross section channel returned for each detection.
bool found_finite_rcs = false;
float min_rcs = std::numeric_limits<float>::infinity();
float max_rcs = -std::numeric_limits<float>::infinity();
// RCS values are regular float channel entries in the composite output.
for (size_t i = 0; i < detection_entries; ++i) {
if ((flags_data[i] & kValidFlag) == 0) {
continue;
}
const float rcs_value = rcs_data[i];
if (std::isfinite(rcs_value)) {
found_finite_rcs = true;
min_rcs = std::min(min_rcs, rcs_value);
max_rcs = std::max(max_rcs, rcs_value);
}
}
if (!found_finite_rcs) {
std::cerr
<< "RCS tensor was present but contained no finite values for valid detections\n";
return false;
}
bool found_finite_radial_velocity = false;
float min_radial_velocity = std::numeric_limits<float>::infinity();
float max_radial_velocity = -std::numeric_limits<float>::infinity();
int step_moving_detection_count = 0;
// Approaching objects report negative radial velocity; track magnitude when
// reporting detections from the moving cube.
for (size_t i = 0; i < detection_entries; ++i) {
if ((flags_data[i] & kValidFlag) == 0) {
continue;
}
const float radial_velocity = radial_velocity_data[i];
if (!std::isfinite(radial_velocity)) {
continue;
}
found_finite_radial_velocity = true;
min_radial_velocity = std::min(min_radial_velocity, radial_velocity);
max_radial_velocity = std::max(max_radial_velocity, radial_velocity);
const float abs_radial_velocity = std::abs(radial_velocity);
*max_abs_radial_velocity =
std::max(*max_abs_radial_velocity, abs_radial_velocity);
if (abs_radial_velocity > kMovingRadialVelocityThresholdMs) {
++step_moving_detection_count;
}
}
if (!found_finite_radial_velocity) {
std::cerr
<< "RadialVelocityMs tensor contained no finite values for valid detections\n";
return false;
}
*moving_detection_count += step_moving_detection_count;
std::cout << " step " << step
<< ": valid points=" << valid_detection_count
<< ", RCS min/max=[" << min_rcs << ", " << max_rcs << "]"
<< ", radial velocity min/max=[" << min_radial_velocity << ", "
<< max_radial_velocity << "]"
<< " m/s\n";
return true;
}
In C, map the PointCloud render var with
ovrtx_map_render_var_output(), find named tensors in
ovrtx_render_var_output_t::tensors, then unmap with
ovrtx_unmap_render_var_output().
CPU and CUDA Mapping#
CPU mapping is easiest for examples, logging, and validation. CUDA mapping is available for GPU point-cloud pipelines:
Python uses
map(device=ovrtx.Device.CUDA).C uses
OVRTX_MAP_DEVICE_TYPE_CUDAfor linear CUDA memory.
Each mapped DLTensor object’s device field reports its actual storage
device type and ID. Explicit CPU mapping returns kDLCPU tensors, and
explicit CUDA mapping returns linear kDLCUDA tensors. In C,
OVRTX_MAP_DEVICE_TYPE_DEFAULT lets the runtime choose the most efficient
representation; inspect device on every returned DLTensor. Params remain
CPU-resident for every mapping mode.
Consume CUDA tensors with GPU-aware code and respect the synchronization hints
on the mapped output. CUDA_ARRAY mapping is intended for image-style
outputs, not point-cloud channel tensors.
Rules#
Use
Counts[0]as the delivered-entry count before slicing or iterating per-point or per-detection tensors.Do not treat
Countsas proof that every delivered entry is valid. When invalid entries may be present, keep entries whoseFlagsVALIDbit is set (Flags[i] & 0x40).Treat requested payload channel names as part of the data contract; they must match the
channelsauthored on thePointCloudRenderVar.CountsandFlagsare auto-enabled by lidar and radar models.Other payload channels are present only when requested.
In Python the mapping is consumer-owned: a held DLPack consumer view (for example, a NumPy array) keeps the buffer valid even after unmap, so lifetime is not a manual concern — copy only if you need data beyond the last view.
In C, mapped tensor pointers are valid only until unmap; copy CPU data, or synchronize and copy GPU data, before unmapping if it must outlive the mapping.
Note
The shapes shown below are the current non-tiled PointCloud layouts.
Read tensor shapes from the mapped output descriptor at runtime instead of
hard-coding allocation sizes.
Lidar Point Cloud#
Produced by the lidar sensor model.
ovrtx_render_var_output_t
name: "/World/Render/Vars/PointCloud"
type: "PointCloud"
tensors (device reported by each DLTensor; Nmax is the per-frame allocation bound):
"Coordinates" -- [3, Nmax] float32 (x, y, z per point; spherical or cartesian per coordsType)
"Intensity" -- [Nmax] float32 (return intensity per point)
"Flags" -- [Nmax] uint8 (validity / classification flags; auto-enabled)
"Counts" -- [1] int32 (number of delivered point entries this frame; auto-enabled)
"TimeOffsetNs" -- [Nmax] int32 (per-point time offset from frame start)
"EmitterId" -- [Nmax] uint32 (emitter / beam index)
"ChannelId" -- [Nmax] uint32 (channel / detector index)
"MaterialId" -- [Nmax] uint32 (material of hit surface)
"TickId" -- [Nmax] uint32 (tick / scan index)
"HitNormal" -- [Nmax, 3] float32 (surface normal at hit)
"Velocity" -- [Nmax, 3] float32 (velocity at hit point)
"ObjectId" -- [Nmax, 4] uint32 (128-bit instance ID, 4x uint32)
"EchoId" -- [Nmax] uint8 (echo / return index)
"TickState" -- [Nmax] uint8 (per-tick state)
params (always CPU):
"frameId" -- uint64
"timestampNs" -- uint64
"modality" -- uint32
"coordsType" -- uint32 (spherical | cartesian)
"frameOfReference" -- uint16 (sensor | parent | world | custom)
"motionCompensationState" -- uint16
"modelToAppTransform" -- float32 [4, 4]
"frameStartTimeStampNs" -- uint64
"frameStartPosM" -- float32 [3]
"frameStartOrientation" -- float32 [4]
"frameEndTimeStampNs" -- uint64
"frameEndPosM" -- float32 [3]
"frameEndOrientation" -- float32 [4]
"maxPoints" -- uint32 (maximum point allocation; use Counts for the delivered per-frame range)
Field-by-field meaning, units, and visualization patterns are in Lidar Sensors.
Radar Point Cloud#
Produced by the radar sensor model.
ovrtx_render_var_output_t
name: "/World/Render/Vars/PointCloud"
type: "PointCloud"
tensors (device reported by each DLTensor; Nmax is the per-frame allocation bound):
"Coordinates" -- [3, Nmax] float32 (range, azimuth, elevation -- or x, y, z per coordsType)
"RCS" -- [Nmax] float32 (radar cross section)
"RadialVelocityMs" -- [Nmax] float32 (radial velocity, m/s -- negative for approaching)
"TimeOffsetNs" -- [Nmax] int32 (per-detection time offset from frame start)
"Flags" -- [Nmax] uint8 (auto-enabled)
"Counts" -- [1] int32 (number of delivered detections this frame; auto-enabled)
params (always CPU):
"frameId" -- uint64
"timestampNs" -- uint64
"modality" -- uint32
"coordsType" -- uint32
"frameOfReference" -- uint16
"modelToAppTransform" -- float32 [4, 4]
"frameStartTimeStampNs" -- uint64
"frameStartPosM" -- float32 [3]
"frameStartOrientation" -- float32 [4]
"frameEndTimeStampNs" -- uint64
"frameEndPosM" -- float32 [3]
"frameEndOrientation" -- float32 [4]
"maxPoints" -- uint32 (maximum detection allocation; use Counts for the delivered per-frame range)
Field-by-field meaning is in Radar Sensors.