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
| Item | Requirement |
|---|---|
| Wuji Glove | 1, tactile calibration completed |
| Connection | Ethernet |
| Terminal | At least 31 columns wide and 26 rows tall |
Software
| Item | Requirement |
|---|---|
| Operating system | Linux or a Mac with Apple silicon, meeting the system requirements |
| Wuji SDK | v2026.8.31 or later |
| Python | 3.10 or later |
| C toolchain | CMake 3.16 or later, plus the extracted SDK package |
Python
C
pip install wuji-sdkLIB="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
| Character | Test | Meaning |
|---|---|---|
# | v > 0.5 | Contact |
. | -0.5 <= v <= 0.5 | Hand surface, no contact |
| Space | v < -0.5 | Invalid 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) | Field | Meaning |
|---|---|
contacts | Taxels with v > 0.5 in this frame |
sensitivity | Current sensitivity |
peak | Highest value in this frame |
row / col | Grid coordinates of the peak, from peak_idx // 31 and peak_idx % 31 |
len | Frame length, normally 744 |
Sensitivity Has Two Clamps, and Only One Is Real
This is the detail worth knowing on this page.
| Layer | Range | Behavior |
|---|---|---|
Example’s clamp_sensitivity | [0.5, 3.0] | Clamps to the boundary |
| SDK parameter layer | No clamp | Any 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 point | Behavior | What to do |
|---|---|---|
| No tactile calibration | The grid is all ., with contacts=0 and a constant peak=0.000 | Run tactile calibration first |
| Glove not worn | Same as above | Put the glove on and touch something |
| Sensitivity outside [0.5, 3.0] | The example clamps before writing | For values outside that range, call set() directly instead |
| Sensitivity write fails | Prints one line to stderr and the view continues | Check the device connection |
| Non-interactive terminal | Keyboard control is unavailable, so it runs out --seconds or waits for Ctrl-C | Preset a value with --sensitivity |
--seconds 0 | Runs until q or Ctrl-C | That’s the default |
| Frame length isn’t 744 | Anything beyond is treated as invalid | The 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.
Related
- Tactile Calibration: the hard requirement for this page
- Tactile Residual Grid View: the same grid showing a continuous signal
- Subscribing With Callbacks: where 744, 526, and 218 come from
- Common Data Types: the
TactileBinarystructure
Documentation