Skip to content

Commit 767aa70

Browse files
Merge pull request #42 from utiasDSL/feat-tf-pose-retrieval
Use tf to get the tool pose instead of current_pose topic
2 parents f9975ea + 28ca8d9 commit 767aa70

File tree

6 files changed

+150
-19
lines changed

6 files changed

+150
-19
lines changed

crisp_py/robot.py

Lines changed: 44 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from crisp_py.robot_config import FrankaConfig, RobotConfig
2121
from crisp_py.utils.callback_monitor import CallbackMonitor
2222
from crisp_py.utils.geometry import Pose, Twist
23+
from crisp_py.utils.tf_pose import TfPose
2324

2425

2526
class Robot:
@@ -60,7 +61,10 @@ def __init__(
6061
if node is None:
6162
if not rclpy.ok():
6263
rclpy.init()
63-
self.node = rclpy.create_node(name, namespace=namespace)
64+
self.node = rclpy.create_node(
65+
name,
66+
namespace=namespace,
67+
)
6468
else:
6569
self.node = node
6670
self.config = robot_config if robot_config else FrankaConfig()
@@ -82,6 +86,7 @@ def __init__(
8286
self._target_joint = None
8387
self._target_wrench = None
8488
self._current_twist = None
89+
self._tf_pose = None
8590

8691
self._callback_monitor = CallbackMonitor(
8792
node=self.node,
@@ -97,15 +102,23 @@ def __init__(
97102
self._target_joint_publisher = self.node.create_publisher(
98103
JointState, self.config.target_joint_topic, qos_profile_system_default
99104
)
100-
self.node.create_subscription(
101-
PoseStamped,
102-
self.config.current_pose_topic,
103-
self._callback_monitor.monitor(
104-
f"{namespace.capitalize()} Current Pose", self._callback_current_pose
105-
),
106-
qos_profile_sensor_data,
107-
callback_group=ReentrantCallbackGroup(),
108-
)
105+
if self.config.use_tf_pose:
106+
self._tf_pose = TfPose(
107+
self.node,
108+
self.config.target_frame,
109+
self.config.base_frame,
110+
self.config.tf_retrieve_rate,
111+
)
112+
else:
113+
self.node.create_subscription(
114+
PoseStamped,
115+
self.config.current_pose_topic,
116+
self._callback_monitor.monitor(
117+
f"{namespace.capitalize()} Current Pose", self._callback_current_pose
118+
),
119+
qos_profile_sensor_data,
120+
callback_group=ReentrantCallbackGroup(),
121+
)
109122
self.node.create_subscription(
110123
JointState,
111124
self.config.current_joint_topic,
@@ -126,13 +139,20 @@ def __init__(
126139
callback_group=ReentrantCallbackGroup(),
127140
)
128141

129-
self.node.create_timer(
130-
1.0 / self.config.publish_frequency,
131-
self._callback_monitor.monitor(
132-
f"{namespace.capitalize()} Target Pose", self._callback_publish_target_pose
133-
),
134-
ReentrantCallbackGroup(),
135-
)
142+
if not self.config.use_tf_pose:
143+
self.node.create_timer(
144+
1.0 / self.config.publish_frequency,
145+
self._callback_monitor.monitor(
146+
f"{namespace.capitalize()} Target Pose", self._callback_publish_target_pose
147+
),
148+
ReentrantCallbackGroup(),
149+
)
150+
else:
151+
self.node.create_timer(
152+
1.0 / self.config.publish_frequency,
153+
self._callback_update_tf_pose,
154+
ReentrantCallbackGroup(),
155+
)
136156
self.node.create_timer(
137157
1.0 / self.config.publish_frequency,
138158
self._callback_monitor.monitor(
@@ -234,6 +254,13 @@ def end_effector_twist(self) -> Twist:
234254
)
235255
return self._current_twist.copy()
236256

257+
def _callback_update_tf_pose(self):
258+
"""Update the current pose from TF if TF pose is enabled."""
259+
if self._tf_pose is not None and self._tf_pose.current_pose is not None:
260+
self._current_pose = self._tf_pose.pose
261+
if self._target_pose is None:
262+
self._target_pose = self._current_pose.copy()
263+
237264
def is_ready(self) -> bool:
238265
"""Check if the robot is ready for operation.
239266

crisp_py/robot_config.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ class RobotConfig:
4949
max_pose_delay: float = 1.0
5050
max_joint_delay: float = 1.0
5151

52+
use_tf_pose: bool = False
53+
tf_retrieve_rate: float = 50.0
54+
5255
def num_joints(self) -> int:
5356
"""Returns the number of joints in the robot."""
5457
return len(self.joint_names)

crisp_py/utils/geometry.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from builtin_interfaces.msg import Time as TimeMsg
77
from geometry_msgs.msg import PoseStamped, TwistStamped
88
from scipy.spatial.transform import Rotation
9+
from tf2_ros import TransformStamped
910

1011

1112
@dataclass
@@ -51,6 +52,22 @@ def from_ros_msg(cls, msg: PoseStamped) -> "Pose":
5152
)
5253
return cls(position, orientation)
5354

55+
@classmethod
56+
def from_transform_msg(cls, msg: TransformStamped) -> "Pose":
57+
"""Create Pose from ROS TransformStamped message."""
58+
position = np.array(
59+
[msg.transform.translation.x, msg.transform.translation.y, msg.transform.translation.z]
60+
)
61+
orientation = Rotation.from_quat(
62+
[
63+
msg.transform.rotation.x,
64+
msg.transform.rotation.y,
65+
msg.transform.rotation.z,
66+
msg.transform.rotation.w,
67+
]
68+
)
69+
return cls(position, orientation)
70+
5471
def to_ros_msg(self, frame_id: str, stamp: TimeMsg) -> PoseStamped:
5572
"""Convert Pose to ROS PoseStamped message."""
5673
msg = PoseStamped()

crisp_py/utils/tf_pose.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
"""Util class to get a pose from the tf2 instead of using a dedicated topic."""
2+
3+
from rclpy.node import Node
4+
from rclpy.time import Time
5+
from tf2_ros.buffer import Buffer
6+
from tf2_ros.transform_listener import TransformListener
7+
8+
from crisp_py.utils.geometry import Pose
9+
10+
11+
class TfPose:
12+
"""Class to get a pose from the tf2 instead of using a dedicated topic."""
13+
14+
def __init__(
15+
self, node: Node, target_frame: str, source_frame: str, retrieve_rate: float = 50.0
16+
):
17+
"""Initialize the TfPose class.
18+
19+
Args:
20+
node (Node): The ROS2 node.
21+
target_frame (str): The target frame to get the pose of.
22+
source_frame (str): The source frame to get the pose relative to.
23+
retrieve_rate (float, optional): The rate at which to retrieve the pose. Defaults to 50.0.
24+
"""
25+
self.node = node
26+
self.target_frame = target_frame
27+
self.source_frame = source_frame
28+
self.retrieve_rate = retrieve_rate
29+
30+
self.tf_buffer = Buffer()
31+
self.tf_listener = TransformListener(self.tf_buffer, self.node)
32+
33+
self.timer = self.node.create_timer(1.0 / self.retrieve_rate, self._callback_retrieve_pose)
34+
35+
self.current_pose: Pose | None = None
36+
37+
@property
38+
def pose(self) -> Pose:
39+
"""Get the current pose."""
40+
if self.current_pose is None:
41+
raise ValueError("Pose has not been retrieved yet.")
42+
return self.current_pose
43+
44+
def _callback_retrieve_pose(self):
45+
"""Retrieve the latest transform between source_frame and target_frame."""
46+
try:
47+
transform = self.tf_buffer.lookup_transform(
48+
self.source_frame, self.target_frame, Time()
49+
)
50+
except Exception as e:
51+
self.node.get_logger().warn(f"Could not get transform: {e}", throttle_duration_sec=5.0)
52+
return
53+
self.current_pose = Pose.from_transform_msg(transform)

examples/17_tf_pose.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
from crisp_py.robot import Robot
2+
from crisp_py.robot_config import RobotConfig
3+
import time
4+
5+
6+
config = RobotConfig(
7+
joint_names=[
8+
"panda_joint1",
9+
"panda_joint2",
10+
"panda_joint3",
11+
"panda_joint4",
12+
"panda_joint5",
13+
"panda_joint6",
14+
"panda_joint7",
15+
],
16+
home_config=[0, -1.57, 1.57, 0, 1.57, 0, 0],
17+
target_frame="panda_hand_tcp",
18+
base_frame="panda_link0",
19+
use_tf_pose=True,
20+
tf_retrieve_rate=50.0,
21+
)
22+
23+
robot = Robot(robot_config=config)
24+
25+
robot.wait_until_ready()
26+
27+
28+
print(f"Current pose from TF: {robot.end_effector_pose}")
29+
print(f"Current pose after move: {robot.end_effector_pose}")
30+
31+

pixi.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)