Offline Pipeline

The Wuji SDK offline pipeline lets you save only lightweight raw data during capture, then recompute the full post-processed products afterward — fingertip poses, joint angles, hand skeleton, and fused IMU orientation. Offline results are identical to real-time computation.

When to use it

Real-time inverse kinematics solving is CPU-intensive. If you only want to record data during capture and analyze it later, running the solver the whole time is wasteful. The offline pipeline fits when you want to:

  • Subscribe only to lightweight emf_poses and raw IMU during capture, lowering CPU usage
  • Decide later which post-processed products you need
  • Save raw data in any format of your choice, without binding to a specific file format

Workflow

  1. Capture online: subscribe only to emf_poses and raw IMU data
  2. Save: store the raw data frames in any format
  3. Recompute offline: use WujiGlove.offline_pipeline() to recompute post-processed products from the saved frames

Offline recomputation produces the same result as real-time computation: the same input plus the same calibration yields the same output.

Create an offline pipeline

WujiGlove.offline_pipeline() creates a pipeline that does not require a connection to a physical device.

from wuji_sdk import WujiGlove

pipeline = WujiGlove.offline_pipeline(
    sn="WujiGlove-12345",   # required, loads the calibration for this device
    hand_side="right",       # required, "left" or "right"
    urdf_path=None,          # optional, specify a calibrated hand model file
)
ParameterDescription
snDevice serial number, used to load the matching calibration model
hand_sideHand side, "left" or "right"; an invalid value raises ValueError
urdf_pathOptional, an explicit path to a URDF hand model

After creation, you can query the calibration source actually in use:

print(pipeline.sn)               # "WujiGlove-12345"
print(pipeline.hand_side)        # "right"
print(pipeline.urdf_source)      # "override" | "calibration_file" | "builtin_default"
print(pipeline.urdf_source_path) # actual URDF path, or None

Calibration lookup order

Hand products depend on a calibrated hand model. The pipeline looks up the URDF in this order:

  1. urdf_path: if passed explicitly, that file is used directly
  2. Calibration file: if urdf_path is not passed, the calibration for sn is looked up under ~/.wuji/sdk/params/
  3. Built-in default model: if neither is found, the pipeline falls back to the built-in default URDF and prints a warning

When the pipeline falls back to the built-in default model (urdf_source is builtin_default), hand products may be inaccurate. Make sure you use the calibration model of the glove that captured the data. IMU fusion products do not depend on the URDF and are unaffected.

Recompute hand products

The hand chain takes emf_poses frames as input and recomputes three products. The three share a single solve, so the results match the online ones.

for emf_poses in my_recording_reader():   # custom reader, yields EmfPoseArray frame by frame
    angles   = pipeline.hand_joint_angles().compute(emf_poses)
    tips     = pipeline.tip_poses().compute(emf_poses)
    skeleton = pipeline.hand_skeleton().compute(emf_poses)
    save(angles, tips, skeleton)   # custom writer
ResourceReturn typeContent
pipeline.hand_joint_angles()HandJointAngles21 DoF hand joint angles
pipeline.tip_poses()FingertipPoses5-finger fingertip poses
pipeline.hand_skeleton()HandSkeleton21 MediaPipe keypoint skeleton

For the full field definitions of these products, see SDK Data Reference.

Recompute IMU fusion

The IMU chain takes raw IMU frames as input and recomputes fused orientation. Use imu_data_palm() for the glove's dorsum IMU.

for raw_frame in my_imu_reader():   # yields raw ImuData frame by frame
    fused = pipeline.imu_data_palm().compute(raw_frame)
    save(fused)   # custom writer

Construct raw IMU frames with ImuData.raw(); it automatically marks orientation_covariance[0] as -1 (not fused). IMU fusion products do not depend on a calibration URDF.

Construct input frames

Use the schema constructors to build input frames in memory:

from wuji_sdk import (
    EmfPose, EmfPoseArray, FrameHeader, ImuData,
    Pose, Quaternion, Vector3F64,
)

emf = EmfPoseArray(
    header=FrameHeader(seq=1, timestamp_us=0, frame_id="r_hand_emf_tx"),
    poses=[
        EmfPose(
            pose=Pose(position=[0.05, 0.0, 0.0],
                      orientation=Quaternion(0.0, 0.0, 0.0, 1.0)),
            confidence=1.0,
        )
        for _ in range(5)
    ],
)

raw_imu = ImuData.raw(
    header=FrameHeader(seq=1, timestamp_us=0, frame_id="imu_raw/palm"),
    angular_velocity=Vector3F64(0.0, 0.0, 0.0),
    linear_acceleration=Vector3F64(0.0, 0.0, 9.8),
)

For a complete runnable example, see examples/3.offline_pipeline.py in the SDK package.