How to Use Newton's Sensor APIs for IMU, Contact Forces, and Raycasts

Newton's sensor APIs provide high-performance access to IMU measurements, contact forces, and depth images through Warp-accelerated kernels that automatically handle state attributes and label-based entity selection.

Newton's sensor APIs for IMU, contact forces, and raycasts enable robotics researchers and simulation engineers to extract physically accurate data without writing custom CUDA kernels. The newton-physics/newton repository provides three specialized sensor classes—SensorIMU, SensorContact, and SensorRaycast—that integrate directly with the physics engine's state management system. These sensors leverage NVIDIA Warp for GPU acceleration while exposing simple Python APIs that handle label matching, memory allocation, and coordinate frame transformations automatically.

Understanding Newton's Sensor Architecture

All three sensor classes follow a consistent design pattern that abstracts away the complexity of GPU kernel programming while maintaining simulation fidelity.

Label-Based Entity Selection

Rather than requiring manual index management, sensors accept string patterns or explicit labels that resolve to body, shape, or site indices. Internally, each sensor calls newton.utils.selection.match_labels to map patterns like "imu_*" or "ball" to concrete simulation entities.

Automatic State Attribute Requests

Sensors declare their data dependencies during construction to ensure memory allocation occurs before simulation begins. For example, SensorIMU automatically requests the body_qdd attribute (body accelerations), while SensorContact requests the force contact attribute. This guarantees that model.state() and model.contacts() allocate the required Warp arrays.

Warp-Accelerated Computation

The computational heavy lifting occurs in small, specialized Warp kernels that execute on CPU or GPU. The Python wrapper classes handle input preparation, kernel launches, and result conversion to wp.array or NumPy views, eliminating the need for manual CUDA programming.

Measuring Specific Force with SensorIMU

The SensorIMU class in newton/_src/sensors/sensor_imu.py measures linear accelerations and angular velocities at arbitrary sites within the simulation, accounting for gravity, Coriolis effects, and body accelerations.

IMU Kernel Implementation

The compute_sensor_imu_kernel function (lines 26-77) transforms site positions to body frames, computes specific force by combining linear acceleration, gravity subtraction, and Coriolis terms (cross(ω, cross(ω, r))), then rotates results into the sensor's local coordinate frame.

Code Example: Reading Accelerometer and Gyroscope Data

import warp as wp
import newton
from newton.sensors import SensorIMU

# Build a model with an IMU site

builder = newton.ModelBuilder()
builder.add_ground_plane()
body = builder.add_body(xform=wp.transform((0, 0, 0.5), wp.quat_identity()))
builder.add_shape_sphere(body, radius=0.1, label="ball")
builder.add_site(body, label="imu_0")

model = builder.finalize()

# Create sensor using label pattern matching

imu = SensorIMU(model, sites="imu_*")

# Simulate and update

solver = newton.solvers.SolverMuJoCo(model)
state = model.state()
solver.step(state, state, None, None, dt=1/240)

imu.update(state)

# Retrieve measurements as NumPy arrays

accelerometer = imu.accelerometer.numpy()  # Shape: (n_sensors, 3) [m/s²]

gyroscope = imu.gyroscope.numpy()        # Shape: (n_sensors, 3) [rad/s]

print(f"Accelerometer: {accelerometer}")
print(f"Gyroscope: {gyroscope}")

The sensor automatically requests the body_qdd attribute during construction (lines 60-63 in sensor_imu.py), ensuring the state object contains acceleration data.

Monitoring Contact Forces with SensorContact

The SensorContact class in newton/_src/sensors/sensor_contact.py aggregates net contact forces on bodies or shapes, optionally breaking down forces by counterpart entities.

How Contact Aggregation Works

The implementation uses an ObjectType enum (lines 41-52) to distinguish between TOTAL, SHAPE, and BODY aggregation modes. The _assemble_sensor_mappings static method (lines 58-86) builds compact shape-pair to reading-index mappings, pruning non-colliding pairs using the model's shape_contact_pairs set to minimize computation.

The select_aggregate_net_force_kernel (lines 75-146) reads the contact force array (contacts.force) and atomically accumulates forces for each sensor-counterpart pair.

Code Example: Monitoring Shape-to-Shape Contact Forces

import warp as wp
import newton
from newton.sensors import SensorContact

# Create a ball-on-ground scenario

builder = newton.ModelBuilder()
builder.add_ground_plane()
body = builder.add_body(xform=wp.transform((0, 0, 0.1), wp.quat_identity()))
builder.add_shape_sphere(body, radius=0.1, label="ball")
model = builder.finalize()

# Initialize contact sensor with label matching

contact_sensor = SensorContact(
    model,
    sensing_obj_shapes="ball",
    counterpart_shapes="ground_plane",
    include_total=True
)

solver = newton.solvers.SolverMuJoCo(model)
state = model.state()
contacts = model.contacts()

# Simulation step with contact update

solver.step(state, state, None, None, dt=1/240)
solver.update_contacts(contacts)

# Update the sensor

contact_sensor.update(state, contacts)

# Net forces (shape: (n_sensors, max_readings, 3))

forces = contact_sensor.net_force.numpy()
print(f"Contact forces shape: {forces.shape}")
print(f"Net contact force (N): {forces[0, 0]}")

SensorContact automatically requests the force contact attribute (lines 176-179 in sensor_contact.py). If you instantiate Contacts before the sensor, manually request the attribute via model.request_contact_attributes(['force']).

Generating Depth Images with SensorRaycast

The SensorRaycast class in newton/_src/sensors/sensor_raycast.py generates depth images by casting rays from a virtual camera into the simulation world, supporting both geometric shapes and particle systems.

Camera Model and Ray Casting

The sensor implements a pinhole camera model with configurable field-of-view, aspect ratio, and resolution. The _compute_camera_basis method (lines 27-54) orthonormalizes the camera's forward, up, and right vectors to ensure consistent ray directions across the image plane.

The sensor_raycast_kernel (imported from newton.geometry.raycast) executes per-pixel ray-shape intersection tests, returning the nearest hit distance. For particle systems, the sensor utilizes a spatial hash grid (wp.HashGrid) with marching steps bounded by MAX_PARTICLE_RAY_MARCH_STEPS (line 35) to prevent infinite loops. The clamp_no_hits_kernel (lines 26-33) post-processes results, replacing max_distance values with -1.0 to indicate no intersection occurred.

Code Example: Creating a Virtual Depth Camera

import warp as wp
import numpy as np
import newton
from newton.sensors import SensorRaycast

# Build a simple scene

builder = newton.ModelBuilder()
builder.add_ground_plane()
box = builder.add_body(xform=wp.transform((0, 0, 0.5), wp.quat_identity()))
builder.add_shape_box(box, half_extents=(0.2, 0.2, 0.2), label="box")
model = builder.finalize()

# Configure depth sensor looking downward

raycast = SensorRaycast(
    model,
    camera_position=(0.0, 0.0, 2.0),
    camera_direction=(0.0, 0.0, -1.0),
    camera_up=(0.0, 1.0, 0.0),
    fov_radians=np.radians(60.0),
    width=320,
    height=240,
    max_distance=5.0
)

# Run a simulation step (optional if bodies move)

solver = newton.solvers.SolverMuJoCo(model)
state = model.state()
solver.step(state, state, None, None, dt=1/120)

# Generate a depth image

raycast.update(state)
depth_image = raycast.get_depth_image_numpy()

print(f"Depth image shape: {depth_image.shape}")
print(f"Center pixel depth: {depth_image[120, 160]} meters")

Update camera poses at runtime using update_camera_pose or point_camera_at. To include particles in ray casting, call update(state, include_particles=True, particle_march_step=0.01).

Key Implementation Files

The sensor system spans multiple modules in the Newton repository:

File Purpose
newton/_src/sensors/sensor_imu.py SensorIMU implementation with compute_sensor_imu_kernel for specific force calculations
newton/_src/sensors/sensor_contact.py SensorContact with select_aggregate_net_force_kernel and ObjectType enum
newton/_src/sensors/sensor_raycast.py SensorRaycast camera model and particle ray-marching logic
newton/_src/geometry/raycast.py Low-level sensor_raycast_kernel for geometric intersection tests
newton/_src/sim/state.py State class definition containing body_qdd and other extended attributes
newton/_src/sim/contacts.py Contacts class with force attribute for contact sensors
newton/utils/selection.py match_labels utility for pattern-based entity resolution

Best Practices for Sensor Performance

To maximize throughput when utilizing Newton's sensor APIs:

  • Batch sensor updates – Update all sensors sequentially after each solver step rather than interleaving updates with simulation substeps. The underlying Warp kernels are lightweight and share device contexts efficiently.
  • Match device contexts – Ensure the model's compute device matches your execution target. Set model.device = wp.device('gpu:0') for GPU acceleration, or use the default CPU device for debugging.
  • Minimize particle overhead – When using SensorRaycast, enable include_particles=True only when your simulation actually contains particle systems. The spatial hash grid construction adds unnecessary overhead for purely rigid-body scenes.
  • Debug with NumPy views – Access the underlying Warp arrays directly (imu.accelerometer, contact_sensor.net_force, raycast.depth_image) and convert to NumPy using .numpy() for visualization with standard tools like Matplotlib or OpenCV.

Summary

  • Newton's sensor APIs provide ready-made solutions for extracting IMU data, contact forces, and depth images without custom CUDA kernel development.
  • SensorIMU computes specific force and angular velocity at arbitrary sites using the compute_sensor_imu_kernel in sensor_imu.py, automatically handling gravity and Coriolis effects.
  • SensorContact aggregates net contact forces via atomic operations in select_aggregate_net_force_kernel, supporting both total force measurements and per-counterpart breakdowns.
  • SensorRaycast generates depth images using geometric ray casting with optional particle intersection via spatial hashing, implemented in sensor_raycast.py.
  • All sensors utilize automatic attribute requests to ensure State and Contacts objects allocate required buffers (body_qdd, force) before simulation begins.

Frequently Asked Questions

How do I add an IMU sensor to a specific body in Newton?

Attach a site to your body using builder.add_site(body, label="imu_site"), then instantiate SensorIMU(model, sites="imu_site") or use wildcard patterns like "imu_*" to match multiple sites. The sensor automatically requests the body_qdd attribute from the model to access body accelerations during updates.

Can SensorContact measure forces between specific shape pairs?

Yes. Pass explicit labels to the sensing_obj_shapes and counterpart_shapes parameters when constructing SensorContact. The internal _assemble_sensor_mappings method filters the model's shape_contact_pairs set to include only relevant collisions, and the select_aggregate_net_force_kernel aggregates forces separately for each valid pair.

What coordinate frame does SensorRaycast use for depth values?

SensorRaycast returns distances in world coordinates (meters) along each camera ray. The _compute_camera_basis method orthonormalizes the camera's forward, up, and right vectors to ensure consistent ray directions. Depth values equal to max_distance are clamped to -1.0 by clamp_no_hits_kernel to indicate no intersection occurred.

Do I need to manually allocate memory for sensor data?

No. Newton's sensors automatically request required extended attributes during construction. SensorIMU requests body_qdd for accelerations, SensorContact requests the force contact attribute, and SensorRaycast allocates internal buffers for depth images. Simply call model.state() and model.contacts() after sensor instantiation to allocate the necessary Warp arrays.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →