Skip to content

Commit d23d01d

Browse files
authored
Improvements of MDP terms in nav_tasks and bugfix in trajectory sampler (#5)
# Description ### ✨ Additions & Enhancements * `NavigationSE2Action` momentum support. * **Height-scan observations**: occlusion-aware utilities incl. door recognition. * **Curriculum**: new helper `change_reward_param`. * **Terrain generation**: `StairsRampTerrainCfg` now accepts `random_state_file` for deterministic builds. ### 🐛 Fixes * **nav_suite 0.2.4** * `TrajectorySampling`: removed unnecessary `torch.int64` cast that caused dtype mismatch. * **nav_tasks 0.3.10** * Height-scan clipping now respects min/max bounds. * Correct usage of `quat_rotate` after Isaac Lab API change. * `TrajectorySampling` index dtype cast to `torch.int64`. * Third-party list in `pyproject.toml` updated to new package names. * Corrected `GoalCommand.command` docstring (returns `(num_envs, 4)`). * `FixGoalCommand.command` once again returns **position + heading** (shape `(num_envs, 4)`) instead of position-only. ## Type of change - Bug fix (non-breaking change which fixes an issue) - New feature (non-breaking change which adds functionality) ## Checklist - [x] I have run the [`pre-commit` checks](https://pre-commit.com/) with `./formatter.sh` - [x] I have made corresponding changes to the documentation (will come shortly) - [x] My changes generate no new warnings - [x] I have updated the changelog and the corresponding version in the extension's `config/extension.toml` file - [x] I have added my name to the `CONTRIBUTORS.md` or my name already exists there
1 parent baca441 commit d23d01d

23 files changed

+289
-75
lines changed

exts/nav_suite/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 = "0.2.3"
4+
version = "0.2.4"
55

66
# Description
77
title = "IsaacLab Navigation Suite"

exts/nav_suite/docs/CHANGELOG.rst

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

4+
0.2.4 (2025-08-08)
5+
~~~~~~~~~~~~~~~~~~
6+
7+
Fixes
8+
^^^^^
9+
10+
- Fixed dtype mismatch in :class:`nav_suite.collectors.TrajectorySampling` by removing the casting of trajectory lengths to ``torch.int64``.
11+
- Renamed configuration option ``attach_yaw_only`` to ``ray_alignment`` according to IsaacLab API changes in 2.1.1
12+
13+
414
0.2.3 (2025-06-11)
515
~~~~~~~~~~~~~~~~~~
616

exts/nav_suite/nav_suite/collectors/trajectory_sampling.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ def sample_paths_by_terrain(
232232
clipped = randomized_samples[mask][:samples_per_terrain_level]
233233
samples_by_terrain[row, :, :3] = self.terrain_analyser.points[clipped[:, 0].type(torch.int64)]
234234
samples_by_terrain[row, :, 3:6] = self.terrain_analyser.points[clipped[:, 1].type(torch.int64)]
235-
samples_by_terrain[row, :, 6] = clipped[:, 2].type(torch.int64)
235+
samples_by_terrain[row, :, 6] = clipped[:, 2]
236236
else:
237237
subterrain_idx_origins = (
238238
randomized_samples_subterrains_origins[:, 0] * num_cols + randomized_samples_subterrains_origins[:, 1]
@@ -255,7 +255,7 @@ def sample_paths_by_terrain(
255255
clipped = randomized_samples[mask][:samples_per_terrain]
256256
samples_by_terrain[row, col, :, :3] = self.terrain_analyser.points[clipped[:, 0].type(torch.int64)]
257257
samples_by_terrain[row, col, :, 3:6] = self.terrain_analyser.points[clipped[:, 1].type(torch.int64)]
258-
samples_by_terrain[row, col, :, 6] = clipped[:, 2].type(torch.int64)
258+
samples_by_terrain[row, col, :, 6] = clipped[:, 2]
259259

260260
# save curr_data as pickle
261261
if self.cfg.enable_saved_paths_loading:

exts/nav_suite/nav_suite/sensors/matterport_raycaster.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ def _update_buffers_impl(self, env_ids: Sequence[int]):
145145
self._data.quat_w[env_ids] = quat_w
146146

147147
# ray cast based on the sensor poses
148-
if self.cfg.attach_yaw_only:
148+
if self.cfg.ray_alignment == "yaw":
149149
# only yaw orientation is considered and directions are not rotated
150150
ray_starts_w = quat_apply_yaw(quat_w.repeat(1, self.num_rays), self.ray_starts[env_ids])
151151
ray_starts_w += pos_w.unsqueeze(1)

exts/nav_tasks/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 = "0.3.9"
4+
version = "0.3.10"
55

66
# Description
77
title = "IsaacLab Navigation RL Tasks"

exts/nav_tasks/docs/CHANGELOG.rst

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

4+
0.3.10 (2025-08-08)
5+
~~~~~~~~~~~~~~~~~~
6+
7+
Changed
8+
^^^^^^^
9+
10+
- Updated configuration option ``attach_yaw_only`` to ``ray_alignment`` across sensor and environment configurations according to IsaacLab API changes in 2.1.1
11+
- Updated math calls with :func:`isaaclab.utils.math.quat_rotate` according to IsaacLab API changes in 2.1.1.
12+
- Updated :meth:`nav_tasks.mdp.commands.FixGoalCommand.command` to return only position plus heading instead of just position.
13+
14+
Added
15+
^^^^^
16+
17+
- Added ``momentum`` feature and corresponding configuration options to :class:`nav_tasks.mdp.actions.NavigationSE2Action` and :class:`nav_tasks.mdp.actions.NavigationSE2ActionCfg`.
18+
- Added occlusion-aware height scan utilities that better handle door recognition and terrain occlusions.
19+
- Added curriculum :func:`nav_tasks.mdp.curriculums.change_reward_param`.
20+
- Added ``random_state_file`` option from :class:`nav_tasks.terrains.StairsRampTerrainCfg`.
21+
22+
Fixes
23+
^^^^^
24+
25+
- Fixed height scan clipping in :func:`nav_tasks.mdp.observations.height_scan_clipped` to correctly apply limits.
26+
- Fixed dtype mismatch in sample selection inside :class:`nav_tasks.collectors.TrajectorySampling` by casting indices to ``torch.int64``.
27+
- Fixed third-party module list in ``pyproject.toml`` to new Isaac Lab package names.
28+
- Fixed docstring of :meth:`nav_tasks.mdp.commands.GoalCommand.command` to return pose with shape ``(num_envs, 4)`` instead of ``(num_envs, 7)``.
29+
30+
431
0.3.9 (2025-08-04)
532
~~~~~~~~~~~~~~~~~~
633

exts/nav_tasks/nav_tasks/configs/env_cfg_base.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ class NavTasksDepthNavSceneCfg(InteractiveSceneCfg):
7676
height_scanner = RayCasterCfg(
7777
prim_path="{ENV_REGEX_NS}/Robot/base",
7878
offset=RayCasterCfg.OffsetCfg(pos=(0.0, 0.0, 20.0)),
79-
attach_yaw_only=True,
79+
ray_alignment="yaw",
8080
pattern_cfg=patterns.GridPatternCfg(resolution=0.1, size=[1.6, 1.0]),
8181
debug_vis=True,
8282
mesh_prim_paths=["/World/ground"],

exts/nav_tasks/nav_tasks/mdp/actions/navigation_actions.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,19 @@ def __init__(self, cfg: NavigationSE2ActionCfg, env: ManagerBasedRLEnv):
8888
f"Unsupported clip mode: {self.cfg.clip_mode}. Supported modes are 'minmax', 'tanh' and 'none'."
8989
)
9090

91+
# parse momentum
92+
if self.cfg.momentum is not None:
93+
if isinstance(self.cfg.momentum, (float, int)):
94+
self._momentum = float(self.cfg.momentum)
95+
elif isinstance(self.cfg.momentum, list):
96+
self._momentum = torch.tensor([self.cfg.momentum], device=self.device).repeat(self.num_envs, 1)
97+
else:
98+
raise ValueError(
99+
f"Unsupported momentum type: {type(self.cfg.momentum)}. Supported types are float, int, and list."
100+
)
101+
else:
102+
self._momentum = 0
103+
91104
# set up buffers
92105
self._init_buffers()
93106

@@ -136,6 +149,11 @@ def process_actions(self, actions):
136149
self._processed_navigation_velocity_actions *= self._scale
137150
self._processed_navigation_velocity_actions += self._offset
138151

152+
# apply a momentum offset
153+
self._processed_navigation_velocity_actions += (
154+
torch.norm(self._env.scene.articulations["robot"].data.root_lin_vel_b, dim=1, keepdim=True) * self._momentum
155+
)
156+
139157
def apply_actions(self):
140158
"""Apply low-level actions for the simulator to the physics engine. This functions is called with the
141159
simulation frequency of 200Hz. Since low-level locomotion runs at 50Hz, we need to decimate the actions."""

exts/nav_tasks/nav_tasks/mdp/actions/navigation_actions_cfg.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,15 @@ class NavigationSE2ActionCfg(ActionTermCfg):
6464
.. note::
6565
Offset is applied after scaling. If a list is provided, it must be of the same length as the number of action dimensions.
6666
"""
67+
68+
momentum: float | list[float] | None = None
69+
"""Momentum of the action space. Default is None.
70+
71+
Momentum is computed as the norm of the robot's base linear velocity in the body frame times the momentum factor.
72+
73+
.. math::
74+
momentum = \text{norm}(v_{base}) \times \text{momentum}
75+
76+
.. note::
77+
Momentum is applied after scaling and offset. If a list is provided, it must be of the same length as the number of action dimensions.
78+
"""

exts/nav_tasks/nav_tasks/mdp/commands/fix_goal_command.py

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from typing import TYPE_CHECKING
1313

1414
from isaaclab.assets import Articulation
15-
from isaaclab.utils.math import quat_apply_inverse, sample_uniform, yaw_quat
15+
from isaaclab.utils.math import quat_apply_inverse, sample_uniform, wrap_to_pi, yaw_quat
1616

1717
from nav_suite.terrain_analysis import TerrainAnalysis, TerrainAnalysisSingleton
1818

@@ -41,6 +41,9 @@ def __init__(self, cfg: FixGoalCommandCfg, env: ManagerBasedRLEnv):
4141
Args:
4242
cfg: The configuration parameters for the command.
4343
env: The environment object.
44+
45+
Raises:
46+
AssertionError: If ``trajectory_num_samples`` is not divisible by ``num_envs``.
4447
"""
4548
super().__init__(cfg, env)
4649
# -- robot
@@ -94,6 +97,10 @@ def __init__(self, cfg: FixGoalCommandCfg, env: ManagerBasedRLEnv):
9497
torch.zeros((self.num_envs, 1), device=self.device),
9598
])
9699

100+
# heading command is just straight forward
101+
self.heading_command_w = torch.zeros(self.num_envs, device=self.device)
102+
self.heading_command_b = torch.zeros_like(self.heading_command_w)
103+
97104
# -- spawn locations (x, y, z, heading)
98105
self.pos_spawn_w = env.scene.env_origins.clone()
99106
self.heading_spawn_w = torch.zeros(self.num_envs, device=self.device)
@@ -114,6 +121,11 @@ def __init__(self, cfg: FixGoalCommandCfg, env: ManagerBasedRLEnv):
114121

115122
# EVAL case that maximum number of samples is set
116123
if self.cfg.trajectory_num_samples is not None:
124+
assert self.cfg.trajectory_num_samples % env.num_envs == 0, (
125+
"[FixGoalCommand] ``trajectory_num_samples`` must be divisible by ``num_envs`` to allow for equal"
126+
" sampling of paths per environment."
127+
)
128+
117129
self.all_goals = self.pos_command_w.unsqueeze(1).expand(
118130
-1, self.cfg.trajectory_num_samples // env.num_envs, -1
119131
)
@@ -187,8 +199,8 @@ def __str__(self) -> str:
187199

188200
@property
189201
def command(self) -> torch.Tensor:
190-
"""The desired base position in base frame. Shape is (num_envs, 3)."""
191-
return self.pos_command_b
202+
"""The desired base pose in base frame. Shape is (num_envs, 4)."""
203+
return torch.cat((self.pos_command_b, self.heading_command_b.unsqueeze(-1)), dim=1)
192204

193205
@property
194206
def analysis(self) -> TerrainAnalysis | TerrainAnalysisSingleton:
@@ -216,6 +228,7 @@ def reset(self, env_ids: Sequence[int] | None = None) -> dict[str, torch.Tensor]
216228
self.nb_sampled_paths = 0
217229
self.not_updated_envs.fill_(False)
218230
self.prev_not_updated_envs.fill_(False)
231+
self.path_idx_per_env.fill_(0)
219232

220233
return super().reset(env_ids=env_ids)
221234

@@ -252,12 +265,19 @@ def _resample_command(self, env_ids: Sequence[int]):
252265
perturbation[:, 2] = 0.1
253266
self.pos_spawn_w[env_ids] = self._env.scene.env_origins[env_ids] + perturbation
254267

268+
# add robot height offset to the spawn position
269+
self.pos_spawn_w[env_ids, 2] += self.robot.data.default_root_state[env_ids, 2]
270+
255271
def _update_command(self):
256272
"""Re-target the position command to the current root position."""
257273
target_vec = self.pos_command_w - self.robot.data.root_pos_w[:, :3]
258274
target_vec[:, 2] = 0.0 # ignore z component
259275
self.pos_command_b[:] = quat_apply_inverse(yaw_quat(self.robot.data.root_quat_w), target_vec)
260276

277+
# update the heading command in the base frame
278+
# heading_w is angle world x axis to robot base x axis
279+
self.heading_command_b[:] = wrap_to_pi(self.heading_command_w - self.robot.data.heading_w)
280+
261281
def _update_metrics(self):
262282
"""Update metrics."""
263283
self.metrics["error_pos"] = torch.norm(self.pos_command_w - self.robot.data.root_pos_w[:, :3], dim=1)

0 commit comments

Comments
 (0)