R2C协议扩展字段方式
R2C协议的Observations / Actions只有标准字段(joint_states、end_effector_poses等)。要传输深度图、语义指令、自定义信号这类标准字段无法承载的数据,不修改.proto(避免破坏协议兼容),通过extensions字段旁路扩展。
核心思想:自描述盒子 —— 每个扩展值自带dtype(类型)+ shape(维度),消费端拿到数据即可解析,零握手、无需外部schema。
Observations / Actions
├── joint_states / end_effector_poses / ... ← 固定字段(proto 定义)
└── extensions: ← 自贴标签的盒子
"depth_image" → {dtype: FLOAT32, shape: [480,640], data: <bytes>}
"language_goal"→ {dtype: STRING, shape: [], data: "pick cube"} 协议定义
DType枚举(common.proto)
enum DType {
DTYPE_UNSPECIFIED = 0;
FLOAT32 = 1; FLOAT64 = 2; INT32 = 3; INT64 = 4;
UINT8 = 5; BOOL = 6; STRING = 7; BYTES = 8;
} ExtensionValue消息(common.proto)
message ExtensionValue {
DType dtype = 1; // 元素类型(必填)
repeated int32 shape = 2; // 维度,[] = 标量,[H,W,C] = 图像
bytes data = 3; // flat row-major 原始数据
string mime_type = 4; // 可选,压缩/编码标注(如 "image/jpeg")
} 在消息中的位置
message Observations { map<string, ExtensionValue> extensions = 12; } // 观测
message Actions { map<string, ExtensionValue> extensions = 8; } // 动作
// ObservationsH264 同样携带 extensions = 12,行为一致 配置驱动用法
在mapper rule中添加extension块:
device_translator:
mappings:
# ── 完整示例:深度图(ndarray → bytes → 包装) ──
- source: "camera.depth_frame"
target: "extensions.depth_image" # 必须 extensions. 前缀
transforms:
- ndarray_to_bytes # 可选:前置变换链
extension:
dtype: FLOAT32 # 必填
shape: [480, 640] # 可选,默认 [] = 标量
mime_type: "" # 可选
# ── 标量示例:语言指令 ──
- source: "task.language_goal"
target: "extensions.language_goal"
extension:
dtype: STRING
# ── 常量示例:无 source,直接写 default ──
- default: [1, 1, 1]
target: "extensions.home_pose"
extension:
dtype: FLOAT64
shape: [3] 执行流程(源码:config_mapper.py)
1. 从 source 读取原始值(或取 default) 2. 依次应用 transforms 变换 3. 应用 slice(如有) 4. _wrap_extension_value():按 dtype/shape/mime_type 打包为 ExtensionValue dict 5. 写入 output["extensions"]["<字段名>"] 6. 序列化时由 _parse_extensions() 转为 protobuf ExtensionValue
打包细节
| 输入值类型 | 打包方式 |
|---|---|
| bytes / bytearray | 直接作为data(配合ndarray_to_bytes等transform)。 |
| str(dtype=STRING) | UTF-8编码进data。 |
| 标量/嵌套 list(数值 dtype) | 递归flatten后逐元素struct.pack小端打包。 |
| default 常量 | 与数值dtype同规则(如[1,1,1] + shape:[3] → 3 元素buffer)。 |
编程接口(写自定义translator/adapter时)
r2c_sdk.common.models.wrappers.common.ExtensionValue:
from r2c_sdk.common.models.wrappers.common import ExtensionValue
import numpy as np
# ── 写入端:工厂方法 ──
ExtensionValue.from_ndarray(np.zeros((480, 640), np.float32))
# → dtype="FLOAT32", shape=[480,640], data=arr.tobytes()
ExtensionValue.from_string("pick cube") # dtype=STRING
ExtensionValue.from_scalar(3.14) # FLOAT64,struct "<d" 小端
ExtensionValue.from_scalar(True) # BOOL,\x01/\x00
ExtensionValue.from_scalar(42) # INT64,8 字节小端有符号
# 直接构造(等效)
ExtensionValue(dtype="UINT8", shape=[224,224,3], data=jpeg_bytes, mime_type="image/jpeg")
# ── 消费端:解析方法 ──
ev.to_ndarray() # np.frombuffer(data, dtype).reshape(shape);标量 shape=[] 返回标量
ev.to_string() # 仅 STRING,UTF-8 解码
ev.to_scalar() # 仅标量数值/BOOL
# STRING/BYTES 也可直接读 ev.data 消费端解析(云端/任意第三方)
import numpy as np
ext = msg.extensions["depth_image"]
if ext.mime_type: # 先按 mime 解码(如 JPEG)
arr = cv2.imdecode(np.frombuffer(ext.data, np.uint8), cv2.IMREAD_UNCHANGED)
else:
dtype = {"FLOAT32": np.float32, "FLOAT64": np.float64, "INT32": np.int32,
"INT64": np.int64, "UINT8": np.uint8, "BOOL": np.bool_}[ext.dtype]
arr = np.frombuffer(ext.data, dtype=dtype).reshape(ext.shape) 下发方向(r2c_to_device)同理:云端把扩展字段写进Actions.extensions边端mapper从extensions.<名字>读取。