Skip to content

Commit d1cab0b

Browse files
Add input_ref_frame = base option for whole body ik and devices (#754)
* Add input_ref_frame = base option for whole body ik and devices * Correct goal_update_name: desired -> target * Add more finegrained input_ref_frame for ik controller * Update function name to controller_base_pose * Check robot has torso * Allow torso action as part of base actions * Enable torso action for joint position delta controller * Add assert for WHOLE_BODY_IK has 1 robot only * Format * Update default goal update mode
1 parent fc6a02c commit d1cab0b

File tree

6 files changed

+241
-27
lines changed

6 files changed

+241
-27
lines changed

robosuite/controllers/composite/composite_controller.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -474,5 +474,6 @@ def _init_joint_action_policy(self):
474474
max_dq_torso=self.composite_controller_specific_config.get("ik_max_dq_torso", 0.2),
475475
input_rotation_repr=self.composite_controller_specific_config.get("ik_input_rotation_repr", "axis_angle"),
476476
input_type=self.composite_controller_specific_config.get("ik_input_type", "axis_angle"),
477+
input_ref_frame=self.composite_controller_specific_config.get("ik_input_ref_frame", "world"),
477478
debug=self.composite_controller_specific_config.get("verbose", False),
478479
)
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
{
2+
"type": "WHOLE_BODY_IK",
3+
"composite_controller_specific_configs": {
4+
"ref_name": ["gripper0_right_grip_site", "gripper0_left_grip_site"],
5+
"interpolation": null,
6+
"actuation_part_names": ["right", "left"],
7+
"max_dq": 4,
8+
"nullspace_joint_weights": {
9+
"robot0_torso_waist_yaw": 100.0,
10+
"robot0_torso_waist_pitch": 100.0,
11+
"robot0_torso_waist_roll": 500.0,
12+
"robot0_l_shoulder_pitch": 4.0,
13+
"robot0_r_shoulder_pitch": 4.0,
14+
"robot0_l_shoulder_roll": 3.0,
15+
"robot0_r_shoulder_roll": 3.0,
16+
"robot0_l_shoulder_yaw": 2.0,
17+
"robot0_r_shoulder_yaw": 2.0
18+
},
19+
"ik_pseudo_inverse_damping": 5e-2,
20+
"ik_integration_dt": 1e-1,
21+
"ik_max_dq": 4.0,
22+
"ik_max_dq_torso": 0.2,
23+
"ik_input_type": "absolute",
24+
"ik_input_ref_frame": "mobilebase0_base",
25+
"ik_input_rotation_repr": "axis_angle",
26+
"verbose": false
27+
},
28+
"body_parts": {
29+
"arms": {
30+
"right": {
31+
"type" : "JOINT_POSITION",
32+
"input_max": 1,
33+
"input_min": -1,
34+
"input_type": "absolute",
35+
"output_max": 0.5,
36+
"output_min": -0.5,
37+
"kd": 200,
38+
"kv": 200,
39+
"kp": 1000,
40+
"velocity_limits": [-1,1],
41+
"kp_limits": [0, 1000],
42+
"interpolation": null,
43+
"ramp_ratio": 0.2,
44+
"gripper": {
45+
"type": "GRIP"
46+
}
47+
},
48+
"left": {
49+
"type" : "JOINT_POSITION",
50+
"input_max": 1,
51+
"input_min": -1,
52+
"input_type": "absolute",
53+
"output_max": 0.5,
54+
"output_min": -0.5,
55+
"kd": 200,
56+
"kv": 200,
57+
"kp": 1000,
58+
"velocity_limits": [-1,1],
59+
"kp_limits": [0, 1000],
60+
"interpolation": null,
61+
"ramp_ratio": 0.2,
62+
"gripper": {
63+
"type": "GRIP"
64+
}
65+
}
66+
},
67+
"torso": {
68+
"type" : "JOINT_POSITION",
69+
"input_max": 1,
70+
"input_min": -1,
71+
"input_type": "absolute",
72+
"output_max": 0.5,
73+
"output_min": -0.5,
74+
"kd": 200,
75+
"kv": 200,
76+
"kp": 1000,
77+
"velocity_limits": [-1,1],
78+
"kp_limits": [0, 1000],
79+
"interpolation": null,
80+
"ramp_ratio": 0.2
81+
},
82+
"head": {
83+
"type" : "JOINT_POSITION",
84+
"input_max": 1,
85+
"input_min": -1,
86+
"input_type": "absolute",
87+
"output_max": 0.5,
88+
"output_min": -0.5,
89+
"kd": 200,
90+
"kv": 200,
91+
"kp": 1000,
92+
"velocity_limits": [-1,1],
93+
"kp_limits": [0, 1000],
94+
"interpolation": null,
95+
"ramp_ratio": 0.2
96+
},
97+
"base": {
98+
"type" : "JOINT_VELOCITY",
99+
"interpolation": null
100+
},
101+
"legs": {
102+
"type": "JOINT_POSITION",
103+
"input_max": 1,
104+
"input_min": -1,
105+
"output_max": 0.5,
106+
"output_min": -0.5,
107+
"kd": 200,
108+
"kv": 200,
109+
"kp": 1000,
110+
"velocity_limits": [-1,1],
111+
"kp_limits": [0, 1000],
112+
"interpolation": null,
113+
"ramp_ratio": 0.2
114+
}
115+
}
116+
}

robosuite/devices/device.py

Lines changed: 70 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ def _reset_internal_state(self):
3434
self.base_modes = [False] * len(self.all_robot_arms)
3535

3636
self._prev_target = {arm: None for arm in self.all_robot_arms[self.active_robot]}
37+
self._prev_torso_target = None
3738

3839
@property
3940
def active_arm(self):
@@ -71,7 +72,7 @@ def get_controller_state(self) -> Dict:
7172
def _postprocess_device_outputs(self, dpos, drotation):
7273
raise NotImplementedError
7374

74-
def input2action(self, mirror_actions=False) -> Optional[Dict]:
75+
def input2action(self, mirror_actions=False, goal_update_mode="target") -> Optional[Dict]:
7576
"""
7677
Converts an input from an active device into a valid action sequence that can be fed into an env.step() call
7778
@@ -80,6 +81,8 @@ def input2action(self, mirror_actions=False) -> Optional[Dict]:
8081
Args:
8182
mirror_actions (bool): actions corresponding to viewing robot from behind.
8283
first axis: left/right. second axis: back/forward. third axis: down/up.
84+
goal_update_mode (str): the mode to update the goal in. Can be 'target' or 'achieved'.
85+
If 'target', the goal is updated based on the current target goal. If 'achieved', the goal is updated based on the current achieved state.
8386
8487
Returns:
8588
Optional[Dict]: Dictionary of actions to be fed into env.step()
@@ -134,6 +137,7 @@ def input2action(self, mirror_actions=False) -> Optional[Dict]:
134137
robot,
135138
arm,
136139
norm_delta=np.zeros(6),
140+
goal_update_mode=goal_update_mode,
137141
)
138142
ac_dict[f"{arm}_abs"] = arm_action["abs"]
139143
ac_dict[f"{arm}_delta"] = arm_action["delta"]
@@ -144,17 +148,20 @@ def input2action(self, mirror_actions=False) -> Optional[Dict]:
144148
if base_mode is True:
145149
arm_norm_delta = np.zeros(6)
146150
base_ac = np.array([dpos[0], dpos[1], drotation[2]])
147-
torso_ac = np.array([dpos[2]])
151+
device_torso_input = dpos[2] # Use vertical movement for torso in base mode
148152
else:
149153
arm_norm_delta = np.concatenate([dpos, drotation])
150154
base_ac = np.zeros(3)
151-
torso_ac = np.zeros(1)
155+
device_torso_input = 0.0 # No torso input when not in base mode
152156

153157
ac_dict["base"] = base_ac
154-
# ac_dict["torso"] = torso_ac
155158
ac_dict["base_mode"] = np.array([1 if base_mode is True else -1])
156159
else:
157160
arm_norm_delta = np.concatenate([dpos, drotation])
161+
device_torso_input = 0.0 # No torso input for non-mobile robots by default
162+
163+
if hasattr(robot, "torso") and robot.torso is not None:
164+
ac_dict["torso"] = self.get_torso_action(robot, device_torso_input)
158165

159166
# populate action dict items for arm and grippers
160167
arm_action = self.get_arm_action(
@@ -183,7 +190,7 @@ def get_arm_action(self, robot, arm, norm_delta, goal_update_mode="target"):
183190
assert goal_update_mode in [
184191
"achieved",
185192
"target",
186-
] # update next target either based on achieved pose or current target pose
193+
], f"goal_update_mode must be either 'achieved' or 'target', got {goal_update_mode}" # update next target either based on achieved pose or current target pose
187194

188195
# TODO: the logic between OSC and while body based ik is fragmented right now. Unify
189196
if isinstance(robot.part_controllers[arm], OperationalSpaceController):
@@ -249,6 +256,10 @@ def get_arm_action(self, robot, arm, norm_delta, goal_update_mode="target"):
249256
pos = self._prev_target[arm][0:3].copy()
250257
ori = T.quat2mat(T.axisangle2quat(self._prev_target[arm][3:6].copy()))
251258

259+
ref_frame = self.env.robots[0].composite_controller.composite_controller_specific_config.get(
260+
"ik_input_ref_frame", "world"
261+
)
262+
252263
delta_action = norm_delta.copy()
253264
delta_action[0:3] *= 0.05
254265
delta_action[3:6] *= 0.15
@@ -262,9 +273,63 @@ def get_arm_action(self, robot, arm, norm_delta, goal_update_mode="target"):
262273
abs_action = np.concatenate([new_pos, new_axisangle])
263274
self._prev_target[arm] = abs_action.copy()
264275

276+
# convert to be w.r.t base frame
277+
if ref_frame != "world":
278+
# convert to matrix format
279+
abs_action_mat = T.make_pose(
280+
translation=abs_action[0:3],
281+
rotation=T.quat2mat(T.axisangle2quat(abs_action[3:6])),
282+
)
283+
delta_action_mat = T.make_pose(
284+
translation=delta_action[0:3],
285+
rotation=T.quat2mat(T.axisangle2quat(delta_action[3:6])),
286+
)
287+
abs_action_base_mat = self.env.robots[0].composite_controller.joint_action_policy.transform_pose(
288+
src_frame_pose=abs_action_mat,
289+
src_frame="world",
290+
dst_frame=ref_frame,
291+
)
292+
delta_action_base_mat = self.env.robots[0].composite_controller.joint_action_policy.transform_pose(
293+
src_frame_pose=delta_action_mat,
294+
src_frame="world",
295+
dst_frame=ref_frame,
296+
)
297+
# get the new delta action and abs action position and orientation from the matrix
298+
delta_action = np.concatenate(
299+
[delta_action_base_mat[:3, 3], T.quat2axisangle(T.mat2quat(delta_action_base_mat[:3, :3]))]
300+
)
301+
abs_action = np.concatenate(
302+
[abs_action_base_mat[:3, 3], T.quat2axisangle(T.mat2quat(abs_action_base_mat[:3, :3]))]
303+
)
304+
265305
return {
266306
"delta": delta_action,
267307
"abs": abs_action,
268308
}
269309
else:
270310
raise NotImplementedError
311+
312+
def get_torso_action(self, robot, device_input):
313+
"""Generate torso action from device input"""
314+
if robot.torso is None:
315+
return np.zeros(1)
316+
317+
torso_controller = robot.part_controllers[robot.torso]
318+
319+
if torso_controller.name == "JOINT_POSITION":
320+
if torso_controller.input_type == "delta":
321+
scale = 0.2
322+
return np.array([device_input * scale])
323+
else:
324+
scale = 0.01
325+
target = self._prev_torso_target if self._prev_torso_target is not None else torso_controller.goal_qpos
326+
if abs(device_input) < 1e-6:
327+
action = target
328+
else:
329+
action = target + np.array([device_input * scale])
330+
self._prev_torso_target = action.copy()
331+
return action
332+
elif torso_controller.name == "JOINT_VELOCITY":
333+
return np.array([device_input * 0.5])
334+
else:
335+
return np.zeros(1)

robosuite/devices/mjgui.py

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -116,13 +116,16 @@ def _reset_internal_state(self):
116116
target_mat = self.env.sim.data.site_xmat[self.env.sim.model.site_name2id(site_name)]
117117
set_mocap_pose(self.env.sim, target_pos, target_mat, f"{target_name_prefix}_eef_target")
118118

119-
def input2action(self) -> Dict[str, np.ndarray]:
119+
def input2action(self, goal_update_mode="desired") -> Dict[str, np.ndarray]:
120120
"""
121121
Uses mocap body poses to determine action for robot. Obtain input_type
122122
(i.e. absolute actions or delta actions) and input_ref_frame (i.e. world frame, base frame or eef frame)
123123
from the controller itself.
124124
125125
"""
126+
assert (
127+
goal_update_mode == "desired"
128+
), "goal_update_mode must be 'desired' for MJGUI: targets are based off the pose of the mocap body."
126129
# TODO: unify this logic to be independent from controller type.
127130
action: Dict[str, np.ndarray] = {}
128131
gripper_dof = self.env.robots[0].gripper[self.active_end_effector].dof
@@ -131,20 +134,6 @@ def input2action(self) -> Dict[str, np.ndarray]:
131134
target_name_prefix = "right" if "right" in site_name else "left" # hardcoded for now
132135
target_pos_world, target_ori_mat_world = get_mocap_pose(self.env.sim, f"{target_name_prefix}_eef_target")
133136

134-
if isinstance(self.env.robots[0].composite_controller, WholeBodyIK):
135-
assert (
136-
self.env.robots[0].composite_controller.composite_controller_specific_config.get(
137-
"ik_input_ref_frame", "world"
138-
)
139-
== "world"
140-
), ("Only support world frame for MJGui teleop for now. " "Please modify the controller configs.")
141-
assert (
142-
self.env.robots[0].composite_controller.composite_controller_specific_config.get(
143-
"ik_input_type", "absolute"
144-
)
145-
== "absolute"
146-
), ("Only support absolute actions for MJGui teleop for now. " "Please modify the controller configs.")
147-
# check if need to update frames
148137
if isinstance(self.env.robots[0].composite_controller, WholeBody):
149138
# TODO: should be more general
150139
if (

robosuite/scripts/collect_human_demonstrations.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
from robosuite.wrappers import DataCollectionWrapper, VisualizationWrapper
2121

2222

23-
def collect_human_trajectory(env, device, arm, max_fr):
23+
def collect_human_trajectory(env, device, arm, max_fr, goal_update_mode):
2424
"""
2525
Use the device (keyboard or SpaceNav 3D mouse) to collect a demonstration.
2626
The rollout trajectory is saved to files in npz format.
@@ -60,7 +60,7 @@ def collect_human_trajectory(env, device, arm, max_fr):
6060
active_robot = env.robots[device.active_robot]
6161

6262
# Get the newest action
63-
input_ac_dict = device.input2action()
63+
input_ac_dict = device.input2action(goal_update_mode=goal_update_mode)
6464

6565
# If action is none, then this a reset so we should break
6666
if input_ac_dict is None:
@@ -279,6 +279,15 @@ def gather_demonstrations_as_hdf5(directory, out_dir, env_info):
279279
default=False,
280280
help="(DualSense Only)Reverse the effect of the x and y axes of the joystick.It is used to handle the case that the left/right and front/back sides of the view are opposite to the LX and LY of the joystick(Push LX up but the robot move left in your view)",
281281
)
282+
parser.add_argument(
283+
"--goal_update_mode",
284+
type=str,
285+
default="target",
286+
choices=["target", "achieved"],
287+
help="Used by the device to get the arm's actions. The mode to update the goal in. Can be 'target' or 'achieved'. If 'target', the goal is updated based on the current target pose. "
288+
"If 'achieved', the goal is updated based on the current achieved state. "
289+
"We recommend using 'achieved' (and input_ref_frame='base') if collecting demonstrations with a mobile base robot.",
290+
)
282291
args = parser.parse_args()
283292

284293
# Get controller config
@@ -291,6 +300,10 @@ def gather_demonstrations_as_hdf5(directory, out_dir, env_info):
291300
# mink-speicific import. requires installing mink
292301
from robosuite.examples.third_party_controller.mink_controller import WholeBodyMinkIK
293302

303+
# if WHOLE BODY IK; assert only one robot
304+
if controller_config["type"] == "WHOLE_BODY_IK":
305+
assert len(args.robots) == 1, "Whole Body IK only supports one robot"
306+
294307
# Create argument configuration
295308
config = {
296309
"env_name": args.environment,
@@ -366,5 +379,5 @@ def gather_demonstrations_as_hdf5(directory, out_dir, env_info):
366379

367380
# collect demonstrations
368381
while True:
369-
collect_human_trajectory(env, device, args.arm, args.max_fr)
382+
collect_human_trajectory(env, device, args.arm, args.max_fr, args.goal_update_mode)
370383
gather_demonstrations_as_hdf5(tmp_directory, new_dir, env_info)

0 commit comments

Comments
 (0)