# Transformer使用指导
#### 一、Transformer概述
Transformer（值变换器）是**配置驱动映射** 中的数据转换单元，位于mapping rule的transforms链上，对**单个值**做转换：类型转换、切片索引、数值缩放、图像编解码、机器人夹爪换算等。
Transformer只在device_to_r2c（上行：将设备数据转换到Observations）和r2c_to_device（下行：将Actions数据转换到设备指令）两个映射段里生效。
三条使用路径如[表1]所示。
 表1三条使用路径说明 
| 场景         | 做法                                          | 章节                                                                     |
|:---|:---|:---|
| 用现成能力      | 在内置清单里找名字，写进YAML。                           | [内置Transformer清单]          |
| 单次覆盖（进程内） | build_transformer_registry(overrides=...) 传实例。 | [程序化注入overrides]            |
| 需要分发/复用    | 打包成wheel，用entry_point注册。                    | [基于entry_point扩展Transformer] |
   
#### 二、快速上手
通过以下示例帮助您快速上手。
#### YAML写法
机器人配置YAML里，transforms写在一条mapping rule内部：
```
device_to_r2c:
  mappings:
    # 腕部相机：ndarray → JPEG bytes（Observations.images.* 必须是 bytes）
    - target: "images.color.wrist"
      source: "wrist"
      transforms:
        - ndarray_to_jpeg: {quality: 85}
    # 末端位姿：numpy → list
    - target_key: "joint_states.position"
      source_path: "eef_pose"
      transforms: array_to_list
r2c_to_device:
  mappings:
    # 模型输出 7D → 前 6 维给 eef_pose，第 7 维给夹爪
    - target: "eef_pose"
      source: "joint_states.position"
      transforms:
        - slice: {start: 0, end: 6}
```
完整可运行样例详见在CloudRobo平台下载的R2C SDK包中的config/robot_so101_lerobot_config.yaml。
#### 程序化写法
```
from r2c_sdk.core.config_mapper import ConfigDrivenMapper
mapper = ConfigDrivenMapper.from_rule_mappings([
    {"source": "eef_pose", "target": "joint_states.position",
     "transforms": ["array_to_list"]},
])
mapper.map({"eef_pose": [0.1, 0.2, 0.3, 0.0, 0.0, 0.0]})
```
#### 查看当前有哪些transformer
```
from r2c_sdk.core.transformers import TransformerRegistry
print(TransformerRegistry.available_transformers())   # 内置 + 第三方，按字母序
print(TransformerRegistry.lookup("ndarray_to_jpeg"))  # 拿到实例，或 None
```
#### 三、配置语法详解
如下详细说明各配置语法。
#### transform与transforms
表2transform与transforms说明 
| 写法                           | 含义            |
|:---|:---|
| transform: \<条目\>             | 单数，等价于只有一个元素的链。 |
| transforms: \[\<条目\>, ...\] | 复数，按顺序串联。     |
   
两者**互斥**，同时出现直接报错：
```
ValueError: Use 'transforms' or 'transform' (singular) — not both.
```
transforms: \[\] 和 transforms:（值为 null）都会报错，不需要转换就删掉这个key。
#### 单个条目的四种写法
表3四种写法示例 
| 序号 | 写法        | 示例                                | 传给transform() 的config |
|:---|:---|:---|:---|
| 1     | 裸名字符串       | to_float                           | None            |
| 2   | 名字:参数 简写   | "crop:bottom_left"             | 字符串 "bottom_left"  |
| 3   | 单键映射        | {ndarray_to_jpeg: {quality: 85}} | 映射 {"quality": 85}   |
| 4    | 单键映射（标量） | {subtract: 1.0}                | 1.0                |
   
```
transforms: to_float                       # 写法 1
transforms: "crop:bottom_left"             # 写法 2（注意整个条目要加引号）
transforms: [{"ndarray_to_jpeg": {"quality": 85}}]   # 写法 3
transforms: [{"subtract": 1.0}]            # 写法 4
```
**写法2的config永远是字符串**，其适配 crop（区域名）、decode_image（bgr/rgb/gray）这类字符串参数；但对ndarray_to_jpeg这种要dict的会失败："ndarray_to_jpeg:85" → Invalid config for transformer 'ndarray_to_jpeg': ... got str。
#### 不支持name/config双键写法
下面这种写法（在部分旧文档里出现过）**会直接报错**：
```
transforms:
  - name: my_scale          # ✗ 非法
    config:
      factor: 0.001
```
正确写法是将名字作为唯一key，config作为它的值：
```
transforms:
  - my_scale: {factor: 0.001}     # ✓
```
#### 流水线：链式组合
列表即流水线，前一个的输出是后一个的输入：
```
transforms:
  - "crop:center"                  # ndarray 裁剪
  - {resize: [224, 224]}           # 缩放
  - {bgr_to_rgb: null}             # 通道序（无参数用 null 占位）
  - {ndarray_to_jpeg: {quality: 90}}
```
链中任何一步抛出异常，整条rule失败，异常向上抛给调用方。
 #### 程序化注入overrides
build_transformer_registry(overrides=...) 接受**实例** （注意与entry_point注册**类**不同），可用于测试或临时替换：
```
from r2c_sdk.core.config_mapper import ConfigDrivenMapper
from r2c_sdk.core.transformers import build_transformer_registry
registry = build_transformer_registry(overrides={"my_double": MyDouble()})
mapper = ConfigDrivenMapper.from_rule_mappings(rules, transformers=registry)
```
#### context参数
transform(value, config, context) 的第三个参数是**整个源 payload**，供transformer按名称反查索引（subtract / euler_modulo 的names模式用它）。
```
mapper.map({"pose": [0.1, 7.0], "pose_names": ["roll", "yaw"]})
```
 #### 四、内置Transformer清单
当前版本共**63**个内置transformer。权威清单以TransformerRegistry.available_transformers()为准（版本升级会新增）。
#### 类型与结构
表4类型与结构转换器说明 
| 名称                         | 说明                              | 配置   |
|:---|:---|:---|
| identity                   | 原样返回。                           | 无配置。   |
| to_float / to_int / to_str | 标量类型转换。                         | 无配置。 |
| to_bool                      | 支持 "true"/"yes"/"on"/"1" 等字符串。  | 无配置。   |
| to_list                     | 可迭代对象转换为list（拒绝str/bytes）。        | 无配置。   |
| list_wrapper                 | 整体包成\[value\]（与 to_list 展平语义相反）。 | 无配置。   |
   
#### 序列/数值
表5序列/数值说明 
| 名称               | 说明                             | 配置                                                             |
|:---|:---|:---|
| slice             | value\[start:end\]转换为新list。   | {start: N, end: M}（至少一个）。                                   |
| index           | 取第N个元素。                         | {index: N}                                                     |
| subtract          | 逐元素相减。                         | 标量/等长list/，参考[subtract的三种模式]。 |
| euler_modulo     | 指定位置取模（欧拉角回绕）。                  | {modulus, indices} 或 {modulus, names, names_path}            |
| array_to_list       | ndarray转换为list（依赖 .tolist()）。 | 无配置。                                                         |
| list_to_ndarray | 序列 转换为float32 ndarray。         | 无配置。                                                           |
| crop             | ndarray裁剪。                   | 参考[crop的三种模式]。            |
| resize            | ndarray缩放。                      | \[height, width\]                                             |
| normalize           | 归一化/缩放。                          | {type: scale, range, source_min, source_max}                 |
| format             | HWC与CHW布局转换（双向转换）（仅3D）。         | "HWC" / "CHW"                                                 |
| transpose         | 任意轴序转置。                        | \[2, 0, 1\]这类整数列表                                             |
   
- **subtract的三种模式**
  ```
  transforms: [{"subtract": 1.0}]                       # 标量：所有元素 -1.0
  transforms: [{"subtract": [0.0, 0.0, 1.0]}]           # 等长列表：逐元素相减
  transforms:                                           # 定向模式：只改指定位置
    - subtract: {values: [1.0], indices: [2]}
  transforms:                                           # 定向模式（按名，需 payload 带名字表）
    - subtract: {values: [1.0], names: [yaw], names_path: pose_names}
  ```
  indices 与 names 互斥；names 模式要求 context 中存在 names_path 指向的名字列表，否则抛错。
  
- **crop的三种模式**
  ```
  transforms: "crop:bottom_left"                       # 预设区域名
  transforms: [{"crop": {"region": "center"}}]         # 映射形式
  transforms: [{"crop": {"box": [100, 300, 50, 400]}}] # 像素框 [y1, y2, x1, x2]
  ```
  9个预设：top_left、top_right、bottom_left、bottom_right、left_half、right_half、top_half、bottom_half、center。box越界或y1 \>= y2会报错。
  
 
#### 图像编解码
表6图像编解码说明 
| 名称                       | 方向                           | 配置                                       |
|:---|:---|:---|
| ndarray_to_png        | ndarray转换为bytes。            | 无配置。                                      |
| ndarray_to_jpeg        | ndarray转换为bytes。             | {quality: 0-100}，默认95。                    |
| ndarray_to_webp        | ndarray转换为bytes。           | {quality: 0-100}，默认80。                  |
| ndarray_to_bytes       | ndarray转换为裸内存bytes。         | 无配置。                                  |
| bytes_to_ndarray        | 裸 bytes转换为ndarray。          | {dtype: "float32", shape: \[...\]}必填dtype。 |
| png_to_ndarray         | bytes转换为ndarray。            | 无配置。                                    |
| jpeg_to_ndarray         | bytes转换为ndarray。          | 无配置。                                     |
| webp_to_ndarray        | bytes转换为ndarray。            | 无配置。                                     |
| decode_image            | bytes转换为ndarray（自动识别格式）。    | "bgr" / "rgb" / "gray"                  |
| bgr_to_rgb / rgb_to_bgr | 通道序互换。                      | 无配置。                                    |
| jpeg_to_base64          | bytes/ndarray转换为base64字符串。 | 无配置。                                    |
   
WebP体积通常比同画质JPEG小30\~40%，编码耗时高约10\~15倍，适合带宽敏感场景。
 #### ROS消息
表7ROS 消息说明 
| 名称                                                                               | 说明                                                                                                                                                                                                                                                                                                                                                         |
|:---|:---|
| ros_image_to_ndarray / ros_image_to_png / ros_image_to_jpeg / ros_image_to_webp | sensor_msgs/Image转换为各种目标。                                                                                                                                                                                                                                                                                                                                  |
| ros_compressed_image_to_ndarray / _png / _jpeg / _webp                            | sensor_msgs/CompressedImage转换为各种目标。                                                                                                                                                                                                                                                                                                                         |
| ros_compress_image_to_\*（4 个）                                                   | 上一行的别名，兼容compress拼写。                                                                                                                                                                                                                                                                                                                                     |
| ros_message_to_mapping                                                         | 任意ROS消息转换为递归dict。                                                                                                                                                                                                                                                                                                                                         |
| list_to_ros_float64_multi_array                                               | list转换为std_msgs/Float64MultiArray。                                                                                                                                                                                                                                                                                                                         |
| scalar_to_ros_float64                                                            | 标量转换为std_msgs/Float64。                                                                                                                                                                                                                                                                                                                                     |
| list_to_ros_joint_state                                                        | list转换为sensor_msgs/JointState（{names, velocity, effort}可选）。                                                                                                                                                                                                                                                                                                 |
| list_to_ros_move_request                                                       | 6维位姿转换为jaka_msgs/srv/Move.Request。                                                                                                                                                                                                                                                                                                                        |
| select_joints_by_name                                                        | 按名字筛选/重排关节数组，如下所示： **select_joints_by_name** 输入必须是含name（或 names）列表的Mapping，其余等长列表会同步筛选重排： ``` transforms: - select_joints_by_name: names: ["joint_1", "joint_3", "joint_5"] ``` 请求的名字在源消息中找不到时**直接报错**（并打印requested / available / missing三行对照），不会静默补0。 |
   
ros_image_to_\* 支持 rgb8、bgr8、rgba8、bgra8、mono8、8uc1、mono16、16uc1编码。
[ROS 消息]及[机器人夹爪]中涉及ROS消息类型的transformer（ros_\*、list_to_ros_\*、scalar_to_ros_float64、scalar_to_step_motor_gripper\*）在调用时才import对应的ROS消息包，因此**必须在装有对应ROS运行时的机器上运行**；纯Python环境调用会抛ImportError。
 #### 机器人夹爪
表8机器人夹爪说明 
| 名称                                                                   | 适用      | 方向                                   |
|:---|:---|:---|
| parallel_gripper_fingers_to_stroke                                 | 平行夹爪     | 两指 \[left, right\] 转换为行程（求和）。       |
| parallel_gripper_stroke_to_fingers                                | 平行夹爪   | 行程 转换为\[S/2, S/2\]。                  |
| scalar_to_step_motor_gripper                                     | 步进电机夹爪  | 标量 转换为 step_motor/Motor 开合指令（按阈值）。 |
| scalar_to_step_motor_gripper_debounce                              | 同上      | 连续N次越阈才发指令（去抖动）。                    |
| so101_gripper_value_to_joint / so101_gripper_joint_to_value        | SO101  | 模型值 ↔ 关节弧度（双向转换）。                  |
| extract_gripper_states                                           | 青龙       | 关节列表 转换为 左右夹爪二值状态。                |
| qinglong_gripper_real_transform / qinglong_gripper_visual_transform | 青龙     | 夹爪值 转换为 真实 / 视觉关节指令。               |
| moz1_gripper_value_to_joints / moz1_extract_gripper_states          | Moz1   | 夹爪值 ↔ 8 关节（双向转换）。                  |
| jaka_gripper_value_to_joints / jaka_extract_gripper_state           | Jaka    | 夹爪值 ↔ 2 关节（双向转换）。                  |
| r1_gripper_value_to_joint / r1_gripper_joint_to_value                | 星海图 R1 | 夹爪值 ↔ 2 关节（双向转换）。                     |
   
scalar_to_step_motor_gripper配置项：open_threshold(80.0)、close_threshold(20.0)、id、speed、mode、angle、state、sub_divide。值落在两个阈值之间时返回None（不产生指令）。
#### 五、常用配置
图1常用配置图示   
![](https://support.huaweicloud.com/sdkreference-cloudrobo/zh-cn_image_0000002745634324.png "点击放大")
**相机图像上行（ndarray）**
```
- target: "images.color.wrist"
  source_path: "wrist"
  transforms:
    - "crop:center"
    - {resize: [224, 224]}
    - {ndarray_to_jpeg: {quality: 85}}
```
**ROS2图像上行**
```
- target: "images.color.front"
  source_path: "camera.image"
  transforms:
    - {ros_image_to_jpeg: {quality: 85}}     # 一步到位，无需中间 ndarray
```
**模型输出拆分**
```
- target: "eef_pose"
  source: "joint_states.position"
  transforms: [{slice: {start: 0, end: 6}}]
- target: "gripper_position"
  source: "joint_states.position"
  source_index: 6
```
**夹爪双向换算**（上行提取状态、下行下发指令）
```
device_to_r2c:
  mappings:
    - target: "gripper_stroke"
      source: "joint_states.position"
      transforms: [parallel_gripper_fingers_to_stroke]
r2c_to_device:
  mappings:
    - target: "finger_targets"
      source: "gripper_stroke"
      transforms: [parallel_gripper_stroke_to_fingers]
```
#### 六、校验、调试与排错
参考以下说明进行校验、调试及处理常见报错。
#### 启动即校验（fail fast）
构造ConfigDrivenMapper时（服务启动阶段）会对每条 rule 的每个transform调用validate_config并检查名字是否存在。**配置错误在启动期暴露，不会拖到运行期**。
表9启动时常见报错的处理方法 
| **错误原文**                                                        | **原因分析**           | **处理方法**                                                                               |
|:---|:---|:---|
| Unknown transformer 'x' in rule \<src\> → \<tgt\>            | 名字拼错，或第三方包未安装/未生效。    | 用available_transformers()核对；检查第三方包，可参考[注意事项]。 |
| Invalid config for transformer 'x' in rule ...: \<原因\>       | config不合法。          | 根据报错原因修改YAML配置文件。                                                                        |
| Each transform mapping must include exactly one transform name | 用了name + config双键写法。 | 修改为{转换器名字:config}。                                                                       |
| Use 'transforms' or 'transform' (singular) --- not both.      | 两个key同时出现。           | 任意删掉一个key。                                                                               |
| 'transforms' must not be an empty list.                     | 空列表/ null。          | 删掉该key。                                                                                 |
| targets under 'images.\*' require an image-encoding transform | images.\* 目标没配编码转换。  | 添加ndarray_to_jpeg等。                                                                     |
   
运行期才会出现的错误：Unknown mapper transform: 'x'，以及各transformer自己的类型/形状校验异常。
#### 开启日志
```
import logging
logging.getLogger("r2c_sdk.core.transformers").setLevel(logging.DEBUG)
```
entry_point的冲突与加载失败都以WARNING级别打在这个logger上。
#### 快速定位某个值经过了什么
临时在rule上加identity收敛范围，或写单元测试直接调mapper：
```
mapper = ConfigDrivenMapper.from_rule_mappings([...])
print(mapper.map(payload))
```
 #### 七、基于entry_point扩展Transformer
当内置能力不够，或希望把转换逻辑**独立打包复用** 时，用Python标准entry_point机制注册第三方transformer，**无需修改SDK源码**。
#### 加载扩展包的时序图
加载扩展包的时序图如[图2]所示。
图2加载扩展包的时序图   
![](https://support.huaweicloud.com/sdkreference-cloudrobo/zh-cn_image_0000002772530489.png "点击放大")
两个关键点：
- **扫描只发生一次** （_scanned标志），时机是r2c_sdk.core.transformers首次被import。**装完包必须重启进程**才能生效。
- **实例被缓存复用** （参考[注意事项]）。
#### 加载转换器的优先级
图3加载转换器的优先级图示   
![](https://support.huaweicloud.com/sdkreference-cloudrobo/zh-cn_image_0000002772370693.png "点击放大")
同名时**内置永远胜出**，并记录日志：
```
WARNING r2c_sdk.core.transformers: Transformer 'identity' from entry_point is ignored because a builtin with the same name exists.
```
第三方名字请添加厂商/项目前缀，例如my_gripper_stroke_to_fingers。
#### 实操：以examples/ur5e_transformer为示例
该示例把内置的平行夹爪"手指 ↔ 行程"(双向转换）换算在SDK之外重新实现了一遍（并换成项目自己的比例系数），是标准的第三方transformer包。
```
examples/ur5e_transformer/
├── pyproject.toml                        # entry_point 声明
├── my_parallel_gripper_transformers.py   # transformer 实现
└── README.md                             # 安装说明
```
**第 1 步：实现类**
my_parallel_gripper_transformers.py：
```
from dataclasses import dataclass
from typing import Any, Mapping
from r2c_sdk.core.interfaces import IValueTransformer
@dataclass(frozen=True)
class MyGripperFingersToStrokeTransformer(IValueTransformer):
    """两手指位置 -> 行程。"""
    def transform(self, value: Any, config: Any = None, context: Any = None) -> Any:
        left, right = value
        return (float(left) + float(right)) * 1250.0
@dataclass(frozen=True)
class MyGripperStrokeToFingersTransformer(IValueTransformer):
    """行程 -> 两手指位置（逆变换）。"""
    def transform(self, value: Any, config: Any = None, context: Any = None) -> Any:
        return [float(value) / 2500.0, float(value) / 2500.0]
```
**第 2 步：声明entry_point**
pyproject.toml：
```
[build-system]
requires = ["setuptools"]
build-backend = "setuptools.build_meta"
[project]
name = "my-parallel-gripper-r2c-transformers"
version = "0.1.0"
requires-python = ">=3.10"
dependencies = ["hw-r2c-sdk"]
[tool.setuptools]
py-modules = ["my_parallel_gripper_transformers"]
[project.entry-points."r2c_sdk.transformers"]
my_gripper_fingers_to_stroke = "my_parallel_gripper_transformers:MyGripperFingersToStrokeTransformer"
my_gripper_stroke_to_fingers = "my_parallel_gripper_transformers:MyGripperStrokeToFingersTransformer"
```
要点：
- group名固定为r2c_sdk.transformers。
- 等号左边是**用户写在YAML里的名字**。
- 等号右边是 模块：类，**必须是类**，不是实例。
- 值用引号包起来（含 : 的TOML值）。
**第 3 步：安装**
```
pip install -e .          # 开发期（示例目录 README 用的是 pip install --no-build-isolation -e .）
```
若构建后端在当前环境不可用（受限网络 / 内网源），添加 --no-build-isolation 并预装 setuptools，即示例 README 的做法。uv 用户可直接 uv pip install -e .。
**第 4 步：在YAML中使用**
```
device_to_r2c:
  mappings:
    - target: "gripper_stroke"
      source: "joint_states.position"          # [left, right]
      transform: my_gripper_fingers_to_stroke
r2c_to_device:
  mappings:
    - target: "gripper_fingers"
      source: "gripper_stroke"
      transform: my_gripper_stroke_to_fingers
```
**第 5 步：验证**
```
from r2c_sdk.core.transformers import TransformerRegistry, build_transformer_registry
print([n for n in TransformerRegistry.available_transformers() if n.startswith("my_")])
reg = build_transformer_registry()
print(reg["my_gripper_fingers_to_stroke"].transform([0.02, 0.04]))  # 75.0
print(reg["my_gripper_stroke_to_fingers"].transform(75.0))          # [0.03, 0.03]
```
#### 接口契约
```
class IValueTransformer(ABC):
    @abstractmethod
    def transform(self, value: Any, config: Any = None, context: Any = None) -> Any: ...
    @staticmethod
    def validate_config(config: Any) -> None:
        """在加载期校验配置，非法则抛 ValueError。默认空实现。"""
```
- 必须实现transform，签名固定三参数（context 可忽略）。
- validate_config可选覆盖；**覆盖了就会在启动期被调用**，可在那里做早失败检查。
- transform返回None是合法语义：该rule本帧不产生值（如夹爪去抖动、阈值中间态）。
- 推荐 @dataclass(frozen=True) 写无状态实现；需要内部状态（计数器等）时用普通类。
 
 #### 注意事项
表10注意事项规避方法 
| 注意事项          | 说明                                     | 规避方法                                                                    |
|:---|:---|:---|
| 注册的是**类**     | SDK调ep.load() 后执行cls()，**无参实例化。**        | __init__ 不能有必填参数；需要参数请在transform()里读config。                              |
| 实例**被缓存复用** | 同一名字全进程共用一个实例（_entry_cache），多个mapper共享。 | 有状态transformer 要按需重置状态，或改用纯函数式实现。                                          |
| 扫描只做一次          | import时扫描，装包后当前进程不感知。                    | **重启进程**；重装后仍不生效先确认装进了运行时的那个虚拟环境。                                         |
| 同名内置优先        | 第三方被静默忽略（WARNING日志）。                    | 名字加前缀；检查日志关键字 is ignored because a builtin。                              |
| 加载失败被跳过       | ep.load() 抛错只记 WARNING，名字不出现在注册表里。     | 排查时看日志Failed to load transformer entry_point，或表现为启动期 Unknown transformer。 |
| 非子类被跳过         | 不是IValueTransformer子类只记WARNING。        | 继承IValueTransformer并实现transform。                                          |
| 装了没用          | 打包时模块没被包含（py-modules/packages漏配）。      | python -c "import 你的模块" 先确认可导入。                                           |
   
#### 打包发布
```
python -m build                     # 产出 wheel + sdist
pip install dist/xxx-0.1.0-py3-none-any.whl
```
发布到PyPI或内网源后，用户只需pip install \<包名\> 即可在YAML里按名引用。运行时不需要--extra-index之外的特殊配置，entry_point元数据随wheel一起安装。
