Calibration
Wuji Glove uses EMF data to solve finger-joint angles, which first requires an IK calibration to generate a URDF model matched to the wearer's hand. The SDK ships a Python calibration API, runtime hand-profile switching, user-supplied URDF override, and per-user / per-device-serial-number isolation of calibration files.
When to Calibrate
- First time you use a glove
- A different wearer puts it on
- The application needs to switch between the Wuji Hand and Wuji Hand 2 models
The device serial number already encodes which side (left or right) the glove belongs to, so the left and right gloves are calibrated independently and never share URDFs. When SDK user isolation is enabled, each user keeps a separate calibration URDF. See SDK User Management for user management.
Calibration Flow
Calibration asks the wearer to follow a sequence of poses while the SDK collects EMF data and solves a matching URDF.
At the start of calibration, the SDK temporarily forces the EMF divider to 1 and restores the prior value when capture ends — including error and cancellation paths. Calling glove.emf_poses_rate_divider().set(N>1) beforehand won't stretch calibration runtime. See Output Rate Tuning.
Synchronous Blocking Call
Best for scripts and CLI tools:
from wuji_sdk import SdkManager, Handedness
manager = SdkManager.instance()
glove = manager.connect(handedness=Handedness.Left, device_name="glove")
result = glove.calibrate_blocking(timeout_s=900.0)
print("device_sn:", result["device_sn"])
print("active hand profile:", result["active_hand_profile"])
print("generated profiles:", result["generated_hand_profiles"])
print("URDFs:", result["calibrated_urdfs"])
print("poses collected:", result["poses_collected"])
print("sdk user:", result["sdk_user"]["display_name"])calibrate_blocking() does not return until calibration finishes or times out. Ctrl+C is deferred until the call returns.
Asynchronous Call
Use the async API in asyncio programs. It honors standard asyncio cancellation:
result = await glove.calibrate(timeout_s=900.0)Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
skip_constraints | bool | False | Skip stability and constraint checks (debug only) |
timeout_s | float | 900.0 | Overall timeout in seconds. Must be a finite positive number |
hand_profile | WujiHandProfile, str, or None | None | Profile to generate (see below) |
on_feedback | Callable or None | None | Real-time feedback callback |
Real-time Feedback
Pass an on_feedback callback to receive per-step status:
def on_feedback(fb):
if fb.get("state") == "collecting":
progress = fb.get("progress", 0.0)
step = fb.get("step_index", 0)
total = fb.get("step_total", 0)
print(f"step {step}/{total}: {progress*100:.0f}%")
for hint in fb.get("hints", []):
print(f"hint: {hint}")
result = glove.calibrate_blocking(on_feedback=on_feedback)fb carries the current pose index, state, progress, frame count, pose-deviation diagnostic metrics (metrics), and human-readable hints (hints).
Exceptions raised inside the callback are logged by the SDK and do not interrupt calibration.
Hand Profile Selection
Wuji Glove ships two real-time IK models:
| Hand | String Value | Enum |
|---|---|---|
| Wuji Hand | "wujihand" | WujiHandProfile.WUJI_HAND |
| Wuji Hand 2 | "wujihand2" | WujiHandProfile.WUJI_HAND_2 |
Pin a Hand Profile at Calibration Time
from wuji_sdk import WujiHandProfile
result = glove.calibrate_blocking(
hand_profile=WujiHandProfile.WUJI_HAND_2,
)
# result["generated_hand_profiles"] == ["wujihand2"]
# result["calibrated_urdfs"] == {"wujihand2": "/path/to/...urdf"}When you omit hand_profile, the SDK uses the same captured data to generate URDFs for both profiles and writes them to:
calibration.hand_model_paths.wujihandcalibration.hand_model_paths.wujihand2
Real-time IK defaults to "wujihand" and can switch at runtime.
Switch Hand Profile at Runtime
Set calibration.hand_profile to swap the real-time IK model:
# Switch to Wuji Hand 2
glove.set("calibration.hand_profile", "wujihand2")
# Switch back to Wuji Hand
glove.set("calibration.hand_profile", "wujihand")The switch takes effect on the next frame. When calibration.hand_profile is unset, the SDK defaults to "wujihand".
Custom URDF Override
calibration.hand_model_path lets you point real-time IK at a user-supplied URDF. It has the highest priority:
glove.set("calibration.hand_model_path", "/path/to/custom_hand.urdf")Clear the value to fall back to the calibration-generated URDF for the current calibration.hand_profile.
URDF Lookup Order
Real-time IK, tf_static, and offline_pipeline resolve the hand URDF in this order:
- User-supplied path:
calibration.hand_model_pathpointing to a file outside the SDK-managed directory - Calibrated URDF for the active profile:
calibration.hand_model_paths.<profile>(wujihandorwujihand2) - Legacy compatibility path:
calibration.hand_model_paths.hand_1/hand_2orcalibration.hand_model_pathwritten by older SDK versions - Built-in default URDF: shipped with the SDK, selected by hand side
If the target profile's calibrated URDF is missing, the SDK silently falls back to the built-in default and logs a warning. Hand tracking keeps running but accuracy drops. In production, check the warning log to confirm IK is using the expected URDF.
Return Fields
Summary returned by calibrate() / calibrate_blocking():
| Field | Type | Description |
|---|---|---|
device_sn | str | Device serial number (already encodes the hand side) |
hand_profile | str or None | The profile passed in, or None if omitted |
active_hand_profile | str or None | Profile real-time IK will use after this calibration |
generated_hand_profiles | list[str] | All profiles actually generated this run (["wujihand"] or ["wujihand","wujihand2"]) |
calibrated_urdfs | dict[str, str] | Map of profile to URDF file path |
calibrated_urdf | str or None | The single path when only one profile is generated, None otherwise |
poses_collected | int | Number of poses captured |
frames_per_pose | dict[str, int] | Pose name to frame count |
sdk_user | dict | SDK user info captured at calibration start (user_id / display_name / description / is_default) |
Calibration Artifacts
Generated URDF files are named <sn>_hand_<profile>_<calibration_id>.urdf. The root directory is scoped by SDK user:
| SDK User | URDF Directory | Parameter File |
|---|---|---|
| Default user | ~/.wuji/sdk/models/ | ~/.wuji/sdk/params/<sn>.toml |
| Named user | ~/.wuji/sdk/users/<user_id>/models/ | ~/.wuji/sdk/users/<user_id>/params/<sn>.toml |
Uniqueness is guaranteed by the (user_id, device_sn, hand_profile) tuple. Switching the SDK user reloads the matching URDF for connected devices automatically, no reconnect required.
Error Handling
| Scenario | Behavior |
|---|---|
hand_profile is a string or enum other than "wujihand" / "wujihand2" | Raises ValueError / WujiException immediately. Calibration never starts |
timeout_s is not a finite positive number | Raises ValueError immediately |
| Calibration times out | Raises TimeoutError. The current attempt is not persisted |
| SDK user switches mid-calibration | Raises WujiException (message contains SDK user changed during calibration). The current attempt is dropped and the previous URDF is preserved |
| URDF write fails | Raises WujiException. The current run's new files are rolled back automatically, and the previous URDF and hand_model_paths parameters are left untouched |
| Calibration solver fails to converge | Raises WujiException. The message names the failing hand_profile, and the previous URDF is preserved |
Every failure path preserves the last successful calibration, so a failed attempt cannot corrupt existing URDFs. Just retry.
Resource Reference
| Resource Path | Access | Description |
|---|---|---|
calibration.hand_profile | GET / SET | Current real-time IK hand profile ("wujihand" / "wujihand2") |
calibration.hand_model_paths.wujihand | GET / SET | Wuji Hand calibrated URDF path, written by the SDK |
calibration.hand_model_paths.wujihand2 | GET / SET | Wuji Hand 2 calibrated URDF path, written by the SDK |
calibration.hand_model_path | GET / SET | User-supplied URDF override (highest priority) |