Unverified Commit d02d3b8a authored by James Smith's avatar James Smith Committed by GitHub

Updates to ray caster ray alignment and more customizable drift sampling (#2556)

This PR pushes some changes from our internal RAI fork of Isaac Lab.

The 2 main changes are:

#### Adds a new `ray_alignment` parameter to replace the previous
`yaw_only` arg.

Now the ray alignment can be aligned to `world`, `yaw`, or `base.

#### Improves drift height sampling

At the moment, the `RayCasterCfg` hosts a parameter called
`drift_range`. It allows to randomize the position of the sensor in
world frame, which comes with a couple of downsides:
* If the projection is done along the gravity vector, the z-component
drift term cannot be visualized (it is implicitly given in the pose, and
added to the height scan in the observation term).
* The perturbation is applied in world frame, i.e., if the ray cast is
yaw aligned, then the drift varies with the yaw angle
* The drift is applied to all (x,y,z) components equally

This PR adds a new parameter ray_cast_drift_range. It gives more freedom
in choosing the drift ranges as x, y and z components can be treated
separately. It also applies the drift after projecting and rotating the
ray cast points, i.e. the drift is now invariant under the yaw angle and
the drift in z is can be visualized

I've added Fabian Jenelten and Jeonghwan Kim to this PR as they worked
on these changes within RAI - I'm simply helping to push them up 😄

## Type of change

<!-- As you go through the list, delete the ones that are not
applicable. -->

- New feature (non-breaking change which adds functionality)
- Breaking change (fix or feature that would cause existing
functionality to not work as expected)

## Checklist

- [x] I have run the [`pre-commit` checks](https://pre-commit.com/) with
`./isaaclab.sh --format`
- [x] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [ ] I have updated the changelog and the corresponding version in the
extension's `config/extension.toml` file
- [ ] I have added my name to the `CONTRIBUTORS.md` or my name already
exists there

<!--
As you go through the checklist above, you can mark something as done by
putting an x character in it

For example,
- [x] I have done this task
- [ ] I have not done this task
-->

---------
Signed-off-by: 's avatarooctipus <zhengyuz@nvidia.com>
Co-authored-by: 's avatarooctipus <zhengyuz@nvidia.com>
parent c5fd8ebe
...@@ -57,6 +57,7 @@ Guidelines for modifications: ...@@ -57,6 +57,7 @@ Guidelines for modifications:
* David Yang * David Yang
* Dhananjay Shendre * Dhananjay Shendre
* Dorsa Rohani * Dorsa Rohani
* Fabian Jenelten
* Felipe Mohr * Felipe Mohr
* Felix Yu * Felix Yu
* Gary Lvov * Gary Lvov
...@@ -69,6 +70,7 @@ Guidelines for modifications: ...@@ -69,6 +70,7 @@ Guidelines for modifications:
* Jack Zeng * Jack Zeng
* Jan Kerner * Jan Kerner
* Jean Tampon * Jean Tampon
* Jeonghwan Kim
* Jia Lin Yuan * Jia Lin Yuan
* Jiakai Zhang * Jiakai Zhang
* Jinghuan Shang * Jinghuan Shang
......
...@@ -105,7 +105,7 @@ class MySceneCfg(InteractiveSceneCfg): ...@@ -105,7 +105,7 @@ class MySceneCfg(InteractiveSceneCfg):
height_scanner = RayCasterCfg( height_scanner = RayCasterCfg(
prim_path="{ENV_REGEX_NS}/Robot/base", prim_path="{ENV_REGEX_NS}/Robot/base",
offset=RayCasterCfg.OffsetCfg(pos=(0.0, 0.0, 20.0)), offset=RayCasterCfg.OffsetCfg(pos=(0.0, 0.0, 20.0)),
attach_yaw_only=True, ray_alignment="yaw",
pattern_cfg=patterns.GridPatternCfg(resolution=0.1, size=[1.6, 1.0]), pattern_cfg=patterns.GridPatternCfg(resolution=0.1, size=[1.6, 1.0]),
debug_vis=True, debug_vis=True,
mesh_prim_paths=["/World/ground"], mesh_prim_paths=["/World/ground"],
......
...@@ -84,7 +84,7 @@ class SensorsSceneCfg(InteractiveSceneCfg): ...@@ -84,7 +84,7 @@ class SensorsSceneCfg(InteractiveSceneCfg):
prim_path="{ENV_REGEX_NS}/Robot/base", prim_path="{ENV_REGEX_NS}/Robot/base",
update_period=0.02, update_period=0.02,
offset=RayCasterCfg.OffsetCfg(pos=(0.0, 0.0, 20.0)), offset=RayCasterCfg.OffsetCfg(pos=(0.0, 0.0, 20.0)),
attach_yaw_only=True, ray_alignment="yaw",
pattern_cfg=patterns.GridPatternCfg(resolution=0.1, size=[1.6, 1.0]), pattern_cfg=patterns.GridPatternCfg(resolution=0.1, size=[1.6, 1.0]),
debug_vis=True, debug_vis=True,
mesh_prim_paths=["/World/defaultGroundPlane"], mesh_prim_paths=["/World/defaultGroundPlane"],
......
...@@ -49,7 +49,7 @@ def define_sensor() -> RayCaster: ...@@ -49,7 +49,7 @@ def define_sensor() -> RayCaster:
prim_path="/World/Origin.*/ball", prim_path="/World/Origin.*/ball",
mesh_prim_paths=["/World/ground"], mesh_prim_paths=["/World/ground"],
pattern_cfg=patterns.GridPatternCfg(resolution=0.1, size=(2.0, 2.0)), pattern_cfg=patterns.GridPatternCfg(resolution=0.1, size=(2.0, 2.0)),
attach_yaw_only=True, ray_alignment="yaw",
debug_vis=not args_cli.headless, debug_vis=not args_cli.headless,
) )
ray_caster = RayCaster(cfg=ray_caster_cfg) ray_caster = RayCaster(cfg=ray_caster_cfg)
......
...@@ -52,7 +52,7 @@ class InteractiveSceneCfg: ...@@ -52,7 +52,7 @@ class InteractiveSceneCfg:
height_scanner = RayCasterCfg( height_scanner = RayCasterCfg(
prim_path="{ENV_REGEX_NS}/Robot_1/base", prim_path="{ENV_REGEX_NS}/Robot_1/base",
offset=RayCasterCfg.OffsetCfg(pos=(0.0, 0.0, 20.0)), offset=RayCasterCfg.OffsetCfg(pos=(0.0, 0.0, 20.0)),
attach_yaw_only=True, ray_alignment="yaw",
pattern_cfg=GridPatternCfg(resolution=0.1, size=[1.6, 1.0]), pattern_cfg=GridPatternCfg(resolution=0.1, size=[1.6, 1.0]),
debug_vis=True, debug_vis=True,
mesh_prim_paths=["/World/ground"], mesh_prim_paths=["/World/ground"],
......
...@@ -111,7 +111,13 @@ class RayCaster(SensorBase): ...@@ -111,7 +111,13 @@ class RayCaster(SensorBase):
if env_ids is None: if env_ids is None:
env_ids = slice(None) env_ids = slice(None)
# resample the drift # resample the drift
self.drift[env_ids] = self.drift[env_ids].uniform_(*self.cfg.drift_range) r = torch.empty(len(env_ids), 3, device=self.device)
self.drift[env_ids] = r.uniform_(*self.cfg.drift_range)
# resample the height drift
r = torch.empty(len(env_ids), device=self.device)
self.ray_cast_drift[env_ids, 0] = r.uniform_(*self.cfg.ray_cast_drift_range["x"])
self.ray_cast_drift[env_ids, 1] = r.uniform_(*self.cfg.ray_cast_drift_range["y"])
self.ray_cast_drift[env_ids, 2] = r.uniform_(*self.cfg.ray_cast_drift_range["z"])
""" """
Implementation. Implementation.
...@@ -212,6 +218,7 @@ class RayCaster(SensorBase): ...@@ -212,6 +218,7 @@ class RayCaster(SensorBase):
self.ray_directions = self.ray_directions.repeat(self._view.count, 1, 1) self.ray_directions = self.ray_directions.repeat(self._view.count, 1, 1)
# prepare drift # prepare drift
self.drift = torch.zeros(self._view.count, 3, device=self.device) self.drift = torch.zeros(self._view.count, 3, device=self.device)
self.ray_cast_drift = torch.zeros(self._view.count, 3, device=self.device)
# fill the data buffer # fill the data buffer
self._data.pos_w = torch.zeros(self._view.count, 3, device=self._device) self._data.pos_w = torch.zeros(self._view.count, 3, device=self._device)
self._data.quat_w = torch.zeros(self._view.count, 4, device=self._device) self._data.quat_w = torch.zeros(self._view.count, 4, device=self._device)
...@@ -233,23 +240,44 @@ class RayCaster(SensorBase): ...@@ -233,23 +240,44 @@ class RayCaster(SensorBase):
# note: we clone here because we are read-only operations # note: we clone here because we are read-only operations
pos_w = pos_w.clone() pos_w = pos_w.clone()
quat_w = quat_w.clone() quat_w = quat_w.clone()
# apply drift # apply drift to ray starting position in world frame
pos_w += self.drift[env_ids] pos_w += self.drift[env_ids]
# store the poses # store the poses
self._data.pos_w[env_ids] = pos_w self._data.pos_w[env_ids] = pos_w
self._data.quat_w[env_ids] = quat_w self._data.quat_w[env_ids] = quat_w
# ray cast based on the sensor poses # ray cast based on the sensor poses
if self.cfg.attach_yaw_only: if self.cfg.ray_alignment == "world":
# apply horizontal drift to ray starting position in ray caster frame
pos_w[:, 0:2] += self.ray_cast_drift[env_ids, 0:2]
# no rotation is considered and directions are not rotated
ray_starts_w = self.ray_starts[env_ids]
ray_starts_w += pos_w.unsqueeze(1)
ray_directions_w = self.ray_directions[env_ids]
elif self.cfg.ray_alignment == "yaw" or self.cfg.attach_yaw_only:
if self.cfg.attach_yaw_only:
self.cfg.ray_alignment = "yaw"
omni.log.warn(
"The `attach_yaw_only` property will be deprecated in a future release. Please use"
" `ray_alignment='yaw'` instead."
)
# apply horizontal drift to ray starting position in ray caster frame
pos_w[:, 0:2] += quat_apply_yaw(quat_w, self.ray_cast_drift[env_ids])[:, 0:2]
# only yaw orientation is considered and directions are not rotated # only yaw orientation is considered and directions are not rotated
ray_starts_w = quat_apply_yaw(quat_w.repeat(1, self.num_rays), self.ray_starts[env_ids]) ray_starts_w = quat_apply_yaw(quat_w.repeat(1, self.num_rays), self.ray_starts[env_ids])
ray_starts_w += pos_w.unsqueeze(1) ray_starts_w += pos_w.unsqueeze(1)
ray_directions_w = self.ray_directions[env_ids] ray_directions_w = self.ray_directions[env_ids]
else: elif self.cfg.ray_alignment == "base":
# apply horizontal drift to ray starting position in ray caster frame
pos_w[:, 0:2] += quat_apply(quat_w, self.ray_cast_drift[env_ids])[:, 0:2]
# full orientation is considered # full orientation is considered
ray_starts_w = quat_apply(quat_w.repeat(1, self.num_rays), self.ray_starts[env_ids]) ray_starts_w = quat_apply(quat_w.repeat(1, self.num_rays), self.ray_starts[env_ids])
ray_starts_w += pos_w.unsqueeze(1) ray_starts_w += pos_w.unsqueeze(1)
ray_directions_w = quat_apply(quat_w.repeat(1, self.num_rays), self.ray_directions[env_ids]) ray_directions_w = quat_apply(quat_w.repeat(1, self.num_rays), self.ray_directions[env_ids])
else:
raise RuntimeError(f"Unsupported ray_alignment type: {self.cfg.ray_alignment}.")
# ray cast and store the hits # ray cast and store the hits
# TODO: Make this work for multiple meshes? # TODO: Make this work for multiple meshes?
self._data.ray_hits_w[env_ids] = raycast_mesh( self._data.ray_hits_w[env_ids] = raycast_mesh(
...@@ -259,6 +287,9 @@ class RayCaster(SensorBase): ...@@ -259,6 +287,9 @@ class RayCaster(SensorBase):
mesh=self.meshes[self.cfg.mesh_prim_paths[0]], mesh=self.meshes[self.cfg.mesh_prim_paths[0]],
)[0] )[0]
# apply vertical drift to ray starting position in ray caster frame
self._data.ray_hits_w[env_ids, :, 2] += self.ray_cast_drift[env_ids, 2].unsqueeze(-1)
def _set_debug_vis_impl(self, debug_vis: bool): def _set_debug_vis_impl(self, debug_vis: bool):
# set visibility of markers # set visibility of markers
# note: parent only deals with callbacks. not their visibility # note: parent only deals with callbacks. not their visibility
......
...@@ -60,4 +60,4 @@ class RayCasterCameraCfg(RayCasterCfg): ...@@ -60,4 +60,4 @@ class RayCasterCameraCfg(RayCasterCfg):
def __post_init__(self): def __post_init__(self):
# for cameras, this quantity should be False always. # for cameras, this quantity should be False always.
self.attach_yaw_only = False self.ray_alignment = "base"
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
from dataclasses import MISSING from dataclasses import MISSING
from typing import Literal
from isaaclab.markers import VisualizationMarkersCfg from isaaclab.markers import VisualizationMarkersCfg
from isaaclab.markers.config import RAY_CASTER_MARKER_CFG from isaaclab.markers.config import RAY_CASTER_MARKER_CFG
...@@ -43,10 +44,22 @@ class RayCasterCfg(SensorBaseCfg): ...@@ -43,10 +44,22 @@ class RayCasterCfg(SensorBaseCfg):
offset: OffsetCfg = OffsetCfg() offset: OffsetCfg = OffsetCfg()
"""The offset pose of the sensor's frame from the sensor's parent frame. Defaults to identity.""" """The offset pose of the sensor's frame from the sensor's parent frame. Defaults to identity."""
attach_yaw_only: bool = MISSING attach_yaw_only: bool = False
"""Whether the rays' starting positions and directions only track the yaw orientation. """Whether the rays' starting positions and directions only track the yaw orientation.
This is useful for ray-casting height maps, where only yaw rotation is needed. This is useful for ray-casting height maps, where only yaw rotation is needed.
.. warning::
This attribute is deprecated. Use :attr:`~isaaclab.sensors.ray_caster.ray_caster_cfg.ray_alignment` instead.
To get the same behavior, set `ray_alignment` to `"yaw"`.
"""
ray_alignment: Literal["base", "yaw", "world"] = "yaw"
"""Specify in what frame the rays are projected onto the ground. Default is `world`.
* `base` if the rays' starting positions and directions track the full root position and orientation.
* `yaw` if the rays' starting positions and directions track root position and only yaw component of orientation. This is useful for ray-casting height maps.
* `world` if rays' starting positions and directions are always fixed. This is useful in combination with the grid map package.
""" """
pattern_cfg: PatternBaseCfg = MISSING pattern_cfg: PatternBaseCfg = MISSING
...@@ -56,7 +69,13 @@ class RayCasterCfg(SensorBaseCfg): ...@@ -56,7 +69,13 @@ class RayCasterCfg(SensorBaseCfg):
"""Maximum distance (in meters) from the sensor to ray cast to. Defaults to 1e6.""" """Maximum distance (in meters) from the sensor to ray cast to. Defaults to 1e6."""
drift_range: tuple[float, float] = (0.0, 0.0) drift_range: tuple[float, float] = (0.0, 0.0)
"""The range of drift (in meters) to add to the ray starting positions (xyz). Defaults to (0.0, 0.0). """The range of drift (in meters) to add to the ray starting positions (xyz) in world frame. Defaults to (0.0, 0.0).
For floating base robots, this is useful for simulating drift in the robot's pose estimation.
"""
ray_cast_drift_range: dict[str, tuple[float, float]] = {"x": (0.0, 0.0), "y": (0.0, 0.0), "z": (0.0, 0.0)}
"""The range of drift (in meters) to add to the projected ray points in local projection frame. Defaults to (0.0, 0.0) for x, y, and z drift.
For floating base robots, this is useful for simulating drift in the robot's pose estimation. For floating base robots, this is useful for simulating drift in the robot's pose estimation.
""" """
......
...@@ -92,7 +92,7 @@ class MySceneCfg(InteractiveSceneCfg): ...@@ -92,7 +92,7 @@ class MySceneCfg(InteractiveSceneCfg):
height_scanner = RayCasterCfg( height_scanner = RayCasterCfg(
prim_path="{ENV_REGEX_NS}/Robot/base", prim_path="{ENV_REGEX_NS}/Robot/base",
offset=RayCasterCfg.OffsetCfg(pos=(0.0, 0.0, 20.0)), offset=RayCasterCfg.OffsetCfg(pos=(0.0, 0.0, 20.0)),
attach_yaw_only=True, ray_alignment="yaw",
pattern_cfg=patterns.GridPatternCfg(resolution=0.1, size=[1.6, 1.0]), pattern_cfg=patterns.GridPatternCfg(resolution=0.1, size=[1.6, 1.0]),
debug_vis=True, debug_vis=True,
mesh_prim_paths=["/World/ground"], mesh_prim_paths=["/World/ground"],
......
...@@ -70,7 +70,7 @@ class SensorsSceneCfg(InteractiveSceneCfg): ...@@ -70,7 +70,7 @@ class SensorsSceneCfg(InteractiveSceneCfg):
prim_path="{ENV_REGEX_NS}/Robot/base", prim_path="{ENV_REGEX_NS}/Robot/base",
update_period=0.02, update_period=0.02,
offset=RayCasterCfg.OffsetCfg(pos=(0.0, 0.0, 20.0)), offset=RayCasterCfg.OffsetCfg(pos=(0.0, 0.0, 20.0)),
attach_yaw_only=True, ray_alignment="yaw",
pattern_cfg=patterns.GridPatternCfg(resolution=0.1, size=[1.6, 1.0]), pattern_cfg=patterns.GridPatternCfg(resolution=0.1, size=[1.6, 1.0]),
debug_vis=True, debug_vis=True,
mesh_prim_paths=["/World/defaultGroundPlane"], mesh_prim_paths=["/World/defaultGroundPlane"],
......
...@@ -62,7 +62,7 @@ class MySceneCfg(InteractiveSceneCfg): ...@@ -62,7 +62,7 @@ class MySceneCfg(InteractiveSceneCfg):
height_scanner = RayCasterCfg( height_scanner = RayCasterCfg(
prim_path="{ENV_REGEX_NS}/Robot_1/base", prim_path="{ENV_REGEX_NS}/Robot_1/base",
offset=RayCasterCfg.OffsetCfg(pos=(0.0, 0.0, 20.0)), offset=RayCasterCfg.OffsetCfg(pos=(0.0, 0.0, 20.0)),
attach_yaw_only=True, ray_alignment="yaw",
pattern_cfg=patterns.GridPatternCfg(resolution=0.1, size=[1.6, 1.0]), pattern_cfg=patterns.GridPatternCfg(resolution=0.1, size=[1.6, 1.0]),
debug_vis=True, debug_vis=True,
mesh_prim_paths=["/World/ground"], mesh_prim_paths=["/World/ground"],
......
...@@ -123,7 +123,7 @@ def main(): ...@@ -123,7 +123,7 @@ def main():
prim_path="/World/envs/env_.*/ball", prim_path="/World/envs/env_.*/ball",
mesh_prim_paths=["/World/ground"], mesh_prim_paths=["/World/ground"],
pattern_cfg=patterns.GridPatternCfg(resolution=0.1, size=(1.6, 1.0)), pattern_cfg=patterns.GridPatternCfg(resolution=0.1, size=(1.6, 1.0)),
attach_yaw_only=True, ray_alignment="yaw",
debug_vis=not args_cli.headless, debug_vis=not args_cli.headless,
) )
ray_caster = RayCaster(cfg=ray_caster_cfg) ray_caster = RayCaster(cfg=ray_caster_cfg)
......
...@@ -13,7 +13,7 @@ from isaaclab.sensors import RayCasterCfg, patterns ...@@ -13,7 +13,7 @@ from isaaclab.sensors import RayCasterCfg, patterns
## ##
VELODYNE_VLP_16_RAYCASTER_CFG = RayCasterCfg( VELODYNE_VLP_16_RAYCASTER_CFG = RayCasterCfg(
attach_yaw_only=False, ray_alignment="base",
pattern_cfg=patterns.LidarPatternCfg( pattern_cfg=patterns.LidarPatternCfg(
channels=16, vertical_fov_range=(-15.0, 15.0), horizontal_fov_range=(-180.0, 180.0), horizontal_res=0.2 channels=16, vertical_fov_range=(-15.0, 15.0), horizontal_fov_range=(-180.0, 180.0), horizontal_res=0.2
), ),
......
...@@ -66,7 +66,7 @@ class MySceneCfg(InteractiveSceneCfg): ...@@ -66,7 +66,7 @@ class MySceneCfg(InteractiveSceneCfg):
height_scanner = RayCasterCfg( height_scanner = RayCasterCfg(
prim_path="{ENV_REGEX_NS}/Robot/base", prim_path="{ENV_REGEX_NS}/Robot/base",
offset=RayCasterCfg.OffsetCfg(pos=(0.0, 0.0, 20.0)), offset=RayCasterCfg.OffsetCfg(pos=(0.0, 0.0, 20.0)),
attach_yaw_only=True, ray_alignment="yaw",
pattern_cfg=patterns.GridPatternCfg(resolution=0.1, size=[1.6, 1.0]), pattern_cfg=patterns.GridPatternCfg(resolution=0.1, size=[1.6, 1.0]),
debug_vis=False, debug_vis=False,
mesh_prim_paths=["/World/ground"], mesh_prim_paths=["/World/ground"],
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment