Skip to content

Commit f3a05b1

Browse files
(refactor) refactor gym_manipulator.py to use the new kinematics from placo
1 parent ab74a87 commit f3a05b1

File tree

4 files changed

+59
-66
lines changed

4 files changed

+59
-66
lines changed

lerobot/common/model/kinematics.py

Lines changed: 31 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,123 +1,117 @@
11
import numpy as np
22

33

4-
class PlacoRobotKinematics:
4+
class RobotKinematics:
55
"""Robot kinematics using placo library for forward and inverse kinematics."""
6-
6+
77
def __init__(self, urdf_path: str, ee_frame_name: str = "gripper_tip", joint_names: list[str] = None):
88
"""
99
Initialize placo-based kinematics solver.
10-
10+
1111
Args:
1212
urdf_path: Path to the robot URDF file
1313
ee_frame_name: Name of the end-effector frame in the URDF
1414
joint_names: List of joint names to control (if None, will use default naming)
1515
"""
16-
try:
17-
import placo
18-
except ImportError:
19-
raise ImportError(
20-
"placo is required for PlacoRobotKinematics. "
21-
"Please install it with: pip install placo"
22-
)
23-
16+
import placo
17+
2418
self.robot = placo.RobotWrapper(urdf_path)
2519
self.solver = placo.KinematicsSolver(self.robot)
2620
self.solver.mask_fbase(True) # Fix the base
27-
21+
2822
self.ee_frame_name = ee_frame_name
29-
23+
3024
# Set joint names
3125
if joint_names is None:
3226
# Default joint names for SO-ARM100
3327
self.joint_names = ["1", "2", "3", "4", "5"]
3428
else:
3529
self.joint_names = joint_names
36-
30+
3731
# Initialize frame task for IK
3832
self.tip_starting_pose = np.eye(4)
3933
self.tip_frame = self.solver.add_frame_task(self.ee_frame_name, self.tip_starting_pose)
4034
self.tip_frame.configure(self.ee_frame_name, "soft", 1.0, 1.0)
41-
35+
4236
def forward_kinematics(self, robot_pos_deg, frame=None):
4337
"""
4438
Compute forward kinematics for given joint configuration.
45-
39+
4640
Args:
4741
robot_pos_deg: Joint positions in degrees (numpy array)
4842
frame: Target frame name (if None, uses ee_frame_name)
49-
43+
5044
Returns:
5145
4x4 transformation matrix of the end-effector pose
5246
"""
5347
if frame is None:
5448
frame = self.ee_frame_name
55-
49+
5650
# Convert degrees to radians
57-
robot_pos_rad = np.deg2rad(robot_pos_deg[:len(self.joint_names)])
58-
51+
robot_pos_rad = np.deg2rad(robot_pos_deg[: len(self.joint_names)])
52+
5953
# Update joint positions in placo robot
6054
for i, joint_name in enumerate(self.joint_names):
6155
self.robot.set_joint(joint_name, robot_pos_rad[i])
62-
56+
6357
# Update kinematics
6458
self.robot.update_kinematics()
65-
59+
6660
# Get the transformation matrix
6761
return self.robot.get_T_world_frame(frame)
68-
62+
6963
def ik(self, current_joint_pos, desired_ee_pose, position_only=True, frame=None):
7064
"""
7165
Compute inverse kinematics using placo solver.
72-
66+
7367
Args:
7468
current_joint_pos: Current joint positions in degrees (used as initial guess)
7569
desired_ee_pose: Target end-effector pose as a 4x4 transformation matrix
7670
position_only: If True, only match position (not orientation)
7771
frame: Target frame name (if None, uses ee_frame_name)
78-
72+
7973
Returns:
8074
Joint positions in degrees that achieve the desired end-effector pose
8175
"""
8276
if frame is None:
8377
frame = self.ee_frame_name
84-
78+
8579
# Convert current joint positions to radians for initial guess
86-
current_joint_rad = np.deg2rad(current_joint_pos[:len(self.joint_names)])
87-
80+
current_joint_rad = np.deg2rad(current_joint_pos[: len(self.joint_names)])
81+
8882
# Set current joint positions as initial guess
8983
for i, joint_name in enumerate(self.joint_names):
9084
self.robot.set_joint(joint_name, current_joint_rad[i])
91-
85+
9286
# Update the target pose for the frame task
9387
self.tip_frame.T_world_frame = desired_ee_pose
94-
88+
9589
# Configure the task based on position_only flag
9690
if position_only:
9791
# Only constrain position, not orientation
9892
self.tip_frame.configure(self.ee_frame_name, "soft", 1.0, 0.0)
9993
else:
10094
# Constrain both position and orientation
10195
self.tip_frame.configure(self.ee_frame_name, "soft", 1.0, 1.0)
102-
96+
10397
# Solve IK
10498
self.solver.solve(True)
10599
self.robot.update_kinematics()
106-
100+
107101
# Extract joint positions
108102
joint_positions_rad = []
109103
for joint_name in self.joint_names:
110104
joint = self.robot.get_joint(joint_name)
111105
joint_positions_rad.append(joint)
112-
106+
113107
# Convert back to degrees
114108
joint_positions_deg = np.rad2deg(joint_positions_rad)
115-
109+
116110
# Preserve gripper position if present in current_joint_pos
117111
if len(current_joint_pos) > len(self.joint_names):
118112
result = np.zeros_like(current_joint_pos)
119-
result[:len(self.joint_names)] = joint_positions_deg
120-
result[len(self.joint_names):] = current_joint_pos[len(self.joint_names):]
113+
result[: len(self.joint_names)] = joint_positions_deg
114+
result[len(self.joint_names) :] = current_joint_pos[len(self.joint_names) :]
121115
return result
122116
else:
123-
return joint_positions_deg
117+
return joint_positions_deg

lerobot/common/robots/so100_follower/config_so100_follower.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,10 @@ class SO100FollowerEndEffectorConfig(SO100FollowerConfig):
4646

4747
# Path to URDF file for kinematics
4848
urdf_path: str | None = None
49-
49+
5050
# End-effector frame name in the URDF
5151
ee_frame_name: str = "gripperframe"
52-
52+
5353
# Joint names for kinematics (if None, uses default naming)
5454
joint_names: list[str] | None = None
5555

lerobot/common/robots/so100_follower/so100_follower_end_effector.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222

2323
from lerobot.common.cameras import make_cameras_from_configs
2424
from lerobot.common.errors import DeviceNotConnectedError
25-
from lerobot.common.model.kinematics_utils import PlacoRobotKinematics
25+
from lerobot.common.model.kinematics import RobotKinematics
2626
from lerobot.common.motors import Motor, MotorNormMode
2727
from lerobot.common.motors.feetech import FeetechMotorsBus
2828

@@ -68,11 +68,11 @@ def __init__(self, config: SO100FollowerEndEffectorConfig):
6868
"urdf_path must be provided in the configuration for end-effector control. "
6969
"Please set urdf_path in your SO100FollowerEndEffectorConfig."
7070
)
71-
72-
self.kinematics = PlacoRobotKinematics(
71+
72+
self.kinematics = RobotKinematics(
7373
urdf_path=self.config.urdf_path,
7474
ee_frame_name=self.config.ee_frame_name,
75-
joint_names=self.config.joint_names
75+
joint_names=self.config.joint_names,
7676
)
7777

7878
# Store the bounds for end-effector position
@@ -135,7 +135,7 @@ def send_action(self, action: dict[str, Any]) -> dict[str, Any]:
135135

136136
# Calculate current end-effector position using forward kinematics
137137
if self.current_ee_pos is None:
138-
self.current_ee_pos = self.kinematics.forward_kinematics(self.current_joint_pos, frame=self.config.ee_frame_name)
138+
self.current_ee_pos = self.kinematics.forward_kinematics(self.current_joint_pos)
139139

140140
# Set desired end-effector position by adding delta
141141
desired_ee_pos = np.eye(4)
@@ -152,7 +152,7 @@ def send_action(self, action: dict[str, Any]) -> dict[str, Any]:
152152

153153
# Compute inverse kinematics to get joint positions
154154
target_joint_values_in_degrees = self.kinematics.ik(
155-
self.current_joint_pos, desired_ee_pos, position_only=True, frame=self.config.ee_frame_name
155+
self.current_joint_pos, desired_ee_pos, position_only=True
156156
)
157157

158158
target_joint_values_in_degrees = np.clip(target_joint_values_in_degrees, -180.0, 180.0)

lerobot/scripts/rl/gym_manipulator.py

Lines changed: 20 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,7 @@ def __init__(
264264
def _get_observation(self) -> np.ndarray:
265265
"""Helper to convert a dictionary from bus.sync_read to an ordered numpy array."""
266266
obs_dict = self.robot.get_observation()
267-
joint_positions = np.array([obs_dict[name] for name in self._joint_names], dtype=np.float32)
267+
joint_positions = np.array([obs_dict[name] for name in self._joint_names])
268268

269269
images = {key: obs_dict[key] for key in self._image_keys}
270270
return {"agent_pos": joint_positions, "pixels": images}
@@ -1090,13 +1090,11 @@ def __init__(self, env, ee_pose_limits):
10901090
dtype=np.float32,
10911091
)
10921092

1093-
# Initialize kinematics instance for the appropriate robot type
1094-
robot_type = getattr(env.unwrapped.robot.config, "robot_type", "so101")
1095-
if "so100" in robot_type or "so101" in robot_type:
1096-
# Note to be compatible with the rest of the codebase,
1097-
# we are using the new calibration method for so101 and so100
1098-
robot_type = "so_new_calibration"
1099-
self.kinematics = RobotKinematics(robot_type)
1093+
self.kinematics = RobotKinematics(
1094+
urdf_path=env.unwrapped.robot.config.urdf_path,
1095+
ee_frame_name=env.unwrapped.robot.config.ee_frame_name,
1096+
joint_names=env.unwrapped.robot.config.joint_names,
1097+
)
11001098

11011099
def observation(self, observation):
11021100
"""
@@ -1110,7 +1108,7 @@ def observation(self, observation):
11101108
"""
11111109
current_joint_pos = self.unwrapped._get_observation()["agent_pos"]
11121110

1113-
current_ee_pos = self.kinematics.forward_kinematics(current_joint_pos, frame="gripper_tip")[:3, 3]
1111+
current_ee_pos = self.kinematics.forward_kinematics(current_joint_pos)[:3, 3]
11141112
observation["agent_pos"] = np.concatenate([observation["agent_pos"], current_ee_pos], -1)
11151113
return observation
11161114

@@ -1157,12 +1155,11 @@ def __init__(
11571155
self.event_lock = Lock() # Thread-safe access to events
11581156

11591157
# Initialize robot control
1160-
robot_type = getattr(env.unwrapped.robot.config, "robot_type", "so101")
1161-
if "so100" in robot_type or "so101" in robot_type:
1162-
# Note to be compatible with the rest of the codebase,
1163-
# we are using the new calibration method for so101 and so100
1164-
robot_type = "so_new_calibration"
1165-
self.kinematics = RobotKinematics(robot_type)
1158+
self.kinematics = RobotKinematics(
1159+
urdf_path=env.unwrapped.robot.config.urdf_path,
1160+
ee_frame_name=env.unwrapped.robot.config.ee_frame_name,
1161+
joint_names=env.unwrapped.robot.config.joint_names,
1162+
)
11661163
self.leader_torque_enabled = True
11671164
self.prev_leader_gripper = None
11681165

@@ -1260,14 +1257,14 @@ def _handle_intervention(self, action):
12601257
leader_pos_dict = self.robot_leader.bus.sync_read("Present_Position")
12611258
follower_pos_dict = self.robot_follower.bus.sync_read("Present_Position")
12621259

1263-
leader_pos = np.array([leader_pos_dict[name] for name in leader_pos_dict], dtype=np.float32)
1264-
follower_pos = np.array([follower_pos_dict[name] for name in follower_pos_dict], dtype=np.float32)
1260+
leader_pos = np.array([leader_pos_dict[name] for name in leader_pos_dict])
1261+
follower_pos = np.array([follower_pos_dict[name] for name in follower_pos_dict])
12651262

12661263
self.leader_tracking_error_queue.append(np.linalg.norm(follower_pos[:-1] - leader_pos[:-1]))
12671264

12681265
# [:3, 3] Last column of the transformation matrix corresponds to the xyz translation
1269-
leader_ee = self.kinematics.forward_kinematics(leader_pos, frame="gripper_tip")[:3, 3]
1270-
follower_ee = self.kinematics.forward_kinematics(follower_pos, frame="gripper_tip")[:3, 3]
1266+
leader_ee = self.kinematics.forward_kinematics(leader_pos)[:3, 3]
1267+
follower_ee = self.kinematics.forward_kinematics(follower_pos)[:3, 3]
12711268

12721269
action = np.clip(leader_ee - follower_ee, -self.end_effector_step_sizes, self.end_effector_step_sizes)
12731270
# Normalize the action to the range [-1, 1]
@@ -1293,7 +1290,6 @@ def _handle_intervention(self, action):
12931290
gripper_action = 1
12941291

12951292
action = np.append(action, gripper_action)
1296-
action = torch.from_numpy(action).float()
12971293

12981294
return action
12991295

@@ -1342,9 +1338,12 @@ def step(self, action):
13421338
# NOTE:
13431339
obs, reward, terminated, truncated, info = self.env.step(action)
13441340

1341+
if isinstance(action, np.ndarray):
1342+
action = torch.from_numpy(action)
1343+
13451344
# Add intervention info
13461345
info["is_intervention"] = is_intervention
1347-
info["action_intervention"] = action if is_intervention else None
1346+
info["action_intervention"] = action
13481347

13491348
self.prev_leader_gripper = np.clip(
13501349
self.robot_leader.bus.sync_read("Present_Position")["gripper"],

0 commit comments

Comments
 (0)