SDK Data Reference

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.

FieldTypeDescription
headerFrameHeaderFrame header
dataList[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.

FieldTypeMax PointsDescription
headerFrameHeaderFrame header
palmList[float]290Palm region
thumbList[float]52Thumb region
indexList[float]52Index finger region
middleList[float]58Middle finger region
ringList[float]52Ring finger region
pinkyList[float]45Pinky 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.

FieldTypeDescription
namestrField name
offsetintByte offset
typeintData 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.

FieldTypeDescription
headerFrameHeaderFrame header
frame_idstrCoordinate frame
point_strideintBytes per point
fieldsList[PointField]Field descriptor list
dataList[int]Raw binary data
MethodReturnsDescription
point_count()intGet 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().

FieldTypeDescription
headerFrameHeaderFrame header
dataList[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().

FieldTypeDescription
headerFrameHeaderFrame header
dataList[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
}