Files
XplorePlane/XP.Hardware.MotionControl/Documents/CoordinateCompensation.md
T

281 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 载物台坐标系补偿功能 | Stage Coordinate Compensation
> 文档版本:v1.0
> 创建日期:2026-07-28
> 适用模块:XP.Hardware.MotionControl
---
## 1. 功能概述 | Overview
在平面 CT 检测中,工件在载物台上的实际摆放位置常与程序假定的"名义位置"存在偏差。主框架通过图像坐标匹配可计算出工件当前的**像素偏移** `(dX, dY)`(可为负)。
坐标系补偿功能负责:
1. 将主框架传入的像素偏移换算为载物台的**物理位移(mm)**,记录为**补偿值**;
2. 执行修正移动,使工件对齐到位;
3. 后续任意"移动到名义位置"的指令,可选择**自动叠加**该补偿值,使工件在整个检测流程中持续对齐。
补偿值在软件启动时初始化为 `(0, 0)`
---
## 2. 坐标模型 | Coordinate Model
### 2.1 补偿值的本质
补偿值是载物台 `StageX / StageY` 坐标系上的一个**物理偏移向量(mm)**,表示"工件实际位置"与"程序名义位置"之间的差。
作用链条:
```
名义 home 位置 → 工件特征在图像里偏了 (dX, dY) 像素
↓ 按当前放大比换算
需要载物台移动 (stageDx, stageDy) mm 才能把特征移正
↓ 记录
补偿值 comp = (stageDx, stageDy)
↓ 后续
"移动到名义位置 P" → 实际目标 = P + comp,工件特征始终对齐
```
### 2.2 为什么存 mm 而非像素
像素偏移与放大比绑定,一旦放大比变化就失效;而**物理 mm 偏移代表工件的真实错位,与放大比无关**,在后续任何放大比下都成立。因此补偿值以 mm(Stage 坐标系)存储。
### 2.3 作用范围
补偿仅作用于 `StageX / StageY``SourceZ / DetectorZ / 旋转轴` 不参与补偿。
---
## 3. 像素 → mm 换算 | Pixel-to-mm Conversion
补偿的换算逻辑与 `MoveStageByPixelOffset` 一致,内部统一由私有方法 `TryConvertPixelToStageMm` 实现:
```
mmPerPixelX = DetectorPixelSizeX / M
mmPerPixelY = DetectorPixelSizeY / M
dxMm = dxPixels × mmPerPixelX × PixelDirectionX
dyMm = dyPixels × mmPerPixelY × PixelDirectionY
```
| 参数 | 来源 | 说明 |
|------|------|------|
| `DetectorPixelSizeX/Y` | App.config`MotionControl:Geometry:*`) | 探测器单像素物理尺寸(mm),由探测器规格书获取 |
| `PixelDirectionX/Y` | App.config | 对齐图像像素方向与载物台运动方向(+1 或 -1) |
| `M` | `GetCurrentGeometry()` 实时计算 | 当前放大倍率(FDD / FOD |
换算失败的情形(返回失败结果):
- 探测器像素尺寸未配置(`DetectorPixelSizeX/Y <= 0`
- 当前放大倍率无效(`M` 为 NaN 或 `<= 0`
> 补偿功能**复用现有几何配置**,不引入新的 App.config 参数。
---
## 4. 记录模式 | Compensation Mode
补偿的记录方式由 `CompensationMode` 枚举控制:
| 模式 | 语义 | 适用场景 |
|------|------|----------|
| `Replace`(默认) | `comp = 本次换算值` | 每次都从同一名义参考位置测量、每位置只记录一次的**一次性对位** |
| `Accumulate` | `comp += 本次换算值` | 在同一位置**迭代修正残差**的闭环逼近 |
### 4.1 如何选择
判断标准是**主框架每次传入的 `dX, dY` 代表什么**
- 传入的是"**这次还差多少**"(残差/增量)→ 用 `Accumulate`
- 传入的是"**离名义原点一共差多少**"(绝对总量)→ 用 `Replace`
### 4.2 覆盖模式的重要前提
`ApplyPixelOffsetCompensation` 的移动为**相对移动**。覆盖模式假设"每次测量都从同一名义参考位置开始、每位置只调用一次"。
若在同一位置**连续多次**修正而不回到参考位置,覆盖模式只保证补偿值不累积,但相对移动会重复叠加,导致物理位置与补偿值不一致。此时应改用 `Accumulate`
### 4.3 累加 vs 覆盖示例
前提:`1 像素 = 0.01mm`(当前放大比下)。
**场景 A:闭环迭代残差(用 Accumulate)** —— 每轮都在当前位置重新测残差:
| 轮次 | 测得残差 | 换算 mm | 相对移动 | Accumulate comp | Replace comp |
|------|------|------|------|------|------|
| 第 1 轮 | +200px | +2.0mm | +2.0 | 2.0 ✓ | 2.0 |
| 第 2 轮 | +30px | +0.3mm | +0.3 | **2.3 ✓** | 0.3 ✗ |
| 第 3 轮 | +2px | +0.02mm | +0.02 | **2.32 ✓** | 0.02 ✗ |
台子实际总共走了 2.32mm,只有累加能记成真实总偏移。
**场景 B:绝对总量(用 Replace)** —— 每次都相对固定名义原点给出总偏移:
| 时刻 | 测得总偏移 | 换算 mm | Accumulate comp | Replace comp |
|------|------|------|------|------|
| 第 1 次 | +200px | +2.0mm | 2.0 | 2.0 ✓ |
| 第 2 次重测 | +205px | +2.05mm | 4.05 ✗ | **2.05 ✓** |
第 2 次的 205px 已包含第一次的 200px,累加会重复计入。
---
## 5. 接口说明 | API Reference
全部定义在 `IMotionControlService`(单例)。
| 方法 | 说明 |
|------|------|
| `ApplyPixelOffsetCompensation(dxPixels, dyPixels, mode = Replace)` | 换算像素→mm,按模式记录补偿,并以相对移动方式驱动 StageX/StageY 修正到位(记录 + 移动,一步完成) |
| `RecordPixelOffsetCompensation(dxPixels, dyPixels, mode = Replace)` | 仅换算并记录补偿,不移动(预览/仅计算) |
| `GetStageCompensation()` | 返回当前补偿值 `(Xmm, Ymm)` |
| `SetStageCompensation(xMm, yMm)` | 直接以物理量(mm)设置补偿值 |
| `ResetStageCompensation()` | 补偿值清零 |
| `MoveToTarget(axisId, target, speed = null, applyCompensation = false)` | 单轴定点移动;`applyCompensation = true` 时仅对 StageX/StageY 叠加补偿 |
| `MoveAllToTarget(targets, applyCompensation = false)` | 多轴联动移动;`applyCompensation = true` 时仅对字典中的 StageX/StageY 目标叠加补偿 |
### 5.1 补偿值生命周期
- 补偿值随软件运行保持,存储在 `MotionControlService` 单例中。
- **`HomeAll()` / `HomeAllAsync()` 回零不会清零补偿**,补偿的清零仅通过 `ResetStageCompensation()`
- 换工件、重新装夹时应由主框架显式调用 `ResetStageCompensation()`(覆盖模式下也可依赖新值直接冲掉旧值,但显式清零语义更明确)。
### 5.2 边界校验
`MoveToTarget` / `MoveAllToTarget` 叠加补偿后,**仍执行原有的 `Min/Max` 边界校验**。若补偿把目标推出行程范围,命令会被拒绝。
### 5.3 线程安全
补偿值以 `_compXmm / _compYmm` 字段存储,读写通过 `_compLock` 加锁保护,以应对轮询线程与 UI/命令线程的并发访问。
### 5.4 事件通知
补偿值被记录、设置或清零时,发布 `StageCompensationChangedEvent`
```csharp
public record StageCompensationData(double Xmm, double Ymm);
public class StageCompensationChangedEvent : PubSubEvent<StageCompensationData> { }
```
UI 可订阅该事件实时显示当前补偿量。
---
## 6. 典型调用流程 | Typical Usage
### 6.1 主框架:坐标匹配后记录并修正
```csharp
public class CoordinateMatcher
{
private readonly IMotionControlService _mc;
public CoordinateMatcher(IMotionControlService mc) => _mc = mc;
// 坐标匹配得到像素偏移后,一步完成"记录补偿 + 修正移动"(默认覆盖)
public void OnMatchCompleted(double dxPixels, double dyPixels)
{
var result = _mc.ApplyPixelOffsetCompensation(dxPixels, dyPixels);
if (!result.Success)
_log.Warn($"补偿失败:{result.ErrorMessage}");
}
// 同一位置迭代修正残差时,使用累加模式
public void OnResidualMatch(double dxPixels, double dyPixels)
{
_mc.ApplyPixelOffsetCompensation(dxPixels, dyPixels, CompensationMode.Accumulate);
}
}
```
### 6.2 其他类库:定点移动叠加补偿
```csharp
// 单轴:仅 StageX/StageY 生效
_mc.MoveToTarget(AxisId.StageX, 100.0, applyCompensation: true);
_mc.MoveToTarget(AxisId.StageY, 50.0, applyCompensation: true);
// 多轴联动一次性带补偿
_mc.MoveAllToTarget(new Dictionary<AxisId, double>
{
{ AxisId.StageX, 100.0 },
{ AxisId.StageY, 50.0 }
}, applyCompensation: true);
```
### 6.3 查询、设置与清零
```csharp
// 查询当前补偿值
var (compX, compY) = _mc.GetStageCompensation();
// 直接设置(如从历史记录恢复)
_mc.SetStageCompensation(1.25, -0.80);
// 换工件时清零
_mc.ResetStageCompensation();
```
### 6.4 订阅补偿变化事件
```csharp
eventAggregator.GetEvent<StageCompensationChangedEvent>()
.Subscribe(data =>
{
// 更新 UI 显示
CompensationText = $"补偿: X={data.Xmm:F4}mm, Y={data.Ymm:F4}mm";
}, ThreadOption.UIThread);
```
---
## 7. 点击居中 vs 坐标系补偿 | Click-to-Center vs Compensation
两者基于相同的像素→mm 换算,但用途不同:
| 用途 | 接口 | 是否记录补偿 | 说明 |
|------|------|--------------|------|
| 点击居中 | `MoveStageByPixelOffset(dx, dy)` | 否 | 用户点击图像某点,一次性把该点移到中心,不影响后续运动 |
| 坐标系补偿 | `ApplyPixelOffsetCompensation(dx, dy)` | 是 | 坐标匹配后记录补偿并修正到位,后续定点运动可叠加补偿 |
`MoveStageByPixelOffset` 保持原有行为不变,继续服务点击居中场景。
---
## 8. 边界与注意事项 | Edge Cases & Notes
| 情况 | 处理方式 |
|------|----------|
| 探测器像素尺寸未配置 | 换算失败,返回 `MotionResult.Fail`,不记录、不移动 |
| 当前放大倍率无效(NaN 或 ≤ 0) | 换算失败,返回 `MotionResult.Fail` |
| 补偿后目标越界 | 移动被 `Min/Max` 边界校验拒绝 |
| `SetStageCompensation` 传入 NaN/Infinity | 返回 `MotionResult.Fail`,补偿值不变 |
| 回零(HomeAll) | 补偿值**不变**,需显式 `ResetStageCompensation()` 清零 |
| 覆盖模式同位置连续修正 | 物理移动会重复叠加,应改用 `Accumulate` |
---
## 9. 相关代码位置 | Source Locations
| 内容 | 文件 |
|------|------|
| 补偿模式枚举 | `Abstractions/Enums/CompensationMode.cs` |
| 补偿变化事件 | `Abstractions/Events/StageCompensationChangedEvent.cs` |
| 接口定义 | `Services/IMotionControlService.cs`"坐标系补偿"区域) |
| 实现(含 `TryConvertPixelToStageMm``_compXmm/_compYmm``_compLock` | `Services/MotionControlService.cs`"坐标系补偿"区域) |
---
## 10. 关联文档 | Related Documents
| 文档 | 说明 |
|------|------|
| `MotionControl_Design.md` | 运动控制模块设计文档(第 14 节补偿设计) |
| `GUIDENCE.md` | 外部集成指南(第 11 节坐标系补偿) |
| `Calibration_README.md` | 像素-载物台映射子模块 |
| `GeometryWithDetectorSwing.md` | FOD/FDD/放大倍率几何计算模型 |
---
**最后更新 | Last Updated**: 2026-07-28