Skip to main content
The contact detection and contact residual streams (TactileBinary and TactileResidual) depend on a trained contact model. Before first use, run wuji calib tactile to complete tactile calibration. The model loads automatically per SDK user and glove serial number.

Tactile Matrix Layout

Tactile data comes from a 24×31 piezoresistive sensor matrix, 744 positions in total, of which 526 are active taxels. TactileFrame, TactileBinary, and TactileResidual all use this layout, in 24×31 row-major order. Access row r, column c: data[r * 31 + c] (r is 0–23, c is 0–30). The figure below marks where each column index (left) and row index (right) sits on the physical glove, letting you map a matrix index (or a point’s order in the exported point cloud) to a specific location on the glove. Physical position of tactile matrix column indices (col, left) and row indices (row, right) on the glove

Layout Change

Starting with firmware v0.11.0, the tactile matrix changes from 24×32 (768 values) to 24×31 (744 values), removing invalid column 26 from the 24×32 layout. The remaining columns keep their relative order: columns 0–25 keep their indices, and columns 27–31 shift left to become columns 26–30. Tactile matrix layout change: 24×32 (firmware v0.10.1 and earlier) on the left with invalid column 26 highlighted, 24×31 (firmware v0.11.0 and later) on the right

TactileFrame

Calibrated tactile matrix data. Frame rate: 120 FPS. data is an array of 744 values in 24×31 row-major order. Access row r, column c: data[r * 31 + c]. Invalid positions are fixed at -1.0, which distinguishes them from an active taxel reading zero pressure (0.0).

TactileZones

Tactile data aggregated by finger region. Each region is a rectangle in row-major order. Positions inside the rectangle that belong to the region carry a pressure value, and the rest are fixed at -1.0. Active taxels across the six regions total 526, the same mask as TactileFrame. Array length is the rectangle’s cell count — index within it to stay in bounds.

PointField

Point cloud field descriptor.

PointCloud

Tactile point cloud data: each frame gives the 3D position and pressure of the 526 active taxels on the glove. Positions come from linear blend skinning over the hand skeleton, in the l_wrist or r_wrist frame. Beyond x / y / z, each point also carries pressure (normalized to 0.0–1.0), so applications can color the 3D view by force.
The spatial positions in glove.tactile_point_cloud() come from the EMF-derived hand pose, so its output rate follows the rate in effect on emf_poses. See Output Rate Tuning.
The byte layout is self-describing, in the style of ROS sensor_msgs/PointCloud2: data is a flat byte stream, point_stride is the byte count per point, and fields describes the per-point fields (name, byte offset, type). The current implementation has 4 fields and 16 bytes per point, so data is point_count × 16 bytes packed point by point. Adding a new per-point field only means appending one entry to fields — the PointCloud message definition itself stays unchanged.
Slice back to per-point values:
Unpack the point cloud by field layout:

TactileBinary

Binary contact detection inferred from tactile data. Frame rate: 120 FPS, published at the same rate as glove.tactile(). data is laid out in 24×31 row-major order, with the same shape and mask as TactileFrame (526 active points). Access row r, column c: data[r * 31 + c]. Existing tactile-grid visualizers work unchanged.
Contact detection relies on a contact model trained through Wuji CLI tactile calibration or the SDK calibration API glove.calibrate_tactile(), and loaded automatically from the current SDK user and glove serial number. When no model is loaded, the data stream is still published at 120 FPS, but every active taxel is 0.0 (no contact) and invalid taxels remain -1.0. 1.0 never appears.Run calibration again after upgrading from an earlier version: a model trained back then doesn’t move to the new model directory, the SDK no longer loads it, and the stream behaves as if you never calibrated.

Train a Contact Model

Train once, and the model is saved per SDK user and glove serial number (see tactile data paths) and loaded automatically when you subscribe—no path to configure. There are two training entries:
  • Wuji CLI: Run wuji calib tactile and follow the terminal prompts through four recorded motions. The CLI then trains, verifies, and installs the model. For usage, see Tactile Calibration.
  • SDK calibration API: await glove.calibrate_tactile() (or the blocking glove.calibrate_tactile_blocking()) records a few prompted hand motions, then trains and installs the model automatically. After each recording keep it, re-record it, or stop—pass an on_pose_prompt callback for interactive control, or omit it for a hands-off run. For complete usage, see the examples 7.tactile_calibration.py (Python) and 4_tactile_calibration.c (C—API details in Tactile Contact Calibration).

Move a Model to Another Computer

The recommended way is wuji user export and wuji user import. For a manual move, two requirements must be met:
  • Copy all three model files: contact.safetensors, contact.npz, and contact.json (see tactile data paths). contact.json is the model’s commit marker — without it the model doesn’t load.
  • Put them into the paths["dir"] directory that manager.tactile_model_paths(user_id, sn) returns.
The SDK hot-reloads when you switch SDK users or retrain the model.

TactileResidual

Per-taxel signed contact residual inferred from tactile data. Frame rate: 120 FPS, published at the same rate as glove.tactile(). data is laid out in 24×31 row-major order, with the same shape and mask as TactileFrame (526 active points). Access row r, column c: data[r * 31 + c]. The residual signal is not smoothed, normalized, or binarized — pick a threshold and post-process it yourself. It shares the same contact model as TactileBinary. Subscribing to both runs model inference only once.
Residual computation relies on a contact model trained through Wuji CLI tactile calibration or the SDK calibration API glove.calibrate_tactile(), and loaded automatically from the current SDK user and glove serial number. When no model is loaded, the data stream is still published at 120 FPS, but every active taxel stays close to the static zero point and cannot reflect true pressure changes.Run calibration again after upgrading from an earlier version: a model trained back then doesn’t move to the new model directory, the SDK no longer loads it, and the stream behaves as if you never calibrated.
tactile_residual and tactile_binary share one model directory, and it loads automatically from the current SDK user and glove serial number. For the training entries and how to move models across computers, see TactileBinary. The SDK hot-reloads when you switch SDK users or retrain the model.
The C API matches the Python API on this page and uses the wuji_glove_ prefix. For initialization conventions, memory management, error codes, and build setup, see the C API Reference.

Tactile Contact Sensitivity

wuji_glove_set_tactile_binary_sensitivity sets the tactile_binary sensitivity multiplier for the active SDK user. The SDK stores the value locally. It takes effect immediately and persists across reconnects. Pass a finite positive value. Values above 1.0 make contact detection more sensitive. wuji_glove_get_tactile_binary_sensitivity returns the multiplier currently in effect. The returned value is limited to 0.5–3.0. If no valid value is available, the getter returns 1.0.
Both dev and out_sensitivity must be non-NULL. If either pointer is NULL, the getter returns WUJI_STATUS_ERR_INVALID_ARG. When out_sensitivity is valid, the getter leaves *out_sensitivity unchanged on failure. Call wuji_last_error() for details.

Tactile Contact Calibration

wuji_glove_calibrate_tactile_blocking runs the guided tactile contact calibration in one call: you record a few prompted hand motions, then the SDK collects the data, validates it, and trains the model. Decide install before you call:
  • install = true: after training, the SDK runs load validation and installs the model. It’s stored per SDK user and glove serial number, and loads automatically when you subscribe to tactile_binary / tactile_residual. The optional sensitivity pair (has_sensitivity set to true plus sensitivity) is only accepted in this mode — otherwise the call fails.
  • install = false: the SDK trains only. Load validation and install are skipped, the artifacts stay in the run directory, and nothing loads automatically. Use this to evaluate a training run offline before deciding whether to install.
Make the call:
on_pose_prompt makes the flow interactive: it fires before each motion starts and after each recording ends with a WujiTactilePromptRequest, and returns 0 to proceed, 1 to retry, or 2 to stop — any other value stops the run. on_feedback reports collect and train progress, and returns 0 to continue or 1 to abort — any other value aborts. Leave both unset for a hands-off run. Event pointers passed to callbacks are only valid for the duration of the callback. timeout_s is cooperative: on timeout the calibration is cancelled, but the function first waits for in-flight collect or train work and any running callback to return, so no callback ever fires after the function returns. If on_pose_prompt blocks indefinitely (for example, waiting on console input), the timeout can’t force it to return. On success, read the results from summary. model_dir is populated in both outcomes — use installed to tell them apart:
  • installed = true: model_dir points to the installed model, which loads automatically when you subscribe.
  • installed = false: model_dir points to the model saved in this run, which is not loaded automatically.
verified_alive_taxels is only valid when has_verified_alive_taxels is true. Call wuji_tactile_calibration_summary_free to release the heap fields once you’re done. Retrieve failure details with wuji_last_error(). A library built without the model-export feature returns WUJI_STATUS_ERR_UNSUPPORTED. The Python counterpart is glove.calibrate_tactile_blocking() — the async glove.calibrate_tactile() is Python-only, with no async C entry point. To watch contact and residual live, see Tactile Contact Grid View and Tactile Residual Grid View.