Vuer

Hand Tracking

The Hand component offers a way to stream the current pose of the hand to the server. To use this mixed reality (XR) feature, you need to setup vuer behind a SSL proxy. We usually do so with ngrok, which is a paid service, or we can do so with local tunnel, which is free.

Here is the what it looks like with the Vision Pro

rst
.. video:: ../../_static/19_hand_tracking.webm
    :alt: Hand Tracking Demo with Vuer and Vision Pro
    :autoplay:
    :nocontrols:
    :loop:
    :muted:
    :poster: ../_static/19_hand_tracking.png
    :preload: auto
    :width: 100%

Hand API

You can get the full pose of the hands by listening to the HAND_MOVE event. You can add flags left and right to specify which hand you want to track.

python
from vuer import Vuer, VuerSession
from vuer.schemas import Hands
from asyncio import sleep

app = Vuer()

@app.add_handler("HAND_MOVE")
async def handler(event, session):
    print(f"Movement Event: key-{event.key}", event.value)

@app.spawn(start=True)
async def main(session: VuerSession):
    # Important: You need to set the `stream` option to `True` to start
    # streaming the hand movement.

    session.upsert(
        Hands(
            stream=True,
            key="hands",
            # hideLeft=False,       # hides the hand, but still streams the data.
            # hideRight=False,      # hides the hand, but still streams the data.
            # disableLeft=False,    # disables the left data stream, also hides the hand.
            # disableRight=False,   # disables the right data stream, also hides the hand.
        ),
        to="bgChildren",
    )

    while True:
        await sleep(1)

The returned data looks like the following:

typescript
/**
 * Left and right pose are relative to the wrist transformations.
 */
export type HandsData = {
  left?: Float32Array;       // 25 * 16 values.
  right?: Float32Array;      // 25 * 16 values.
  leftState: HandState;
  rightState: HandState;
};

export type HandState = {
  pinch: boolean;
  squeeze: boolean;
  tap: boolean;

  pinchValue: number;
  squeezeValue: number;
  tapValue: number;
}

Matrix format

All 4x4 transform matrices used in WebGL are stored in 16-element Float32Arrays. The values are stored in the array in column-major order; that is, each column is written into the array top-down before moving to the next column to the right and writing it into the array. Therefore, for the array [a0, a1, a2, …, a13, a14, a15], the matrix looks like this:

                                  ⌈  a0 a4 a8 a12  ⌉
                                  |  a1 a5 a9 a13  |
                                  |  a2 a6 a10 a14 |
                                  ⌊  a3 a7 a11 a15 ⌋

For details, refer to the MDN documentation on XR Rigid Body Transformation

Hand Landmarks

We follow the XR Hand | Hand Joints conventions, and return the landmarks in a single array of 25 * 16 values in the following order:

Hand jointIndexHand joint (continue)Index
wrist0middle-finger-phalanx-distal13
thumb-metacarpal1middle-finger-tip14
thumb-phalanx-proximal2ring-finger-metacarpal15
thumb-phalanx-distal3ring-finger-phalanx-proximal16
thumb-tip4ring-finger-phalanx-intermediate17
index-finger-metacarpal5ring-finger-phalanx-distal18
index-finger-phalanx-proximal6ring-finger-tip19
index-finger-phalanx-intermediate7pinky-finger-metacarpal20
index-finger-phalanx-distal8pinky-finger-phalanx-proximal21
index-finger-tip9pinky-finger-phalanx-intermediate22
middle-finger-metacarpal10pinky-finger-phalanx-distal23
middle-finger-phalanx-proximal11pinky-finger-tip24
middle-finger-phalanx-intermediate12--