Skip to content

Commit abbf838

Browse files
Rename IMU sensor to PVA and add real IMU sensor (#5247)
## Summary - Renamed the existing IMU sensor (pose + velocity + acceleration) to **PVA** (`Pva`, `PvaCfg`, `PvaData`, `BasePva`, `BasePvaData`) across both `isaaclab` and `isaaclab_physx` packages. - Created a new lightweight **IMU** sensor (`Imu`, `ImuCfg`, `ImuData`, `BaseImu`, `BaseImuData`) that only provides the two physical quantities a real inertial measurement unit measures: **angular velocity** (gyroscope) and **linear acceleration** (accelerometer). - Updated all consumers: observation functions (`pva_orientation`, `pva_projected_gravity`, `imu_ang_vel`, `imu_lin_acc`), mock interfaces, tests, demo scripts, and changelogs. ### Why The old "IMU" sensor was a misnomer — it provided full pose, velocity, and acceleration data, far more than a real IMU measures. The rename clarifies the API: - **PVA** = full state sensor (pose, velocity, acceleration) for simulation use - **IMU** = realistic sensor model (angular velocity + linear acceleration only) ### What changed | Area | Change | |------|--------| | `isaaclab/sensors/imu/` → `isaaclab/sensors/pva/` | Renamed all classes, factory, config | | `isaaclab_physx/sensors/imu/` → `isaaclab_physx/sensors/pva/` | Renamed PhysX impl, kernels | | New `isaaclab/sensors/imu/` | Lightweight IMU base classes | | New `isaaclab_physx/sensors/imu/` | PhysX IMU impl with simplified Warp kernels | | `observations.py` | `imu_orientation`→`pva_orientation`, `imu_projected_gravity`→`pva_projected_gravity` | | Mock interfaces | Split `MockImu` (2 fields) / `MockPva` (8 fields) | | Tests | `test_pva.py` (8 tests), `test_imu.py` (8 tests), mock tests updated | | Demos | `pva_sensor.py` + `imu_sensor.py` | ## Test plan - [x] `test_pva.py` — 8 tests pass (constant velocity, constant acceleration, pendulum, indirect attachment, offset, validity, env_ids, print) - [x] `test_imu.py` — 8 tests pass (same test suite adapted for IMU-only fields) - [x] `test_mock_sensors.py` — 29 tests pass (IMU + PVA + contact + frame transformer mocks) - [x] `test_mock_data_properties.py` — 254 tests pass - [x] Pre-commit hooks all pass 🤖 Generated with [Claude Code](https://claude.com/claude-code)
1 parent 50b6ccc commit abbf838

42 files changed

Lines changed: 2518 additions & 844 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/source/migration/migrating_to_isaaclab_3-0.rst

Lines changed: 83 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,11 +113,19 @@ The following sensor classes also remain in the ``isaaclab`` package with unchan
113113

114114
- :class:`~isaaclab.sensors.ContactSensor`, :class:`~isaaclab.sensors.ContactSensorCfg`, :class:`~isaaclab.sensors.ContactSensorData`
115115
- :class:`~isaaclab.sensors.Imu`, :class:`~isaaclab.sensors.ImuCfg`, :class:`~isaaclab.sensors.ImuData`
116+
- :class:`~isaaclab.sensors.Pva`, :class:`~isaaclab.sensors.PvaCfg`, :class:`~isaaclab.sensors.PvaData`
116117
- :class:`~isaaclab.sensors.FrameTransformer`, :class:`~isaaclab.sensors.FrameTransformerCfg`, :class:`~isaaclab.sensors.FrameTransformerData`
117118

118119
These sensor classes now use factory patterns that automatically instantiate the appropriate backend
119120
implementation (PhysX by default), maintaining full backward compatibility.
120121

122+
.. note::
123+
124+
The ``Imu`` sensor in Isaac Lab 3.0 is **not** the same as the ``Imu`` sensor in 2.x.
125+
The old ``Imu`` (full state sensor) has been renamed to :class:`~isaaclab.sensors.Pva`.
126+
The new :class:`~isaaclab.sensors.Imu` is a lightweight sensor that only provides angular velocity
127+
and linear acceleration. See :ref:`imu-to-pva-migration` below for details.
128+
121129
If you need to import the PhysX sensor implementations directly (e.g., for type hints or subclassing),
122130
you can import from ``isaaclab_physx.sensors``:
123131

@@ -126,6 +134,7 @@ you can import from ``isaaclab_physx.sensors``:
126134
# Direct PhysX implementation imports
127135
from isaaclab_physx.sensors import ContactSensor, ContactSensorData
128136
from isaaclab_physx.sensors import Imu, ImuData
137+
from isaaclab_physx.sensors import Pva, PvaData
129138
from isaaclab_physx.sensors import FrameTransformer, FrameTransformerData
130139
131140
@@ -156,11 +165,81 @@ If you need to import Newton implementations directly (e.g., for type hints or s
156165
from isaaclab_newton.assets import RigidObject as NewtonRigidObject
157166
158167
168+
.. _imu-to-pva-migration:
169+
170+
IMU Sensor Renamed to PVA; New Lightweight IMU Sensor
171+
-----------------------------------------------------
172+
173+
The old ``Imu`` sensor has been renamed to **PVA** (Pose Velocity Acceleration) because it provided
174+
full pose, velocity, and acceleration data — far more than a real inertial measurement unit measures.
175+
A new lightweight **IMU** sensor has been introduced that only provides the two physical quantities
176+
a real IMU measures: angular velocity (gyroscope) and linear acceleration (accelerometer).
177+
178+
If you were using the old ``Imu`` sensor, you need to decide which new sensor to use:
179+
180+
- Use :class:`~isaaclab.sensors.Pva` / :class:`~isaaclab.sensors.PvaCfg` if you need full state
181+
data (pose, linear velocity, angular velocity, linear and angular acceleration, projected gravity).
182+
- Use :class:`~isaaclab.sensors.Imu` / :class:`~isaaclab.sensors.ImuCfg` if you only need angular
183+
velocity and linear acceleration (as a real IMU provides).
184+
185+
**Import changes:**
186+
187+
.. code-block:: python
188+
189+
# Before (Isaac Lab 2.x) — the old IMU provided full state
190+
from isaaclab.sensors import Imu, ImuCfg, ImuData
191+
192+
# After (Isaac Lab 3.x) — use PVA for the same full-state sensor
193+
from isaaclab.sensors import Pva, PvaCfg, PvaData
194+
195+
# Or use the new lightweight IMU for angular velocity + linear acceleration only
196+
from isaaclab.sensors import Imu, ImuCfg, ImuData
197+
198+
**Configuration changes:**
199+
200+
The ``gravity_bias`` configuration parameter has been removed from both sensors:
201+
202+
- **PVA** reports raw kinematic acceleration (no gravity contribution), as the acceleration
203+
is derived from finite differencing of velocities which do not include gravity.
204+
- **IMU** unconditionally includes gravity in its accelerometer readings, matching the behavior
205+
of a real accelerometer. The gravity vector is automatically queried from the simulation.
206+
207+
.. code-block:: python
208+
209+
# Before (Isaac Lab 2.x)
210+
imu_cfg = ImuCfg(
211+
prim_path="{ENV_REGEX_NS}/Robot/base",
212+
gravity_bias=(0.0, 0.0, 9.81), # had to be configured manually
213+
)
214+
215+
# After (Isaac Lab 3.x) — PVA (no gravity in acceleration)
216+
pva_cfg = PvaCfg(prim_path="{ENV_REGEX_NS}/Robot/base")
217+
218+
# After (Isaac Lab 3.x) — IMU (gravity always included automatically)
219+
imu_cfg = ImuCfg(prim_path="{ENV_REGEX_NS}/Robot/base")
220+
221+
**Observation function changes:**
222+
223+
.. code-block:: python
224+
225+
# Before (Isaac Lab 2.x)
226+
from isaaclab.envs.mdp import imu_orientation, imu_projected_gravity
227+
228+
# After (Isaac Lab 3.x)
229+
from isaaclab.envs.mdp import pva_orientation, pva_projected_gravity
230+
231+
**Data property changes:**
232+
233+
The new ``ImuData`` only provides ``ang_vel_b`` and ``lin_acc_b``. If you were accessing other
234+
properties (``pos_w``, ``quat_w``, ``lin_vel_b``, ``ang_acc_b``, ``projected_gravity_b``), switch
235+
to :class:`~isaaclab.sensors.PvaData` which provides all of them.
236+
237+
159238
Sensor Pose Properties Deprecation
160239
----------------------------------
161240

162241
The ``pose_w``, ``pos_w``, and ``quat_w`` properties on :class:`~isaaclab.sensors.ContactSensorData`
163-
and :class:`~isaaclab.sensors.ImuData` are deprecated and will be removed in a future release.
242+
are deprecated and will be removed in a future release.
164243

165244
If you need to track sensor poses in world frame, please use a dedicated sensor such as
166245
:class:`~isaaclab.sensors.FrameTransformer` instead.
@@ -787,7 +866,7 @@ All ``.data.*`` properties on asset and sensor classes now return ``wp.array`` i
787866
:class:`~isaaclab.assets.RigidObject`, :class:`~isaaclab.assets.RigidObjectCollection`,
788867
:class:`~isaaclab_physx.assets.DeformableObject`) and all sensor classes
789868
(:class:`~isaaclab_physx.sensors.ContactSensor`, :class:`~isaaclab_physx.sensors.Imu`,
790-
:class:`~isaaclab_physx.sensors.FrameTransformer`).
869+
:class:`~isaaclab_physx.sensors.Pva`, :class:`~isaaclab_physx.sensors.FrameTransformer`).
791870

792871
To convert back to ``torch.Tensor`` for use with PyTorch operations, wrap the property
793872
access with ``wp.to_torch()``:
@@ -851,6 +930,8 @@ Common patterns that need updating:
851930
- ``isaaclab_physx``
852931
* - :class:`~isaaclab_physx.sensors.Imu`
853932
- ``isaaclab_physx``
933+
* - :class:`~isaaclab_physx.sensors.Pva`
934+
- ``isaaclab_physx``
854935
* - :class:`~isaaclab_physx.sensors.FrameTransformer`
855936
- ``isaaclab_physx``
856937

scripts/demos/sensors/imu_sensor.py

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242

4343
@configclass
4444
class ImuSensorSceneCfg(InteractiveSceneCfg):
45-
"""Design the scene with sensors on the robot."""
45+
"""Design the scene with IMU sensors on the robot."""
4646

4747
# ground plane
4848
ground = AssetBaseCfg(prim_path="/World/defaultGroundPlane", spawn=sim_utils.GroundPlaneCfg())
@@ -55,9 +55,9 @@ class ImuSensorSceneCfg(InteractiveSceneCfg):
5555
# robot
5656
robot = ANYMAL_C_CFG.replace(prim_path="{ENV_REGEX_NS}/Robot")
5757

58-
imu_RF = ImuCfg(prim_path="{ENV_REGEX_NS}/Robot/LF_FOOT", debug_vis=True)
58+
imu_RF = ImuCfg(prim_path="{ENV_REGEX_NS}/Robot/LF_FOOT")
5959

60-
imu_LF = ImuCfg(prim_path="{ENV_REGEX_NS}/Robot/RF_FOOT", gravity_bias=(0, 0, 0), debug_vis=True)
60+
imu_LF = ImuCfg(prim_path="{ENV_REGEX_NS}/Robot/RF_FOOT")
6161

6262

6363
def run_simulator(sim: sim_utils.SimulationContext, scene: InteractiveScene):
@@ -73,9 +73,6 @@ def run_simulator(sim: sim_utils.SimulationContext, scene: InteractiveScene):
7373
# reset counter
7474
count = 0
7575
# reset the scene entities
76-
# root state
77-
# we offset the root state by the origin since the states are written in simulation world frame
78-
# if this is not done, then the robots will be spawned at the (0, 0, 0) of the simulation world
7976
root_pose = wp.to_torch(scene["robot"].data.default_root_pose).clone()
8077
root_pose[:, :3] += scene.env_origins
8178
scene["robot"].write_root_link_pose_to_sim_index(root_pose=root_pose)
@@ -93,11 +90,8 @@ def run_simulator(sim: sim_utils.SimulationContext, scene: InteractiveScene):
9390
scene.reset()
9491
print("[INFO]: Resetting robot state...")
9592
# Apply default actions to the robot
96-
# -- generate actions/commands
9793
targets = wp.to_torch(scene["robot"].data.default_joint_pos)
98-
# -- apply action to the robot
9994
scene["robot"].set_joint_position_target_index(target=targets)
100-
# -- write data to sim
10195
scene.write_data_to_sim()
10296
# perform step
10397
sim.step()
@@ -110,16 +104,12 @@ def run_simulator(sim: sim_utils.SimulationContext, scene: InteractiveScene):
110104
# print information from the sensors
111105
print("-------------------------------")
112106
print(scene["imu_LF"])
113-
print("Received linear velocity: ", scene["imu_LF"].data.lin_vel_b)
114107
print("Received angular velocity: ", scene["imu_LF"].data.ang_vel_b)
115108
print("Received linear acceleration: ", scene["imu_LF"].data.lin_acc_b)
116-
print("Received angular acceleration: ", scene["imu_LF"].data.ang_acc_b)
117109
print("-------------------------------")
118110
print(scene["imu_RF"])
119-
print("Received linear velocity: ", scene["imu_RF"].data.lin_vel_b)
120111
print("Received angular velocity: ", scene["imu_RF"].data.ang_vel_b)
121112
print("Received linear acceleration: ", scene["imu_RF"].data.lin_acc_b)
122-
print("Received angular acceleration: ", scene["imu_RF"].data.ang_acc_b)
123113

124114

125115
def main():
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md).
2+
# All rights reserved.
3+
#
4+
# SPDX-License-Identifier: BSD-3-Clause
5+
6+
"""Launch Isaac Sim Simulator first."""
7+
8+
import argparse
9+
10+
from isaaclab.app import AppLauncher
11+
12+
# add argparse arguments
13+
parser = argparse.ArgumentParser(description="Example on using the PVA sensor.")
14+
parser.add_argument("--num_envs", type=int, default=1, help="Number of environments to spawn.")
15+
# append AppLauncher cli args
16+
AppLauncher.add_app_launcher_args(parser)
17+
# demos should open Kit visualizer by default
18+
parser.set_defaults(visualizer=["kit"])
19+
# parse the arguments
20+
args_cli = parser.parse_args()
21+
22+
# launch omniverse app
23+
app_launcher = AppLauncher(args_cli)
24+
simulation_app = app_launcher.app
25+
26+
"""Rest everything follows."""
27+
28+
import torch
29+
import warp as wp
30+
31+
import isaaclab.sim as sim_utils
32+
from isaaclab.assets import AssetBaseCfg
33+
from isaaclab.scene import InteractiveScene, InteractiveSceneCfg
34+
from isaaclab.sensors import PvaCfg
35+
from isaaclab.utils import configclass
36+
37+
##
38+
# Pre-defined configs
39+
##
40+
from isaaclab_assets.robots.anymal import ANYMAL_C_CFG # isort: skip
41+
42+
43+
@configclass
44+
class PvaSensorSceneCfg(InteractiveSceneCfg):
45+
"""Design the scene with sensors on the robot."""
46+
47+
# ground plane
48+
ground = AssetBaseCfg(prim_path="/World/defaultGroundPlane", spawn=sim_utils.GroundPlaneCfg())
49+
50+
# lights
51+
dome_light = AssetBaseCfg(
52+
prim_path="/World/Light", spawn=sim_utils.DomeLightCfg(intensity=3000.0, color=(0.75, 0.75, 0.75))
53+
)
54+
55+
# robot
56+
robot = ANYMAL_C_CFG.replace(prim_path="{ENV_REGEX_NS}/Robot")
57+
58+
pva_RF = PvaCfg(prim_path="{ENV_REGEX_NS}/Robot/LF_FOOT", debug_vis=True)
59+
60+
pva_LF = PvaCfg(prim_path="{ENV_REGEX_NS}/Robot/RF_FOOT", debug_vis=True)
61+
62+
63+
def run_simulator(sim: sim_utils.SimulationContext, scene: InteractiveScene):
64+
"""Run the simulator."""
65+
# Define simulation stepping
66+
sim_dt = sim.get_physics_dt()
67+
sim_time = 0.0
68+
count = 0
69+
70+
# Simulate physics
71+
while simulation_app.is_running():
72+
if count % 500 == 0:
73+
# reset counter
74+
count = 0
75+
# reset the scene entities
76+
# root state
77+
# we offset the root state by the origin since the states are written in simulation world frame
78+
# if this is not done, then the robots will be spawned at the (0, 0, 0) of the simulation world
79+
root_pose = wp.to_torch(scene["robot"].data.default_root_pose).clone()
80+
root_pose[:, :3] += scene.env_origins
81+
scene["robot"].write_root_link_pose_to_sim_index(root_pose=root_pose)
82+
root_vel = wp.to_torch(scene["robot"].data.default_root_vel).clone()
83+
scene["robot"].write_root_com_velocity_to_sim_index(root_velocity=root_vel)
84+
# set joint positions with some noise
85+
joint_pos, joint_vel = (
86+
wp.to_torch(scene["robot"].data.default_joint_pos).clone(),
87+
wp.to_torch(scene["robot"].data.default_joint_vel).clone(),
88+
)
89+
joint_pos += torch.rand_like(joint_pos) * 0.1
90+
scene["robot"].write_joint_position_to_sim_index(position=joint_pos)
91+
scene["robot"].write_joint_velocity_to_sim_index(velocity=joint_vel)
92+
# clear internal buffers
93+
scene.reset()
94+
print("[INFO]: Resetting robot state...")
95+
# Apply default actions to the robot
96+
# -- generate actions/commands
97+
targets = wp.to_torch(scene["robot"].data.default_joint_pos)
98+
# -- apply action to the robot
99+
scene["robot"].set_joint_position_target_index(target=targets)
100+
# -- write data to sim
101+
scene.write_data_to_sim()
102+
# perform step
103+
sim.step()
104+
# update sim-time
105+
sim_time += sim_dt
106+
count += 1
107+
# update buffers
108+
scene.update(sim_dt)
109+
110+
# print information from the sensors
111+
print("-------------------------------")
112+
print(scene["pva_LF"])
113+
print("Received linear velocity: ", scene["pva_LF"].data.lin_vel_b)
114+
print("Received angular velocity: ", scene["pva_LF"].data.ang_vel_b)
115+
print("Received linear acceleration: ", scene["pva_LF"].data.lin_acc_b)
116+
print("Received angular acceleration: ", scene["pva_LF"].data.ang_acc_b)
117+
print("-------------------------------")
118+
print(scene["pva_RF"])
119+
print("Received linear velocity: ", scene["pva_RF"].data.lin_vel_b)
120+
print("Received angular velocity: ", scene["pva_RF"].data.ang_vel_b)
121+
print("Received linear acceleration: ", scene["pva_RF"].data.lin_acc_b)
122+
print("Received angular acceleration: ", scene["pva_RF"].data.ang_acc_b)
123+
124+
125+
def main():
126+
"""Main function."""
127+
128+
# Initialize the simulation context
129+
sim_cfg = sim_utils.SimulationCfg(dt=0.005, device=args_cli.device)
130+
sim = sim_utils.SimulationContext(sim_cfg)
131+
# Set main camera
132+
sim.set_camera_view(eye=[3.5, 3.5, 3.5], target=[0.0, 0.0, 0.0])
133+
# design scene
134+
scene_cfg = PvaSensorSceneCfg(num_envs=args_cli.num_envs, env_spacing=2.0)
135+
scene = InteractiveScene(scene_cfg)
136+
# Play the simulator
137+
sim.reset()
138+
# Now we are ready!
139+
print("[INFO]: Setup complete...")
140+
# Run the simulator
141+
run_simulator(sim, scene)
142+
143+
144+
if __name__ == "__main__":
145+
# run the main function
146+
main()
147+
# close sim app
148+
simulation_app.close()

source/isaaclab/config/extension.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[package]
22

33
# Note: Semantic Versioning is used: https://semver.org/
4-
version = "4.5.32"
4+
version = "4.5.33"
55

66
# Description
77
title = "Isaac Lab framework for Robot Learning"

source/isaaclab/docs/CHANGELOG.rst

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,46 @@
11
Changelog
22
---------
33

4+
4.5.33 (2026-04-13)
5+
~~~~~~~~~~~~~~~~~~~
6+
7+
Added
8+
^^^^^
9+
10+
* Added :class:`~isaaclab.sensors.Pva` (Pose Velocity Acceleration) sensor,
11+
renamed from the former ``Imu`` to better reflect its full output: pose,
12+
velocity, and acceleration.
13+
* Added :class:`~isaaclab.sensors.Imu` sensor that models a real inertial
14+
measurement unit, providing only angular velocity (gyroscope) and linear
15+
acceleration (accelerometer) in the sensor's body frame.
16+
* Added :func:`~isaaclab.envs.mdp.observations.pva_orientation` and
17+
:func:`~isaaclab.envs.mdp.observations.pva_projected_gravity` observation
18+
functions for the PVA sensor.
19+
20+
Changed
21+
^^^^^^^
22+
23+
* Changed ``isaaclab.sensors.Imu`` to refer to a new lightweight IMU sensor
24+
that only provides angular velocity and linear acceleration. The old
25+
``Imu``, ``ImuCfg``, ``ImuData``, ``BaseImu``, and ``BaseImuData`` names
26+
now refer to this new sensor. For the original full-featured sensor, use
27+
:class:`~isaaclab.sensors.Pva`, :class:`~isaaclab.sensors.PvaCfg`, etc.
28+
29+
Removed
30+
^^^^^^^
31+
32+
* Removed ``gravity_bias`` configuration parameter from
33+
:class:`~isaaclab.sensors.PvaCfg`. The PVA sensor now always reports raw
34+
kinematic acceleration without gravity contribution.
35+
* Removed ``gravity_bias`` and ``visualizer_cfg`` configuration parameters from
36+
:class:`~isaaclab.sensors.ImuCfg`. The IMU sensor now unconditionally includes
37+
gravity in its accelerometer readings, matching real hardware behavior. The
38+
gravity vector is queried from the simulation automatically.
39+
* Removed ``imu_orientation`` and ``imu_projected_gravity`` observation
40+
functions. Use :func:`~isaaclab.envs.mdp.observations.pva_orientation` and
41+
:func:`~isaaclab.envs.mdp.observations.pva_projected_gravity` instead.
42+
43+
444
4.5.32 (2026-04-13)
545
~~~~~~~~~~~~~~~~~~~
646

0 commit comments

Comments
 (0)