VisionProTeleop Documentation

repository·main·Indexed 21 days ago

https://github.com/improbable-ai/visionproteleop

An ecosystem for robotics research using Apple Vision Pro, enabling real-world and simulation teleoperation and egocentric dataset recording. It consists of a VisionOS Tracking Streamer app, an iOS Tracking Manager app, and the avp_stream Python library. The system supports streaming hand/head tracking data, video, audio, and MuJoCo simulations via WebRTC, including a Cloudflare Workers-based signaling server for remote connections. It provides MJCF robot descriptions for ALOHA 2, Franka Emika Panda, and Shadow Hand E3M5.

Tokens
21.5K
Snippets
60
Records
84
Agent score
74%

What's inside VisionProTeleop

  1. Overview of VisionProTeleop ecosystem

    main

    VisionProTeleop is an ecosystem for robotics research using Apple Vision Pro. It consists of three main components:

    1. Tracking Streamer (VisionOS app): Streams hand/head tracking data to a Python client, receives video/audio/simulation streams from the client, presents AR simulation scenes (MuJoCo/Isaac Lab), and records egocentric video using UVC cameras.
    2. avp_stream (Python library): The client-side library used to receive tracking data and stream video/audio/simulation back to the Vision Pro.
    3. Tracking Manager (iOS app): A companion app for managing cloud recordings, configuring VisionOS app settings, calibrating mounted cameras, and sharing datasets.

    Primary Workflows:

    • Real-World Teleoperation: Control physical robots via hand tracking while viewing robot camera feeds.
    • Simulation Teleoperation: Control simulated robots (MuJoCo/Isaac Lab) with hand tracking, viewing either 2D renderings or native AR scenes.
    • Egocentric Video Recording: Record first-person manipulation videos with synchronized tracking data.
  2. Overview of VisionProTeleop Examples

    main

    The examples/ directory contains various implementations demonstrating different features of VisionProTeleop. Use this table to find an example that matches your requirements:

    ExampleDescriptionHardware CameraUpdate MethodAudioStereo VideoMuJoCo
    01_visualize_hand_callback.pyHand tracking visualization with callback methodCallback
    02_visualize_hand_direct.pyHand tracking visualization with direct frame updatesDirect
    03_visualize_hand_with_audio_callback.pyHand tracking with beep sounds on pinch gesturesCallback
    04_stereo_depth_visualization.pyStereoscopic 3D hand tracking with depth perceptionCallback
    05_text_scroller_callback.pyAnimated text and graphics without camera inputCallback
    06_stream_from_camera.pyStream live camera feed to Vision ProCallback
    07_process_frames.pyCamera streaming with custom frame processingCallback
    08_stream_audio_file.pyHand tracking with looping audio file playbackCallback
    09_mujoco_streaming.pyMuJoCo simulation replay streaming to Vision ProN/A
    10_teleop_osc_franka.pyReal-time teleoperation of Franka Panda with OSCN/A

    Legend:

    • Hardware Camera: Requires a physical camera device (✅) or generates synthetic video (❌).
    • Update Method: Uses callback-based or direct frame update method (N/A for MuJoCo-only).
    • Audio: Includes audio streaming (✅) or not (❌).
    • Stereo Video: Demonstrates stereoscopic 3D video (✅) or standard video (❌).
    • MuJoCo: Streams MuJoCo simulation to Vision Pro (✅) or not (❌).
  3. License and Usage Terms for VisionProTeleop

    main

    VisionProTeleop is released under the MIT License. This is a permissive license that allows you to use, modify, distribute, sublicense, and use the software commercially, even within proprietary software.

    Requirement: You must include the original license and copyright notice in all copies or substantial portions of the software.

  4. Configure simulation positioning with relative_to

    main

    The relative_to parameter determines where the simulation's world frame is placed in your physical AR space.

    Supported formats:

    • 4-dim: [x, y, z, yaw°] — translation (meters) and rotation around the z-axis (in degrees).
    • 7-dim: [x, y, z, qw, qx, qy, qz] — translation (meters) and full quaternion orientation.

    By default, VisionOS detects the physical ground and places the origin there.

    # Place world frame 0.8m above ground, rotated 90° around z-axis
    s.configure_mujoco("robot.xml", model, data, relative_to=[0, 0, 0.8, 90])
  5. Process video frames with callbacks

    main

    You can inject custom logic into the video stream by registering a callback function using register_frame_callback. The callback function receives a frame as a numpy array (H, W, 3) in RGB format and must return the processed frame.

    Key patterns:

    • Overlays: Use OpenCV to draw text, bounding boxes, or filters.
    • Chaining: You can chain multiple processing steps by having one function call another or by applying multiple transformations sequentially within a single callback.
    • Synthetic/Simulation Frames: To stream frames from a simulator (like Isaac Gym) instead of a physical camera, register a callback that returns the rendered frame and call start_streaming with device=None and format=None.
    import cv2
    import numpy as np
    
    def add_overlay(frame):
        """Add information overlay to frame."""
        # frame is a numpy array (H, W, 3) in RGB format
        cv2.putText(frame, "Robot View", (10, 30),
                    cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2)
        return frame
    
    s.register_frame_callback(add_overlay)
    s.start_streaming(
        device="/dev/video0",
        format="v4l2",
        size="640x480",
        fps=30
    )
  6. Use the Direct Method with `update_frame()`

    main

    The direct method gives you explicit control over the video stream by pushing frames manually from your main loop. This is best when you need the video frame rate to be perfectly synchronized with your data processing or when you need precise, frame-by-frame control.

    Pros:

    • Explicit control: You decide exactly when frames are sent.
    • Synchronized timing: Frame rate matches your loop rate.
    • Easy debugging: All logic remains in the main thread.
    • Dynamic frame rate: You can change the rate on-the-fly.

    Cons:

    • Coupled timing: Video frame rate is tied to the main loop speed.
    • Main loop overhead: Frame generation happens in the main thread and can slow down other tasks.
    streamer.start_streaming(device=None, fps=60)
    
    while True:
        # Get data
        hand_data = streamer.get_latest()
        
        # Process data
        processed_data = process(hand_data)
        
        # Generate visualization synced with data processing
        frame = visualize(processed_data)
        
        # Send frame immediately after processing
        streamer.update_frame(frame)
        
        time.sleep(1/60.)  # Everything runs at 60Hz together
  7. Map the Hand Skeleton Structure

    main

    Each hand contains 25 tracked joints. The indices in the finger tracking arrays (e.g., data['right_fingers']) follow this order:

    1. Wrist (joint 0)
    2. Thumb (joints 1-4): Metacarpal → Proximal → Intermediate → Distal
    3. Index Finger (joints 5-8): Metacarpal → Proximal → Intermediate → Distal
    4. Middle Finger (joints 9-12): Metacarpal → Proximal → Intermediate → Distal
    5. Ring Finger (joints 13-16): Metacarpal → Proximal → Intermediate → Distal
    6. Pinky (joints 17-20): Metacarpal → Proximal → Intermediate → Distal
    7. Palm (joints 21-24): Additional palm tracking points
  8. Understand Coordinate Systems and Axis Conventions

    main

    VisionProTeleop uses a right-hand rule axis convention:

    • X-axis: Points to the right
    • Y-axis: Points upward
    • Z-axis: Points backward

    All poses are represented as 4x4 homogeneous transformation matrices:

    $$T = \begin{bmatrix} R_{11} & R_{12} & R_{13} & t_x \ R_{21} & R_{22} & R_{23} & t_y \ R_{31} & R_{32} & R_{33} & t_z \ 0 & 0 & 0 & 1 \end{bmatrix}$$

    Where the upper-left 3x3 block is the rotation matrix $R$ and the rightmost column is the translation vector $t$ (in meters).

  9. How the VisionProTeleop signaling process works

    main

    The signaling server acts as a relay for WebRTC handshake data to establish a peer-to-peer connection between VisionOS and a Python client.

    The lifecycle of a connection:

    1. Room Creation: VisionOS generates a unique room code and connects to the server using that code.
    2. Client Connection: The Python client connects to the server using the same room code.
    3. Signaling Relay: The server relays WebRTC SDP (Session Description Protocol) offers and answers between the two peers.
    4. Connection Established: Once the WebRTC connection is successfully established, the signaling server is no longer required for the data stream.
  10. Use the Callback Method with `register_frame_callback()`

    main

    The callback method allows you to decouple video streaming from your main application logic. You register a function that the video streaming system calls automatically at a specified FPS. This is ideal for applications where the control loop (e.g., robot control) needs to run at a different frequency than the video stream (e.g., 100Hz control vs 60Hz video).

    Pros:

    • Decoupled timing: Video FPS is independent of the main loop.
    • Multithreading: Frame generation occurs in a dedicated video thread.
    • Consistent frame rate: Not affected by main loop delays.

    Cons:

    • Harder to debug: Callback runs in a separate thread.
    • State management: Requires closures to maintain state.
    • Less explicit: Timing is managed by the system, not the user.
    # Robot control running at 100Hz
    streamer.register_frame_callback(visualizer_callback())
    streamer.start_streaming(device=None, fps=60)
    
    while True:
        # Control loop runs at 100Hz
        robot_state = get_robot_state()
        action = compute_action(robot_state)
        robot.execute(action)
        time.sleep(1/100.)  # Control rate: 100Hz
        
        # Video automatically streams at 60Hz in background
  11. Understand the Tracking Data Dictionary structure

    main

    The streamer.latest property returns a dictionary containing real-time tracking information. The dictionary is organized into several categories: Head Tracking, Wrist Tracking, Finger Tracking, Pinch Distance, and Wrist Roll. Note that most pose data is provided as 4x4 homogeneous transformation matrices in numpy.ndarray format.

    data = streamer.latest
    # Returns a dict with keys like 'head', 'right_wrist', 'right_fingers', etc.