Source code for isaaclab_newton.sensors.imu.imu

# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md).
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause

from __future__ import annotations

import logging
from collections.abc import Sequence
from typing import TYPE_CHECKING

import warp as wp

from isaaclab.sensors.imu import BaseImu

from isaaclab_newton.physics import NewtonManager

from .imu_data import ImuData
from .kernels import imu_copy_kernel, imu_reset_kernel

if TYPE_CHECKING:
    from newton.sensors import SensorIMU as NewtonSensorIMU

    from isaaclab.sensors.imu import ImuCfg

logger = logging.getLogger(__name__)


[docs] class Imu(BaseImu): """Newton Inertial Measurement Unit (IMU) sensor. Wrapper around ``newton.sensors.SensorIMU`` providing angular velocity (gyroscope) and linear acceleration (accelerometer) in the sensor's body frame. """ cfg: ImuCfg """The configuration parameters.""" __backend_name__: str = "newton" """The name of the backend for the IMU sensor."""
[docs] def __init__(self, cfg: ImuCfg): """Initializes the Newton IMU sensor. Registers a site request and the ``body_qdd`` state attribute with :class:`NewtonManager`. The site is injected into prototype builders before replication so it ends up in each world. Args: cfg: The configuration parameters. """ super().__init__(cfg) self._data = ImuData() self._sensor_index: int | None = None self._newton_sensor: NewtonSensorIMU | None = None offset_xform = wp.transform(cfg.offset.pos, cfg.offset.rot) self._site_label: str = NewtonManager.cl_register_site(cfg.prim_path, offset_xform) NewtonManager.request_extended_state_attribute("body_qdd") logger.info(f"IMU '{cfg.prim_path}': site registered (label='{self._site_label}')")
def __str__(self) -> str: """String representation of the sensor instance.""" return ( f"IMU sensor @ '{self.cfg.prim_path}': \n" f"\tbackend : newton\n" f"\tupdate period (s) : {self.cfg.update_period}\n" f"\tnumber of sensors : {self._num_envs}\n" ) """ Properties """ @property def data(self) -> ImuData: """The IMU sensor data.""" self._update_outdated_buffers() return self._data """ Operations """ def reset(self, env_ids: Sequence[int] | None = None, env_mask: wp.array | None = None): """Reset the sensor for the given environments. Zeroes out angular velocity and linear acceleration buffers for the specified environments. Args: env_ids: Environment indices to reset. Defaults to all environments. env_mask: Boolean mask of environments to reset. Mutually exclusive with *env_ids*. """ env_mask = self._resolve_indices_and_mask(env_ids, env_mask) super().reset(None, env_mask) wp.launch( imu_reset_kernel, dim=self._num_envs, inputs=[env_mask, self._data._lin_acc_b, self._data._ang_vel_b], device=self._device, ) """ Implementation """ def _initialize_impl(self): """PHYSICS_READY callback: resolves site indices and creates the native SensorIMU.""" super()._initialize_impl() site_map = NewtonManager._cl_site_index_map num_envs = self._num_envs if self._site_label not in site_map: raise ValueError( f"IMU '{self.cfg.prim_path}': site label '{self._site_label}' " "not found in NewtonManager._cl_site_index_map." ) global_idx, per_world = site_map[self._site_label] if per_world is None: # Global site (body=-1, i.e. world frame): replicate across envs. site_indices = [global_idx] * num_envs else: if len(per_world) != num_envs: raise ValueError( f"IMU '{self.cfg.prim_path}': site has {len(per_world)} world entries, expected {num_envs}." ) site_indices: list[int] = [] for env_idx, world_sites in enumerate(per_world): if len(world_sites) != 1: raise ValueError( f"IMU '{self.cfg.prim_path}': pattern matched {len(world_sites)} " f"bodies in env {env_idx}, expected exactly 1." ) site_indices.append(world_sites[0]) self._sensor_index = NewtonManager.add_imu_sensor(site_indices) self._newton_sensor = NewtonManager._newton_imu_sensors[self._sensor_index] self._data.create_buffers(num_envs=num_envs, device=self._device) logger.info(f"IMU initialized: {num_envs} envs, sensor_index={self._sensor_index}") def _update_buffers_impl(self, env_mask: wp.array): """Copies accelerometer/gyroscope data from native Newton sensor into owned buffers.""" if self._newton_sensor is None: raise RuntimeError( f"IMU '{self.cfg.prim_path}': sensor not initialized. " "Access sensor data only after sim.reset() has been called." ) wp.launch( imu_copy_kernel, dim=self._num_envs, inputs=[ env_mask, self._newton_sensor.accelerometer, self._newton_sensor.gyroscope, self._timestamp, ], outputs=[self._data._lin_acc_b, self._data._ang_vel_b], device=self._device, ) def _invalidate_initialize_callback(self, event): """Clears references to the native Newton sensor and re-registers site/attributes. Re-registering here ensures the site and ``body_qdd`` attribute survive a non-teardown stop/reinit cycle. During ``NewtonManager.close()``, Newton state is cleared after ``STOP`` so stale registrations from old sensors cannot leak into the next context. """ super()._invalidate_initialize_callback(event) self._newton_sensor = None self._sensor_index = None # Zero out data buffers so stale data is not served between STOP and reinit. if self._data._ang_vel_b is not None: self._data._ang_vel_b.zero_() if self._data._lin_acc_b is not None: self._data._lin_acc_b.zero_() # Re-register so a subsequent start_simulation picks them up. offset_xform = wp.transform(self.cfg.offset.pos, self.cfg.offset.rot) self._site_label = NewtonManager.cl_register_site(self.cfg.prim_path, offset_xform) NewtonManager.request_extended_state_attribute("body_qdd")