Browse Source

Fix pytorch kinematics

remote-reachy2-improvements
haixuanTao 8 months ago
parent
commit
f4c10569f7
1 changed files with 27 additions and 27 deletions
  1. +27
    -27
      node-hub/dora-pytorch-kinematics/dora_pytorch_kinematics/main.py

+ 27
- 27
node-hub/dora-pytorch-kinematics/dora_pytorch_kinematics/main.py View File

@@ -11,7 +11,7 @@ from dora import Node
from pytorch_kinematics.transforms.rotation_conversions import matrix_to_euler_angles

TRANSFORM = np.array(os.getenv("TRANSFORM", "0. 0. 0. 0. 0. 0. 1.").split(" ")).astype(
np.float32
np.float32,
)
pos = torch.tensor([TRANSFORM[0], TRANSFORM[1], TRANSFORM[2]])
rot = torch.tensor(
@@ -26,9 +26,11 @@ ROB_TF = pk.Transform3d(pos=pos, rot=rot)


def get_xyz_rpy_array_from_transform3d(
transform: "pytorch_kinematics.transforms.Transform3d", convention: str = "XYZ"
transform: "pk.transforms.Transform3d", convention: str = "XYZ",
) -> torch.Tensor:
"""Extracts translation (xyz) and rotation (RPY Euler angles in radians)
"""XYZ-RPY conversion.
Extract translation (xyz) and rotation (RPY Euler angles in radians)
from a pytorch_kinematics Transform3d object and returns them concatenated
into a single tensor.

@@ -59,7 +61,7 @@ def get_xyz_rpy_array_from_transform3d(
# Convert the rotation matrix to Euler angles in radians
# The matrix_to_euler_angles function likely exists based on pytorch3d's structure
rpy = matrix_to_euler_angles(
rotation_matrix, convention=convention
rotation_matrix, convention=convention,
) # Shape (..., 3)

# Concatenate xyz and rpy along the last dimension
@@ -67,9 +69,7 @@ def get_xyz_rpy_array_from_transform3d(


class RobotKinematics:
"""Concise wrapper for pytorch_kinematics FK and IK operations,
closely mirroring library usage patterns.
"""
"""wrapper for pytorch_kinematics FK and IK operations."""

def __init__(
self,
@@ -77,7 +77,7 @@ class RobotKinematics:
end_effector_link: str,
device: Union[str, torch.device] = "cpu",
):
"""Initializes the kinematic chain from a URDF.
"""Initialize the kinematic chain from a URDF.

Args:
urdf_path (str): Path to the URDF file.
@@ -89,7 +89,7 @@ class RobotKinematics:
try:
# Load kinematic chain (core pytorch_kinematics object)
self.chain = pk.build_serial_chain_from_urdf(
open(urdf_path, mode="rb").read(), end_effector_link
open(urdf_path, mode="rb").read(), end_effector_link,
).to(device=self.device)
except Exception as e:
raise RuntimeError(f"Failed to build chain from URDF: {urdf_path}") from e
@@ -97,14 +97,14 @@ class RobotKinematics:
self.num_joints = len(self.chain.get_joint_parameter_names(exclude_fixed=True))
# Default initial guess for IK if none provided
self._default_q = torch.zeros(
(1, self.num_joints), device=self.device, dtype=torch.float32
(1, self.num_joints), device=self.device, dtype=torch.float32,
)
# print(f"Initialized RobotKinematicsConcise: {self.num_joints} joints, EE='{end_effector_link}', device='{self.device}'") # Optional print

def _prepare_joint_tensor(
self, joints: Union[List[float], torch.Tensor], batch_dim_required: bool = True
self, joints: Union[List[float], torch.Tensor], batch_dim_required: bool = True,
) -> torch.Tensor:
"""Validates and formats joint angles input tensor."""
"""Validate and formats joint angles input tensor."""
if isinstance(joints, list):
j = torch.tensor(joints, dtype=torch.float32, device=self.device)
elif isinstance(joints, np.ndarray):
@@ -113,7 +113,7 @@ class RobotKinematics:
j = joints.to(device=self.device, dtype=torch.float32)
else:
raise TypeError(
"Joints must be a list or torch.Tensor and got: ", joints.type
"Joints must be a list or torch.Tensor and got: ", joints.type,
)

if j.ndim == 1:
@@ -124,7 +124,7 @@ class RobotKinematics:
elif j.ndim == 2:
if j.shape[1] != self.num_joints:
raise ValueError(
f"Expected {self.num_joints} joints (dim 1), got {j.shape[1]}"
f"Expected {self.num_joints} joints (dim 1), got {j.shape[1]}",
)
if batch_dim_required and j.shape[0] != 1:
# Most common IK solvers expect batch=1 for initial guess, FK can handle batches
@@ -135,9 +135,9 @@ class RobotKinematics:
return j

def compute_fk(
self, joint_angles: Union[List[float], torch.Tensor]
self, joint_angles: Union[List[float], torch.Tensor],
) -> pk.Transform3d:
"""Computes Forward Kinematics (FK).
"""Compute Forward Kinematics (FK).

Args:
joint_angles (Union[List[float], torch.Tensor]): Joint angles (radians).
@@ -150,11 +150,11 @@ class RobotKinematics:
# robot frame

angles_tensor = self._prepare_joint_tensor(
joint_angles, batch_dim_required=False
joint_angles, batch_dim_required=False,
) # FK handles batches naturally
# Direct call to pytorch_kinematics FK
goal_in_rob_frame_tf = self.chain.forward_kinematics(
angles_tensor, end_only=True
angles_tensor, end_only=True,
)
goal_tf = ROB_TF.compose(goal_in_rob_frame_tf)
return goal_tf
@@ -167,7 +167,7 @@ class RobotKinematics:
lr: float = 0.1,
error_tolerance: float = 1e-4,
) -> Tuple[torch.Tensor, bool]:
"""Computes Inverse Kinematics (IK) using PseudoInverseIK.
"""Compute Inverse Kinematics (IK) using PseudoInverseIK.

Args:
target_pose (pk.Transform3d): Target end-effector pose (batch size 1).
@@ -186,7 +186,7 @@ class RobotKinematics:
target_pose = target_pose.to(device=self.device)
if target_pose.get_matrix().shape[0] != 1:
raise ValueError(
"target_pose must have batch size 1 for this IK method."
"target_pose must have batch size 1 for this IK method.",
) # Common limitation

# Prepare initial guess (q_init)
@@ -196,7 +196,7 @@ class RobotKinematics:
)
if q_init.shape[0] != 1:
raise ValueError(
"initial_guess must result in batch size 1 for this IK setup."
"initial_guess must result in batch size 1 for this IK setup.",
) # Enforce batch=1 for guess

q_init = q_init.requires_grad_(True) # Need gradient for solver
@@ -217,7 +217,7 @@ class RobotKinematics:
solution_angles = ik_solver.solve(target_pose)
if solution_angles.err_rot > 1e-3 or solution_angles.err_pos > 1e-2:
print(
f"IK did not converge: pos_err={solution_angles.err_pos}, rot_err={solution_angles.err_rot} for target {target_pose}"
f"IK did not converge: pos_err={solution_angles.err_pos}, rot_err={solution_angles.err_rot} for target {target_pose}",
)
return None
return solution_angles.solutions.detach()
@@ -243,7 +243,7 @@ def main():
target = event["value"].to_numpy()
target = target.astype(np.float32)
target = pk.Transform3d(
pos=target[:3], rot=torch.tensor(target[3:7])
pos=target[:3], rot=torch.tensor(target[3:7]),
)
solution = robot.compute_ik(target, last_known_state)
metadata["encoding"] = "jointstate"
@@ -258,14 +258,14 @@ def main():
target = pk.Transform3d(
pos=target[:3],
rot=pk.transforms.euler_angles_to_matrix(
torch.tensor(target[3:6]), convention="XYZ"
torch.tensor(target[3:6]), convention="XYZ",
),
)
rob_target = ROB_TF.inverse().compose(target)
solution = robot.compute_ik(rob_target, last_known_state)
if solution is None:
print(
"No IK Solution for :", target, "skipping this frame."
"No IK Solution for :", target, "skipping this frame.",
)
continue

@@ -273,11 +273,11 @@ def main():
delta_angles = solution - last_known_state

valid = np.all(
(delta_angles >= -np.pi) & (delta_angles <= np.pi)
(delta_angles >= -np.pi) & (delta_angles <= np.pi),
)
if not valid:
print(
"IK solution is not valid, as the rotation are too wide. skipping."
"IK solution is not valid, as the rotation are too wide. skipping.",
)
continue
metadata["encoding"] = "jointstate"


Loading…
Cancel
Save