HumansXDocumentation Center 日本語Website ↗
Knowledge Base › Wuji Glove › Manual

Tactile Contact Grid View

SDK Practice

Overview

tactile_binary is one of the outputs of tactile calibration: a contact-or-not verdict per taxel. This example lays it out as a 24x31 terminal grid. The grid shows the contact location in real time.

It’s an observation tool, not another calibration workflow. Use it to verify calibration results, tune sensitivity, and track down a region that doesn’t respond.

Matching examples: examples/python/wuji_glove/8.tactile_contact_view.py and examples/c/wuji_glove/6_tactile_contact_view.c.

For API and field definitions, see Tactile.

The grid rendering, layout mapping, parameter behavior, and uncalibrated appearance on this page are all measured. The view under actual contact isn’t — producing contact requires someone wearing the glove and touching something.

Prerequisites

Hardware

ItemRequirement
Wuji Glove1, tactile calibration completed
ConnectionEthernet
TerminalAt least 31 columns wide and 26 rows tall

Software

ItemRequirement
Operating systemLinux or a Mac with Apple silicon, meeting the system requirements
Wuji SDKv2026.8.31 or later
Python3.10 or later
C toolchainCMake 3.16 or later, plus the extracted SDK package
  • Python

  • C

pip install wuji-sdk
LIB="libwuji_sdk_c.so"
if [ "$(uname -s)" = "Darwin" ]; then LIB="libwuji_sdk_c.dylib"; fi
cmake -S examples/c/wuji_glove -B build/glove \
  -DWUJI_SDK_INCLUDE_DIR="${SDK}/include" \
  -DWUJI_SDK_LIB="${SDK}/lib/${LIB}"
cmake --build build/glove --target 6_tactile_contact_view

Before You Start

Run tactile calibration first. On an uncalibrated glove, tactile_binary is exactly zero and the grid stays empty forever. See Tactile Calibration.

To tell whether a glove is calibrated, check whether the stream is all zeros:

frame = glove.tactile_binary().subscribe().recv()
print("calibrated" if any(v > 0 for v in frame.data) else "all zeros, likely uncalibrated")

The Grid Is the Array’s Physical Layout

This is the key to reading the whole view: 24 x 31 = 744, exactly the channel count of the raw tactile stream. The grid isn’t resampled. It’s those 744 channels laid out as the sensor arranges them physically.

The mapping is row-major:

index = row * 31 + col

tactile, tactile_binary, and tactile_residual all carry 744 values, with invalid channels in identical positions (218 of them). All three views therefore show the same outline.

Invalid Channels Trace the Hand’s Outline

Invalid channels per row:

row  0: 27    row  6: 9     row 12: 0     row 18: 9
row  1: 18    row  7: 9     row 13: 2     row 19: 9
row  2: 14    row  8: 9     row 14: 6     row 20: 9
row  3: 14    row  9: 6     row 15: 5     row 21: 11
row  4: 14    row 10: 2     row 16: 4     row 22: 12
row  5: 11    row 11: 0     row 17: 6     row 23: 12

Edge rows hold most of them while middle rows hold almost none. Rendered, that reads as a hand: five fingers branching at the top and a palm filling the middle.

No column is entirely invalid, because 744 already reflects the hardware-masked column being removed.

Legend

CharacterTestMeaning
#v > 0.5Contact
.-0.5 <= v <= 0.5Hand surface, no contact
Spacev < -0.5Invalid or masked taxel

-1.0 is the sentinel for an invalid channel, so the space test reads v < -0.5 rather than v == -1.0, avoiding a floating-point equality check.

Status Line

One line above the grid:

contacts=0   sensitivity=1.0 peak=0.000 row=0 col=14 len=744  (+/- sensitivity, q quit)
FieldMeaning
contactsTaxels with v > 0.5 in this frame
sensitivityCurrent sensitivity
peakHighest value in this frame
row / colGrid coordinates of the peak, from peak_idx // 31 and peak_idx % 31
lenFrame length, normally 744

Sensitivity Has Two Clamps, and Only One Is Real

This is the detail worth knowing on this page.

LayerRangeBehavior
Example’s clamp_sensitivity[0.5, 3.0]Clamps to the boundary
SDK parameter layerNo clampAny value writes through

Calling the SDK API directly:

set(1.5) -> reads back 1.5
set(3.0) -> reads back 3.0
set(5.0) -> reads back 5.0
set(0.1) -> reads back 0.1

Bypassing the example, glove.set("algorithms.tactile_binary.sensitivity", 5.0) writes and reads back successfully. The example’s [0.5, 3.0] is a recommended range, not a boundary the SDK enforces.

That’s why passing --sensitivity 20.0 shows 3.0 on the status line. The example clamps before it writes:

=== sensitivity=20.0 ===
contacts=0   sensitivity=3.0 peak=0.000 ...

This Parameter Writes to the Device

SENSITIVITY_PARAM = "algorithms.tactile_binary.sensitivity" goes out through glove.set() and changes the tactile_binary stream itself, not the local display. Every other subscriber sees the change at the same moment.

Reading it before it’s ever been set raises rather than returning a default:

WujiException: Parameter not set: algorithms.tactile_binary.sensitivity

The example deliberately wraps set_sensitivity in a try so a failed write doesn’t take the view down. An observation tool crashing mid-run is worse than a reading being off.

Render Throttling

The stream runs at 130 Hz and rendering is throttled to 10 Hz:

RENDER_INTERVAL_S = 0.1

if now - self._last_render < RENDER_INTERVAL_S:
    return

Callbacks fire per frame on an SDK worker thread, and without throttling, terminal output drags that thread down. This is the standard shape for rendering inside a callback: decide quickly whether to draw, and return immediately when you shouldn’t.

Integration Steps

Complete examples: Python tactile_binary view and C tactile_binary view.

Reuse imports, manager setup, C initialization, connection error handling, and callbacks from Python example and C example. Check the status of each C call. On failure, release existing resources and exit as shown in the examples.

1. Connect to the Target Glove

Connect to the target glove by serial number. Don’t depend on discovery order when several devices share the network.

  • Python

  • C

glove = manager.connect(sn=serial_number, device_name="my_glove")
WujiConnectTarget target = {
    .kind = WUJI_CONNECT_TARGET_KIND_SN, .value = serial_number,
};
struct WujiDevice *dev = NULL;
wuji_connect(&target, "glove_0", NULL, &dev);

2. Set Sensitivity and Subscribe

Binary-stream sensitivity is written to the device and affects all subscribers. If the write fails, keep the view running and report the error. Then register the callback.

  • Python

  • C

try:
    glove.set("algorithms.tactile_binary.sensitivity", sensitivity)
except WujiException as exc:
    print(exc, file=sys.stderr)
sub = glove.tactile_binary().subscribe_with_callback(on_binary)
if (wuji_glove_set_tactile_binary_sensitivity(dev, sensitivity) != WUJI_STATUS_OK)
    fprintf(stderr, "%s\n", wuji_last_error());
struct WujiSub *sub = NULL;
wuji_glove_subscribe_tactile_binary(dev, on_binary, NULL, &sub);

3. Run for a Fixed Duration and Clean Up

When the view exits, close the subscription before disconnecting. Don’t run blocking rendering directly on the callback thread.

  • Python

  • C

try:
    time.sleep(seconds)
finally:
    sub.close()
    manager.disconnect_all()
sleep(seconds);
wuji_sub_close(sub);
wuji_dev_disconnect(dev);
wuji_dev_release(dev);
wuji_shutdown();

Running It

  • Python

  • C

python examples/python/wuji_glove/8.tactile_contact_view.py --sn <serial number>
python examples/python/wuji_glove/8.tactile_contact_view.py --sn <serial number> \
  --seconds 60 --sensitivity 1.5
./build/glove/6_tactile_contact_view

Measured output, uncalibrated and unworn:

contacts=0   sensitivity=1.0 peak=0.000 row=0 col=14 len=744  (+/- sensitivity, q quit)
              ....
      ....   ......   ...
     ......  ......  .....
     ......  ......  .....
     ......  ......  .....
 ... ......  ......  .....
...........  ......  .....
...........  ......  .....
...........  ......  .....
...........  ......  ..... ...
...................  ..........
...............................
...............................
.... .................... .....
     .................... .....
.    .................... .....
.    ..........................
.    ..................... ...
.    .....................
.    .....................
.    .....................
.    ...................
.    ..................
.    ..................

Measured on a right-hand Wuji Glove resting on a desk, unworn and uncalibrated.

The outline reads clearly: five fingers branching at the top, a palm filling the middle, and the thumb running down the left. contacts=0 and peak=0.000 are what an uncalibrated glove looks like — the outline comes from the distribution of invalid channels and has nothing to do with calibration, so seeing a hand shape doesn’t mean the stream carries data.

Once calibrated and worn, contact regions turn into #, contacts grows with the contact area, peak rises, and row / col point at the hardest-pressed spot.

Notes

Branch pointBehaviorWhat to do
No tactile calibrationThe grid is all ., with contacts=0 and a constant peak=0.000Run tactile calibration first
Glove not wornSame as abovePut the glove on and touch something
Sensitivity outside [0.5, 3.0]The example clamps before writingFor values outside that range, call set() directly instead
Sensitivity write failsPrints one line to stderr and the view continuesCheck the device connection
Non-interactive terminalKeyboard control is unavailable, so it runs out --seconds or waits for Ctrl-CPreset a value with --sensitivity
--seconds 0Runs until q or Ctrl-CThat’s the default
Frame length isn’t 744Anything beyond is treated as invalidThe example guards with limit = min(len(data), 24*31)

In an interactive terminal, + and - adjust sensitivity and q quits.

Adapting It

Use It to Verify Calibration Quality

After calibrating, press each region in turn and watch whether the matching part of the grid lights up. A region that never lights no matter how you press wasn’t covered during calibration, so redo that motion. This localizes the problem far better than a single dead_pct number.

Tune Sensitivity to Find a Working Point

Adjust live with + and -. While holding a press, lower it gradually until false positives just stop, and that’s the working point for this glove. Record it in your application config.

For Your Own Threshold, Read the Residual

The threshold behind tactile_binary is computed by the SDK from sensitivity. For full control, use tactile_residual, which gives a continuous value and leaves the threshold to you. See Tactile Residual Grid View.

Map Grid Coordinates Back to the Point Cloud

The row / col on the status line converts back to a channel index with row * 31 + col, which then maps to a spatial coordinate in tactile_point_cloud to locate the contact in 3D. The point cloud holds only the 526 valid points, so skip invalid channels when converting.