触觉数据
# 触觉矩阵
sub = glove.tactile().subscribe()
frame = await sub.recv_async()
print(f"最大压力值: {max(frame.data):.2f}")
# 分区数据
sub = glove.tactile_zones().subscribe()
zones = await sub.recv_async()
print(f"手掌: {zones.palm}, 拇指: {zones.thumb}")TactileFrame
校准后的触觉矩阵数据。帧率:120 FPS。
| 字段 | 类型 | 说明 |
|---|---|---|
header | FrameHeader | 帧头 |
data | List[float] | 768 个值,其中 526 个为有效触点(压力值 0.0~1.0),其余为无效触点,固定为 -1.0 |
data 为 768 个值的数组,按 24×32 row-major 排列。访问第 row 行第 col 列:data[row * 32 + col]。无效位置固定为 -1.0,可据此与有效触点的零压力 0.0 区分。
{
"header": {
"seq": 42,
"timestamp_us": 1709876543210,
"frame_id": ""
},
"data": [-1.0, 0.12, 0.45, 0.78, -1.0, 0.33, ...] // 共 768 个值,526 个有效,无效点为 -1.0
}TactileZones
按手指区域聚合的触觉数据。
| 字段 | 类型 | 最大点数 | 说明 |
|---|---|---|---|
header | FrameHeader | — | 帧头 |
palm | List[float] | 290 | 手掌区域 |
thumb | List[float] | 52 | 拇指区域 |
index | List[float] | 52 | 食指区域 |
middle | List[float] | 58 | 中指区域 |
ring | List[float] | 52 | 无名指区域 |
pinky | List[float] | 45 | 小指区域 |
{
"header": {
"seq": 42,
"timestamp_us": 1709876543210,
"frame_id": ""
},
"palm": [0.0, 0.12, 0.45, ...], // 最多 290 个值
// 以下各指点数见上表
"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
点云字段描述。
| 字段 | 类型 | 说明 |
|---|---|---|
name | str | 字段名称 |
offset | int | 字节偏移 |
type | int | 数据的数值类型编码,固定为 7(float32) |
PointCloud
触觉点云数据。
glove.tactile_point_cloud() 的空间位置依赖 EMF 链路解算的手部姿态,输出率随 EMF 链路的 降频因数 同步变化。
| 字段 | 类型 | 说明 |
|---|---|---|
header | FrameHeader | 帧头 |
frame_id | str | 坐标系 |
point_stride | int | 每点字节数 |
fields | List[PointField] | 字段描述列表 |
data | List[int] | 原始二进制数据 |
| 方法 | 返回 | 说明 |
|---|---|---|
point_count() | int | 获取点数量 |
{
"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, ...] // 原始二进制,每点 12 字节
}TactileBinary
二值接触检测结果,基于触觉数据推断,帧率:120 FPS,与 glove.tactile() 同频发布。
| 字段 | 类型 | 说明 |
|---|---|---|
header | FrameHeader | 帧头 |
data | List[float] | 768 个接触状态值:1.0 接触,0.0 未接触,-1.0 无效触点 |
data 按 24×32 row-major 排列,与 TactileFrame 同形、同掩码(526 个有效触点)。访问第 row 行第 col 列:data[row * 32 + col]。现有触觉网格可视化无需改动即可复用。
接触判定依赖一个训练好的接触模型,模型在 Wuji Studio 中训练并按设备自动加载。未加载模型时,数据流仍以 120 FPS 发布,但所有有效触点恒为 0.0(无接触),无效触点为 -1.0,不会出现 1.0。
直接通过 SDK 使用(不经 Wuji Studio)时,先把训练好的模型目录配置给 tactile_binary 再订阅。目录需包含 contact.safetensors 与 contact.npz 两个文件。模型目录变更或重新训练后,SDK 会自动热重载。
# 配置训练好的接触模型目录(经 Wuji Studio 使用时由 Studio 自动配置)
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"接触触点数: {contacts}"){
"header": {
"seq": 42,
"timestamp_us": 1709876543210,
"frame_id": ""
},
"data": [-1.0, 0.0, 1.0, 1.0, -1.0, 0.0, ...] // 共 768 个值,526 个有效
}TactileResidual
每像素有符号接触残差,基于触觉数据推断,帧率:120 FPS,与 glove.tactile() 同频发布。
| 字段 | 类型 | 说明 |
|---|---|---|
header | FrameHeader | 帧头 |
data | List[float] | 768 个残差值。正值 = 比基线压得重(接触),约 0 = 无接触,负值 = 比基线更轻,-1.0 表示无效触点 |
data 按 24×32 row-major 排列,与 TactileFrame 同形、同掩码(526 个有效触点)。访问第 row 行第 col 列:data[row * 32 + col]。
残差信号不平滑、不归一化、不二值化,业务侧自行定阈值与后处理。和 TactileBinary 共享同一接触模型,同时订阅两者只跑一次模型推断。
残差解算依赖一个训练好的接触模型,模型在 Wuji Studio 中训练并按设备自动加载。未加载模型时,数据流仍以 120 FPS 发布,但所有有效触点恒接近静止零点,无法反映真实压力变化。
直接通过 SDK 使用(不经 Wuji Studio)时,配置 algorithms.tactile_binary.model_dir 即可(tactile_residual 与 tactile_binary 共享该模型目录设置)。目录需包含 contact.safetensors 与 contact.npz 两个文件。模型目录变更或重新训练后,SDK 会自动热重载。
# 配置训练好的接触模型目录(经 Wuji Studio 使用时由 Studio 自动配置)
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"残差范围: [{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 个值,526 个有效
}