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 parameter file 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 parameter file for that device |
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 NoneURDF Lookup Order
Hand products depend on a calibrated hand model. The pipeline picks the URDF like this:
- Explicit
urdf_path: when passed, that file is used directly and takes priority over every other source. An unreadable file raises an error instead of falling back - Managed model: without
urdf_path, the pipeline checks whethercalibration.hand_model_pathpoints to a file outside the SDK-managed directory. If it does, that custom file is used. If it doesn't, the stable per-side fileleft_hand.urdf/right_hand.urdffor the current SDK user is used. The two are alternatives, not fallbacks for each other - Built-in default model: when the file picked above is missing or unusable, the pipeline falls back to the built-in default URDF and prints a warning
The default SDK user skips the managed model and always uses the built-in default unless you pass urdf_path. To recompute hand products that match the online ones, switch to the named user that captured the data. See Calibration.
When the pipeline falls back to the built-in default model (urdf_source is builtin_default), hand products may be inaccurate. 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.