Tactile Data
# Tactile matrix
sub = glove.tactile().subscribe()
frame = await sub.recv_async()
print(f"Max pressure: {max(frame.data):.2f}")
# Zone data
sub = glove.tactile_zones().subscribe()
zones = await sub.recv_async()
print(f"Palm: {zones.palm}, Thumb: {zones.thumb}")TactileFrame
Calibrated tactile matrix data. Frame rate: 120 FPS.
| Field | Type | Description |
|---|---|---|
header | FrameHeader | Frame header |
data | List[float] | 768 values: 526 active points (pressure 0.0~1.0), the rest are invalid taxels fixed at -1.0 |
data is an array of 768 values in 24×32 row-major order. Access row r, column c: data[r * 32 + c]. Invalid positions are fixed at -1.0, which distinguishes them from an active taxel reading zero pressure (0.0).
{
"header": {
"seq": 42,
"timestamp_us": 1709876543210,
"frame_id": ""
},
"data": [-1.0, 0.12, 0.45, 0.78, -1.0, 0.33, ...] // 768 values total, 526 active, invalid taxels are -1.0
}TactileZones
Tactile data aggregated by finger region.
| Field | Type | Max Points | Description |
|---|---|---|---|
header | FrameHeader | — | Frame header |
palm | List[float] | 290 | Palm region |
thumb | List[float] | 52 | Thumb region |
index | List[float] | 52 | Index finger region |
middle | List[float] | 58 | Middle finger region |
ring | List[float] | 52 | Ring finger region |
pinky | List[float] | 45 | Pinky finger region |
{
"header": {
"seq": 42,
"timestamp_us": 1709876543210,
"frame_id": ""
},
"palm": [0.0, 0.12, 0.45, ...], // up to 290 values
// For each finger's point count, see the table above
"thumb": [0.0, 0.85, 0.92, ...],
"index": [0.0, 0.67, 0.23, ...],
"middle": [0.0, 0.34, ...],
"ring": [0.0, 0.11, ...],
"pinky": [0.0, 0.05, ...]
}PointField
Point cloud field descriptor.
| Field | Type | Description |
|---|---|---|
name | str | Field name |
offset | int | Byte offset |
type | int | Data type encoding, fixed at 7 (float32) |
PointCloud
Tactile point cloud data.
The spatial positions in glove.tactile_point_cloud() come from the EMF-derived hand pose, so its output rate follows the EMF divider.
| Field | Type | Description |
|---|---|---|
header | FrameHeader | Frame header |
frame_id | str | Coordinate frame |
point_stride | int | Bytes per point |
fields | List[PointField] | Field descriptor list |
data | List[int] | Raw binary data |
| Method | Returns | Description |
|---|---|---|
point_count() | int | Get the number of points |
{
"header": {
"seq": 42,
"timestamp_us": 1709876543210,
"frame_id": "l_wrist"
},
"frame_id": "l_wrist",
"point_stride": 12,
"fields": [
{
"name": "x",
"offset": 0,
"type": 7
},
{
"name": "y",
"offset": 4,
"type": 7
},
{
"name": "z",
"offset": 8,
"type": 7
}
],
"data": [0, 0, 128, 63, ...] // raw binary, 12 bytes per point
}TactileBinary
Binary contact detection inferred from tactile data. Frame rate: 120 FPS, published at the same rate as glove.tactile().
| Field | Type | Description |
|---|---|---|
header | FrameHeader | Frame header |
data | List[float] | 768 contact-state values: 1.0 contact, 0.0 no contact, -1.0 invalid taxel |
data is laid out in 24×32 row-major order, with the same shape and mask as TactileFrame (526 active points). Access row r, column c: data[r * 32 + c]. Existing tactile-grid visualizers work unchanged.
Contact detection relies on a trained contact model that is trained in Wuji Studio and auto-loaded per device. 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.
When using the SDK directly (without Wuji Studio), configure the trained model directory for tactile_binary before subscribing. The directory must contain contact.safetensors and contact.npz. The SDK hot-reloads automatically when the directory changes or the model is retrained.
# Configure the trained contact model directory (set automatically by Wuji Studio when used through it)
glove.set("algorithms.tactile_binary.model_dir", "/path/to/contact_model")
sub = glove.tactile_binary().subscribe()
frame = await sub.recv_async()
contacts = sum(1 for v in frame.data if v == 1.0)
print(f"Active contacts: {contacts}"){
"header": {
"seq": 42,
"timestamp_us": 1709876543210,
"frame_id": ""
},
"data": [-1.0, 0.0, 1.0, 1.0, -1.0, 0.0, ...] // 768 values total, 526 active
}TactileResidual
Per-taxel signed contact residual inferred from tactile data. Frame rate: 120 FPS, published at the same rate as glove.tactile().
| Field | Type | Description |
|---|---|---|
header | FrameHeader | Frame header |
data | List[float] | 768 residual values. Positive = pressed harder than the baseline (contact), ~0 = no contact, negative = lighter than the baseline, -1.0 marks an invalid taxel |
data is laid out in 24×32 row-major order, with the same shape and mask as TactileFrame (526 active points). Access row r, column c: data[r * 32 + 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 trained contact model that is trained in Wuji Studio and auto-loaded per device. 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.
When using the SDK directly (without Wuji Studio), configure algorithms.tactile_binary.model_dir (tactile_residual shares the same model directory setting as tactile_binary). The directory must contain contact.safetensors and contact.npz. The SDK hot-reloads automatically when the directory changes or the model is retrained.
# Configure the trained contact model directory (set automatically by Wuji Studio when used through it)
glove.set("algorithms.tactile_binary.model_dir", "/path/to/contact_model")
sub = glove.tactile_residual().subscribe()
frame = await sub.recv_async()
valid = [v for v in frame.data if v != -1.0]
print(f"Residual range: [{min(valid):.2f}, {max(valid):.2f}]"){
"header": {
"seq": 42,
"timestamp_us": 1709876543210,
"frame_id": ""
},
"data": [-1.0, 0.05, 1.23, 0.87, -1.0, -0.02, ...] // 768 values total, 526 active
}