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_posesand 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
- Capture online: subscribe only to
emf_posesand raw IMU data - Save: store the raw data frames in any format
- 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
)| Parameter | Description |
|---|---|
sn | Device serial number, used to load the matching calibration model |
hand_side | Hand side, "left" or "right"; an invalid value raises ValueError |
urdf_path | Optional, 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 NoneCalibration lookup order
Hand products depend on a calibrated hand model. The pipeline looks up the URDF in this order:
urdf_path: if passed explicitly, that file is used directly- Calibration file: if
urdf_pathis not passed, the calibration forsnis looked up under~/.wuji/sdk/params/ - 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| Resource | Return type | Content |
|---|---|---|
pipeline.hand_joint_angles() | HandJointAngles | 21 DoF hand joint angles |
pipeline.tip_poses() | FingertipPoses | 5-finger fingertip poses |
pipeline.hand_skeleton() | HandSkeleton | 21 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 writerConstruct 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.