-
Notifications
You must be signed in to change notification settings - Fork 5
Tutorial: Teleoperation with OpenXR Hand Tracking #145
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
|
||
| Please ensure that your machine meets the [recommended specifications](#mixed-reality-device) for mixed reality teleoperation. | ||
|
|
||
| - **Why is Room Camera output not published over DDS?** |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @KumoLiu , @mingxin-zheng , could you please help take a look at this DDS issue?
Thanks.
Signed-off-by: YunLiu <[email protected]>
Signed-off-by: YunLiu <[email protected]>
Signed-off-by: YunLiu <[email protected]>
Signed-off-by: YunLiu <[email protected]>
Signed-off-by: YunLiu <[email protected]>
Signed-off-by: YunLiu <[email protected]>
Signed-off-by: YunLiu <[email protected]>
Signed-off-by: YunLiu <[email protected]>
Signed-off-by: YunLiu <[email protected]>
Signed-off-by: YunLiu <[email protected]>
Signed-off-by: YunLiu <[email protected]>
Signed-off-by: YunLiu <[email protected]>
Signed-off-by: YunLiu <[email protected]>
Signed-off-by: YunLiu <[email protected]>
Signed-off-by: YunLiu <[email protected]>
Signed-off-by: YunLiu <[email protected]>
Adds support for Isaac Lab's OpenXR-based `Se3HandTracking` in the Robotic Ultrasound teleoperation workflow sample. Changes: - Update sample with hand tracking default properties based on Isaac Lab v2.0.2 sample: https://github.com/isaac-sim/IsaacLab/blob/b5fa0eb031a2413c182eeb54fa3a9295e8fd867c/scripts/environments/teleoperation/teleop_se3_agent.py - Disable room camera polling to work around observed application crash when running XR teleop in the scene Tested on local network with Apple Vision Pro.
Adds tutorial for using Isaac Lab + CloudXR OpenXR components to run the `robotic_ultrasound` teleoperation sample with XR hand tracking inputs. Instructions are ported from the Isaac Lab XR teleoperation tutorial with specific focus on Isaac for Healthcare specs and integration. https://isaac-sim.github.io/IsaacLab/main/source/how-to/cloudxr_teleoperation.html#setting-up-cloudxr-teleoperation Includes discussion on observed limitations and troubleshooting based on local testing experience. Tested locally with Apple Vision Pro (headset and sim).
Changes: - Update to leverage Isaac Lab 2.1.0 API - Document known limitations and workarounds
20462a4 to
20ba3e0
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for the update! I've tried the latest code and overall it looks good and works.
There are some remain issues related to rotation and the field of view as displayed on the screen. I've left a few comments inline for your reference. Additionally, after starting the AR feature, the initial pose appears awkward. Do you know how we can adjust it to better display all the assets within the scene?
I haven't had the chance to review the DDS-related aspects yet, but I plan to look into them later.
| ) | ||
| elif args_cli.teleop_device.lower() == "handtracking": | ||
| retargeter_device = Se3RelRetargeter( | ||
| bound_hand=OpenXRDevice.TrackingTarget.HAND_RIGHT, zero_out_xy_rotation=True |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Setting zero_out_xy_rotation=True may prevent the probe from correctly orienting downward if it is not initially pointing in that direction. To address this, I developed a custom Retargeter that primarily utilizes the 6DOF from the palm. This adjustment resulted in significantly improved teleoperation of the probe within these degrees of freedom. Additionally, I observed that the Se3RelRetargeter offers options such as use_wrist_position and use_wrist_rotation. Do you think we might be able to apply this approach here? If you could test these settings—use_wrist_position=True, use_wrist_rotation=True, zero_out_xy_rotation=False—and see how they perform, I’d appreciate it. Otherwise, I'm also available to share the customized retargeter for your evaluation.
I am also happy to help verify when I back office.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @KumoLiu , good point. I've updated the teleop sample to use wrist translation and rotation, in my experience I see only a minor difference in ease of control. However I've also reduced the rotation scale factor from the default value of 10.0 and the probe is now easier to control in 6DOF.
Please try out the latest update, if you are seeing significant improvement with your custom retargeter then please feel free to contribute the custom retargeter, thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, it works better now.
Let's use wrist pos and rot for now.
Also put my customized regarter here if you are interested in, The main difference is that in this regarter, I'm trying to use hand_data.get("palm") pos an rot.
class PalmRelRetargeter(RetargeterBase):
"""Retargets OpenXR palm tracking data to end-effector commands using relative positioning."""
def __init__(
self,
bound_hand: OpenXRDevice.TrackingTarget,
zero_out_xy_rotation: bool = False,
delta_pos_scale_factor: float = 10.0,
delta_rot_scale_factor: float = 10.0,
alpha_pos: float = 0.5,
alpha_rot: float = 0.5,
):
"""Initialize the palm relative motion retargeter.
Args:
bound_hand: The hand to track (OpenXRDevice.TrackingTarget.HAND_LEFT or OpenXRDevice.TrackingTarget.HAND_RIGHT)
zero_out_xy_rotation: If True, ignore rotations around x and y axes, allowing only z-axis rotation.
delta_pos_scale_factor: Amplification factor for position changes.
delta_rot_scale_factor: Amplification factor for rotation changes.
alpha_pos: Position smoothing parameter (0-1); higher values track input closely, lower values smooth more.
alpha_rot: Rotation smoothing parameter (0-1); higher values track input closely, lower values smooth more.
"""
if bound_hand not in [OpenXRDevice.TrackingTarget.HAND_LEFT, OpenXRDevice.TrackingTarget.HAND_RIGHT]:
raise ValueError(
"bound_hand must be either OpenXRDevice.TrackingTarget.HAND_LEFT or"
" OpenXRDevice.TrackingTarget.HAND_RIGHT"
)
self.bound_hand = bound_hand
self._zero_out_xy_rotation = zero_out_xy_rotation
self._delta_pos_scale_factor = delta_pos_scale_factor
self._delta_rot_scale_factor = delta_rot_scale_factor
self._alpha_pos = alpha_pos
self._alpha_rot = alpha_rot
self._smoothed_delta_pos = np.zeros(3)
self._smoothed_delta_rot_vec = np.zeros(3)
self._position_threshold = 0.002 # Meters
self._rotation_threshold = 0.02 # Radians
self._previous_palm_data: np.ndarray | None = None # Stores [px, py, pz, qw, qx, qy, qz]
def retarget(self, data: dict) -> np.ndarray:
"""Convert palm pose to robot end-effector command.
Args:
data: Dictionary mapping tracking targets to joint data.
The joint data for "wrist" is expected as [pos_x, pos_y, pos_z, quat_w, quat_x, quat_y, quat_z].
Returns:
np.ndarray: 6D array containing position delta (xyz) and rotation delta vector (rx,ry,rz)
for the robot end-effector.
"""
hand_data = data.get(self.bound_hand)
if hand_data is None:
return np.zeros(6) # No data for the bound hand
current_palm_data = hand_data.get("palm")
if current_palm_data is None:
# Wrist data not available, return zero command
return np.zeros(6)
if self._previous_palm_data is None:
# First frame with valid wrist data, store and return zero command
self._previous_palm_data = current_palm_data.copy()
return np.zeros(6)
# Extract current pose
current_pos = current_palm_data[:3]
# Scipy expects quaternion as [x, y, z, w]
current_rot_scipy = Rotation.from_quat([current_palm_data[4], current_palm_data[5], current_palm_data[6], current_palm_data[3]])
# Extract previous pose
previous_pos = self._previous_palm_data[:3]
previous_rot_scipy = Rotation.from_quat([self._previous_palm_data[4], self._previous_palm_data[5], self._previous_palm_data[6], self._previous_palm_data[3]])
# Calculate delta pose
delta_pos = current_pos - previous_pos
delta_rot_obj_scipy = current_rot_scipy * previous_rot_scipy.inv()
delta_rot_vec = delta_rot_obj_scipy.as_rotvec()
# Store current pose for next iteration
self._previous_palm_data = current_palm_data.copy()
# Process deltas for command generation
command_pos = delta_pos
command_rot_vec = delta_rot_vec
if self._zero_out_xy_rotation:
command_rot_vec[0] = 0.0 # Zero out x-axis rotation
command_rot_vec[1] = 0.0 # Zero out y-axis rotation
# Smooth position and rotation
self._smoothed_delta_pos = self._alpha_pos * command_pos + (1 - self._alpha_pos) * self._smoothed_delta_pos
self._smoothed_delta_rot_vec = self._alpha_rot * command_rot_vec + (1 - self._alpha_rot) * self._smoothed_delta_rot_vec
# Apply thresholds to ignore small movements
if np.linalg.norm(self._smoothed_delta_pos) < self._position_threshold:
self._smoothed_delta_pos = np.zeros(3)
if np.linalg.norm(self._smoothed_delta_rot_vec) < self._rotation_threshold:
self._smoothed_delta_rot_vec = np.zeros(3)
# Scale to get final command
final_pos_command = self._smoothed_delta_pos * self._delta_pos_scale_factor
final_rot_command_vec = self._smoothed_delta_rot_vec * self._delta_rot_scale_factor
return np.concatenate([final_pos_command, final_rot_command_vec])
| XR_INITIAL_POS = [0.2, 0, -0.2] | ||
| XR_INITIAL_ROT = (0.7, 0.0, 0, -0.7) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you clarify how we obtain the initial position and rotation? Does this pertain to the initial position of the headset? I observed that what I see on the Vision Pro does not accurately reflect on IsaacLab. Is this discrepancy expected, or do you have any suggestions on how we can adjust the settings so that what we view through the headset is accurately displayed on the screen?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These settings were experimentally chosen based on observation so that the initial headset position is adjacent to the sample workbench. We can adjust them to maximize user comfort. To my knowledge we do not have control over the screen display viewport during AR mode.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To my knowledge we do not have control over the screen display viewport during AR mode.
Got it, thanks for the explaination.
| delta_pos_4d = torch.matmul( | ||
| delta_pos_4d, | ||
| torch.tensor( | ||
| [[0, 0, 1, 0], [0, 1, 0, 0], [-1, 0, 0, 0], [0, 0, 0, 1]], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why we need to matmul this transform matrix here? Does it related to the initial state of the probe?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Observed discrepancy between the hand tracking reference frame and the probe frame. Without the transformation applied, translation in one hand tracking relative axis moves the probe in a different, unexpected direction along its relative axis.
In the latest update I've included an additional mapping workaround so that wrist rotation deltas are also mapped to the expected probe direction in the probe's relative coordinate frame.
These are experimental workarounds based on observation. I am not certain at the moment why the hand tracking and probe coordinate frames appear to differ.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the update, I update the transform matrix in this PR: #161
Let me know if it make more sense to you. Thanks!
…utorial Signed-off-by: YunLiu <[email protected]> Signed-off-by: YunLiu <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the PR.
Need to merge for rc1 tagging.
If there are any topic unresolved, feel free to open another PR to address.
|
|
||
| - **Spawning cameras fails at app startup** | ||
|
|
||
| The error below may appear if cameras have not been enabled in your app configurations: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Addresses rows (32) and (33) in I4H v0.2 Execution Plan
Description
Integrates the CloudXR and Isaac teams' OpenXR hand tracking support in Isaac for Healthcare.
Se3HandTrackingin the Robotic Ultrasound teleoperation workflow sample.robotic_ultrasoundteleoperation sample with XR hand tracking inputs.Notes:
v2.0.2feature availability and API.mainfor PR submission.v2.1.0API are missing from therobotic_ultrasoundpinned Isaac Lab versionv2.0.2(retargeting), limiting ease of use