Overview
WujiGlove.offline_pipeline() builds a solver pipeline that needs no physical device. Feed it EMF pose frames and it computes finger joint angles, fingertip poses, and a 21-joint skeleton. Feed it raw IMU frames and it runs orientation fusion.
Three uses: rerun algorithms over a finished recording, develop and debug on a machine with no hardware, and re-solve the same data against a different hand model to see what changes.
The most important measurement on this page: the offline pipeline and the online solver produce bit-identical output. Given the same EMF pose frames, offline joint angles differ from the device’s live output by exactly zero.
Matching example: examples/python/wuji_glove/3.offline_pipeline.py.
For the API and parameter semantics, see Offline Pipeline. This page covers only how to run it and what the measurements show.
The C SDK has no matching offline pipeline API. This page covers Python.
Prerequisites
Hardware
None. This is the only page in the series that needs no device.Software
Before You Start
To replay real data, read Recording Data first. Running the example’s synthetic frames needs no preparation.Data Flow
Channels the Pipeline Offers
The object returned byoffline_pipeline() exposes these methods:
Three read-only properties round it out:
sn, hand_side, urdf_source, and urdf_source_path.
Hand solving depends on the URDF. IMU fusion doesn’t. Swapping hand models changes only the first three methods.
Where the URDF Comes From
Thesn you pass at construction decides where the SDK looks for a hand model. For a serial number that was never calibrated:
urdf_source takes two values here. builtin_default means the built-in generic hand model, with path as None. A calibrated device points at ~/.wuji/sdk/models/<serial number>_hand.urdf.
To force a specific model, pass urdf_path:
hand_side accepts only "left" and "right". Anything else raises ValueError: hand_side must be 'left' or 'right'.
Consistency With Online Results
This is the section worth reading.Method
- Connect a real glove and use
TopicRecorderto captureemf_poses,hand_joint_angles, andhand_skeletontogether for 4 seconds - Align the three streams by
header.seqin the MCAP file, yielding 525 common frames - Rebuild the recorded
emf_posesintoEmfPoseArrayand feed them to the offline pipeline - Subtract the offline results from the recorded online results, element by element
Results
Across the first 200 frames:
Joint angles are exactly identical. The 7-nanometer difference in skeleton positions comes from MCAP’s JSON serialization round-tripping a float, not from the algorithm.
Offline replay therefore reproduces online solving without loss. Tuning algorithms on recorded data, comparing hand models, and reproducing production issues all give trustworthy results.
Replay Code
Quaternion takes (x, y, z, w) in that order. When the MCAP stores orientation as an object, read it by field name rather than unpacking positionally.
IMU Fusion
The fuser carries state and advances frame by frame. The example feeds a single frame, which shows nothing, so here is how it evolves over a continuous run.At Rest
Feedingangular_velocity=(0,0,0) and linear_acceleration=(0,0,9.8) for 399 frames:
Orientation locks to the identity quaternion with no drift. When gravity aligns with the ideal Z axis, the fuser produces no correction.
Rotating
Switching toangular_velocity=(0, 0, 1.0) rad/s with timestamps 5 ms apart, 200 frames (1.0 second):
One rad/s for one second should give 1 rad, or 57.30°, and the measurement reads 57.0°. That 0.3° gap exists because the first frame has no predecessor to compute a time delta against, so it doesn’t integrate. Only 199 frames integrate: 199/200 x 57.30° = 57.01°, matching the measurement.
Two consequences. Timestamps must increase for real, because the fuser derives its integration step entirely from them. And the first frame always returns the initial orientation, so never judge anything by it.
Runtime Handling
Integration Steps
Running It
urdf_source reads builtin_default.
The three frames raise curl from 0.00 to 0.70, simulating fingers closing. The thumb’s fifth angle rises from 0.629 to 1.087 accordingly, in the right direction. thumb_conf stays at 0.475 because confidence on synthetic data doesn’t vary with curl.
Adapting It
Compare Two Hand Models
Run the same data through two pipelines, one on the default model and one on a calibrated model, then compare joint angles frame by frame to quantify what calibration buys:Don’t Build a New Pipeline Per Frame
hand_joint_angles() returns a channel object on each call, and calling it repeatedly in a loop is fine. IMU fusion state, however, lives on the pipeline, so a new pipeline resets the fuser. Run a whole batch through one pipeline instance.
Use Synthetic Data for Regression Tests
It runs without hardware, which suits CI. The example’s hand-written three frames extend naturally into fixed-input, fixed-expected-output assertions.Related
- Recording Data: produce the MCAP files this page replays
- Hand Model Calibration: generate a URDF that matches your hand
- Setting a Custom Hand Model: switch models in an online session
- Common Data Types:
EmfPoseArray,ImuData,Pose, and related structures