Skip to content

Commit fc7cd13

Browse files
authored
Refactor: Pose randomization (#340)
## Summary This MR moves pose randomization-on-reset into the objects, rather than it living in the tasks. ## Detailed description - Previously, the randomization-on-reset was handled by tasks. This is wrong, basically, different environments want different randomization, for the same tasks. So randomization should be specified per-environment, not per-task. - This MR allows `object.set_initial_pose()` to take a pose range, which specifies randomization/ - Behind the scenes this creates a randomization event per object.
1 parent 42c213a commit fc7cd13

11 files changed

Lines changed: 234 additions & 131 deletions

File tree

docs/pages/example_workflows/locomanipulation/step_1_environment_setup.rst

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ Environment Description
2626
from isaaclab_arena.environments.isaaclab_arena_environment import IsaacLabArenaEnvironment
2727
from isaaclab_arena.scene.scene import Scene
2828
from isaaclab_arena.tasks.g1_locomanip_pick_and_place_task import G1LocomanipPickAndPlaceTask
29-
from isaaclab_arena.utils.pose import Pose
29+
from isaaclab_arena.utils.pose import Pose, PoseRange
3030
3131
background = self.asset_registry.get_asset_by_name("galileo_locomanip")()
3232
pick_up_object = self.asset_registry.get_asset_by_name(args_cli.object)()
@@ -35,10 +35,13 @@ Environment Description
3535
3636
teleop_device = self.device_registry.get_device_by_name(args_cli.teleop_device)()
3737
38+
XY_RANGE_M = 0.025
3839
pick_up_object.set_initial_pose(
39-
Pose(
40-
position_xyz=(0.5785, 0.18, 0.0707),
41-
rotation_wxyz=(0.0, 0.0, 1.0, 0.0),
40+
PoseRange(
41+
position_xyz_min=(0.5785 - XY_RANGE_M, 0.18 - XY_RANGE_M, 0.0707),
42+
position_xyz_max=(0.5785 + XY_RANGE_M, 0.18 + XY_RANGE_M, 0.0707),
43+
rpy_min=(0.0, 0.0, 0.0),
44+
rpy_max=(0.0, 0.0, 0.0),
4245
)
4346
)
4447
blue_sorting_bin.set_initial_pose(
@@ -86,10 +89,13 @@ See :doc:`../../concepts/concept_assets_design` for details on asset architectur
8689

8790
.. code-block:: python
8891
92+
XY_RANGE_M = 0.025
8993
pick_up_object.set_initial_pose(
90-
Pose(
91-
position_xyz=(0.5785, 0.18, 0.0707),
92-
rotation_wxyz=(0.0, 0.0, 1.0, 0.0),
94+
PoseRange(
95+
position_xyz_min=(0.5785 - XY_RANGE_M, 0.18 - XY_RANGE_M, 0.0707),
96+
position_xyz_max=(0.5785 + XY_RANGE_M, 0.18 + XY_RANGE_M, 0.0707),
97+
rpy_min=(0.0, 0.0, 0.0),
98+
rpy_max=(0.0, 0.0, 0.0),
9399
)
94100
)
95101
blue_sorting_bin.set_initial_pose(

isaaclab_arena/assets/object.py

Lines changed: 59 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,12 @@
88
from isaaclab.assets import ArticulationCfg, AssetBaseCfg, RigidObjectCfg
99
from isaaclab.managers import EventTermCfg, SceneEntityCfg
1010
from isaaclab.sim.spawners.from_files.from_files_cfg import UsdFileCfg
11+
from isaaclab_tasks.manager_based.manipulation.stack.mdp.franka_stack_events import randomize_object_pose
1112

1213
from isaaclab_arena.assets.object_base import ObjectBase, ObjectType
1314
from isaaclab_arena.assets.object_utils import detect_object_type
1415
from isaaclab_arena.terms.events import set_object_pose
15-
from isaaclab_arena.utils.pose import Pose
16+
from isaaclab_arena.utils.pose import Pose, PoseRange
1617
from isaaclab_arena.utils.usd_helpers import has_light, open_stage
1718

1819

@@ -49,12 +50,19 @@ def __init__(
4950
self.object_cfg = self._init_object_cfg()
5051
self.event_cfg = self._init_event_cfg()
5152

52-
def set_initial_pose(self, pose: Pose) -> None:
53+
def set_initial_pose(self, pose: Pose | PoseRange) -> None:
54+
"""Set the initial pose of the object.
55+
56+
Args:
57+
pose: The pose to set. Can be a single pose or a pose range.
58+
In the case of a PoseRange, the object will be reset
59+
to a random pose within the range on environment reset.
60+
"""
5361
self.initial_pose = pose
5462
self.object_cfg = self._add_initial_pose_to_cfg(self.object_cfg)
5563
self.event_cfg = self._update_initial_pose_event_cfg(self.event_cfg)
5664

57-
def get_initial_pose(self) -> Pose | None:
65+
def get_initial_pose(self) -> Pose | PoseRange | None:
5866
return self.initial_pose
5967

6068
def is_initial_pose_set(self) -> bool:
@@ -131,8 +139,12 @@ def _add_initial_pose_to_cfg(
131139
) -> RigidObjectCfg | ArticulationCfg | AssetBaseCfg:
132140
# Optionally specify initial pose
133141
if self.initial_pose is not None:
134-
object_cfg.init_state.pos = self.initial_pose.position_xyz
135-
object_cfg.init_state.rot = self.initial_pose.rotation_wxyz
142+
if isinstance(self.initial_pose, Pose):
143+
initial_pose = self.initial_pose
144+
elif isinstance(self.initial_pose, PoseRange):
145+
initial_pose = self.initial_pose.get_midpoint()
146+
object_cfg.init_state.pos = initial_pose.position_xyz
147+
object_cfg.init_state.rot = initial_pose.rotation_wxyz
136148
return object_cfg
137149

138150
def _requires_reset_pose_event(self) -> bool:
@@ -144,24 +156,55 @@ def _requires_reset_pose_event(self) -> bool:
144156

145157
def _init_event_cfg(self) -> EventTermCfg | None:
146158
if self._requires_reset_pose_event():
147-
return EventTermCfg(
148-
func=set_object_pose,
149-
mode="reset",
150-
params={
151-
"pose": self.initial_pose,
152-
"asset_cfg": SceneEntityCfg(self.name),
153-
},
154-
)
159+
# Two possible event types:
160+
# - initial pose is a Pose - reset to a single pose
161+
# - initial pose is a PoseRange - reset to a random pose within the range
162+
if isinstance(self.initial_pose, Pose):
163+
return EventTermCfg(
164+
func=set_object_pose,
165+
mode="reset",
166+
params={
167+
"pose": self.initial_pose,
168+
"asset_cfg": SceneEntityCfg(self.name),
169+
},
170+
)
171+
elif isinstance(self.initial_pose, PoseRange):
172+
return EventTermCfg(
173+
func=randomize_object_pose,
174+
mode="reset",
175+
params={
176+
"pose_range": self.initial_pose.to_dict(),
177+
"asset_cfgs": [SceneEntityCfg(self.name)],
178+
},
179+
)
180+
else:
181+
raise ValueError(f"Initial pose {self.initial_pose} is not a Pose or PoseRange")
155182
else:
156183
return None
157184

185+
def _needs_reinit_of_event_cfg(self):
186+
# If there is no event cfg, needs to be reinitialized
187+
if self.event_cfg is None:
188+
return True
189+
# Here we check if the event cfg is for the correct pose type.
190+
# If not, needs to be reinitialized.
191+
if (isinstance(self.initial_pose, Pose) and ("pose" not in self.event_cfg.params)) or (
192+
isinstance(self.initial_pose, PoseRange) and ("pose_range" not in self.event_cfg.params)
193+
):
194+
return True
195+
return False
196+
158197
def _update_initial_pose_event_cfg(self, event_cfg: EventTermCfg | None) -> EventTermCfg | None:
159198
if self._requires_reset_pose_event():
160199
# Create an event cfg if one does not yet exist
161-
if event_cfg is None:
200+
if self._needs_reinit_of_event_cfg():
162201
event_cfg = self._init_event_cfg()
163-
# Add the initial pose to the event cfg
164-
event_cfg.params["pose"] = self.initial_pose
202+
if isinstance(self.initial_pose, Pose):
203+
event_cfg.params["pose"] = self.initial_pose
204+
elif isinstance(self.initial_pose, PoseRange):
205+
event_cfg.params["pose_range"] = self.initial_pose.to_dict()
206+
else:
207+
raise ValueError(f"Initial pose {self.initial_pose} is not a Pose or PoseRange")
165208
else:
166209
event_cfg = None
167210
return event_cfg

isaaclab_arena/tasks/g1_locomanip_pick_and_place_task.py

Lines changed: 3 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,13 @@
44
# SPDX-License-Identifier: Apache-2.0
55

66
import numpy as np
7-
import torch
87
from dataclasses import MISSING
98

109
import isaaclab.envs.mdp as mdp_isaac_lab
1110
from isaaclab.envs.common import ViewerCfg
1211
from isaaclab.envs.mimic_env_cfg import MimicEnvCfg, SubTaskConfig
13-
from isaaclab.managers import EventTermCfg, SceneEntityCfg, TerminationTermCfg
12+
from isaaclab.managers import SceneEntityCfg, TerminationTermCfg
1413
from isaaclab.utils import configclass
15-
from isaaclab.utils.math import euler_xyz_from_quat
16-
from isaaclab_tasks.manager_based.manipulation.stack.mdp import franka_stack_events
1714

1815
from isaaclab_arena.assets.asset import Asset
1916
from isaaclab_arena.embodiments.common.arm_mode import ArmMode
@@ -43,6 +40,7 @@ def __init__(
4340
if task_description is None
4441
else task_description
4542
)
43+
self.events_cfg = None
4644

4745
def get_scene_cfg(self):
4846
pass
@@ -71,7 +69,7 @@ def get_termination_cfg(self):
7169
)
7270

7371
def get_events_cfg(self):
74-
return EventsCfg(pick_up_object=self.pick_up_object)
72+
return self.events_cfg
7573

7674
def get_mimic_env_cfg(self, arm_mode: ArmMode):
7775
return G1LocomanipPickPlaceMimicEnvCfg()
@@ -97,39 +95,6 @@ class TerminationsCfg:
9795
object_dropped: TerminationTermCfg = MISSING
9896

9997

100-
@configclass
101-
class EventsCfg:
102-
"""Configuration for Pick and Place."""
103-
104-
reset_pick_up_object_pose: EventTermCfg = MISSING
105-
106-
def __init__(self, pick_up_object: Asset):
107-
initial_pose = pick_up_object.get_initial_pose()
108-
if initial_pose is not None:
109-
roll, pitch, yaw = euler_xyz_from_quat(torch.tensor(initial_pose.rotation_wxyz).reshape(1, 4))
110-
self.reset_pick_up_object_pose = EventTermCfg(
111-
func=franka_stack_events.randomize_object_pose,
112-
mode="reset",
113-
params={
114-
"pose_range": {
115-
"x": (initial_pose.position_xyz[0] - 0.025, initial_pose.position_xyz[0] + 0.025),
116-
"y": (initial_pose.position_xyz[1] - 0.025, initial_pose.position_xyz[1] + 0.025),
117-
"z": (initial_pose.position_xyz[2], initial_pose.position_xyz[2]),
118-
"roll": (roll, roll),
119-
"pitch": (pitch, pitch),
120-
"yaw": (yaw, yaw),
121-
},
122-
"asset_cfgs": [SceneEntityCfg(pick_up_object.name)],
123-
},
124-
)
125-
else:
126-
print(
127-
f"Pick up object {pick_up_object.name} has no initial pose. Not setting reset pick up object pose"
128-
" event."
129-
)
130-
self.reset_pick_up_object_pose = None
131-
132-
13398
@configclass
13499
class G1LocomanipPickPlaceMimicEnvCfg(MimicEnvCfg):
135100
"""

isaaclab_arena/tasks/lift_object_task.py

Lines changed: 4 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
import isaaclab.envs.mdp as mdp_isaac_lab
1010
from isaaclab.envs.common import ViewerCfg
11-
from isaaclab.managers import CommandTermCfg, EventTermCfg
11+
from isaaclab.managers import CommandTermCfg
1212
from isaaclab.managers import ObservationGroupCfg as ObsGroup
1313
from isaaclab.managers import ObservationTermCfg as ObsTerm
1414
from isaaclab.managers import RewardTermCfg, SceneEntityCfg, TerminationTermCfg
@@ -38,7 +38,7 @@ def __init__(
3838
self.background_scene = background_scene
3939
self.minimum_height_to_lift = minimum_height_to_lift
4040
self.scene_config = None
41-
self.events_cfg = LiftObjectEventsCfg(lift_object=self.lift_object, reset_pose_range=reset_pose_range.to_dict())
41+
self.events_cfg = None
4242
self.termination_cfg = self.make_termination_cfg()
4343

4444
def get_scene_cfg(self):
@@ -73,28 +73,6 @@ def get_viewer_cfg(self) -> ViewerCfg:
7373
)
7474

7575

76-
@configclass
77-
class LiftObjectEventsCfg:
78-
"""Configuration for Lift Object."""
79-
80-
reset_lift_object_pose: EventTermCfg = MISSING
81-
82-
def __init__(self, lift_object: Asset, reset_pose_range: dict[str, tuple[float, float]]):
83-
self.reset_lift_object_pose = EventTermCfg(
84-
func=mdp_isaac_lab.reset_root_state_uniform,
85-
mode="reset",
86-
params={
87-
"pose_range": {
88-
"x": reset_pose_range["x"],
89-
"y": reset_pose_range["y"],
90-
"z": reset_pose_range["z"],
91-
},
92-
"velocity_range": {},
93-
"asset_cfg": SceneEntityCfg(lift_object.name),
94-
},
95-
)
96-
97-
9876
@configclass
9977
class LiftObjectTerminationsCfg:
10078
"""Termination terms for the Lift Object task."""
@@ -111,14 +89,12 @@ def __init__(
11189
embodiment: EmbodimentBase,
11290
minimum_height_to_lift: float = 0.04,
11391
episode_length_s: float = 5.0,
114-
reset_pose_range: PoseRange = PoseRange(),
11592
):
11693
super().__init__(
11794
lift_object=lift_object,
11895
background_scene=background_scene,
11996
minimum_height_to_lift=minimum_height_to_lift,
12097
episode_length_s=episode_length_s,
121-
reset_pose_range=reset_pose_range,
12298
)
12399
self.embodiment = embodiment
124100
self.observation_cfg = LiftObjectObservationsCfg(
@@ -180,6 +156,8 @@ class LiftObjectCommandsCfg:
180156

181157
def __init__(self, asset_name: str, body_name: str, lift_object: Asset):
182158
initial_pose = lift_object.get_initial_pose()
159+
if isinstance(initial_pose, PoseRange):
160+
initial_pose = initial_pose.get_midpoint()
183161
self.object_pose = mdp_isaac_lab.UniformPoseCommandCfg(
184162
asset_name=asset_name,
185163
body_name=body_name,

isaaclab_arena/tasks/pick_and_place_task.py

Lines changed: 2 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import isaaclab.envs.mdp as mdp_isaac_lab
1010
from isaaclab.envs.common import ViewerCfg
1111
from isaaclab.envs.mimic_env_cfg import MimicEnvCfg, SubTaskConfig
12-
from isaaclab.managers import EventTermCfg, SceneEntityCfg, TerminationTermCfg
12+
from isaaclab.managers import SceneEntityCfg, TerminationTermCfg
1313
from isaaclab.sensors.contact_sensor.contact_sensor_cfg import ContactSensorCfg
1414
from isaaclab.utils import configclass
1515

@@ -21,7 +21,6 @@
2121
from isaaclab_arena.tasks.task_base import TaskBase
2222
from isaaclab_arena.tasks.terminations import object_on_destination
2323
from isaaclab_arena.utils.cameras import get_viewer_cfg_look_at_object
24-
from isaaclab_arena.utils.pose import PoseRange
2524

2625

2726
class PickAndPlaceTask(TaskBase):
@@ -34,7 +33,6 @@ def __init__(
3433
destination_object: Asset | None = None,
3534
episode_length_s: float | None = None,
3635
task_description: str | None = None,
37-
reset_pose_range: PoseRange = PoseRange(),
3836
):
3937
super().__init__(episode_length_s=episode_length_s)
4038
self.pick_up_object = pick_up_object
@@ -46,9 +44,7 @@ def __init__(
4644
contact_against_prim_paths=[self.destination_location.get_prim_path()],
4745
),
4846
)
49-
self.events_cfg = PickPlaceEventsCfg(
50-
pick_up_object=self.pick_up_object, reset_pose_range=reset_pose_range.to_dict()
51-
)
47+
self.events_cfg = None
5248
self.termination_cfg = self.make_termination_cfg()
5349
self.task_description = (
5450
f"Pick up the {pick_up_object.name}, and place it into the {destination_location.name}"
@@ -122,28 +118,6 @@ class TerminationsCfg:
122118
object_dropped: TerminationTermCfg = MISSING
123119

124120

125-
@configclass
126-
class PickPlaceEventsCfg:
127-
"""Configuration for Pick Up Object."""
128-
129-
reset_pick_up_object_pose: EventTermCfg = MISSING
130-
131-
def __init__(self, pick_up_object: Asset, reset_pose_range: dict[str, tuple[float, float]]):
132-
self.reset_pick_up_object_pose = EventTermCfg(
133-
func=mdp_isaac_lab.reset_root_state_uniform,
134-
mode="reset",
135-
params={
136-
"pose_range": {
137-
"x": reset_pose_range["x"],
138-
"y": reset_pose_range["y"],
139-
"z": reset_pose_range["z"],
140-
},
141-
"velocity_range": {},
142-
"asset_cfg": SceneEntityCfg(pick_up_object.name),
143-
},
144-
)
145-
146-
147121
@configclass
148122
class PickPlaceMimicEnvCfg(MimicEnvCfg):
149123
"""

0 commit comments

Comments
 (0)