# 载物台坐标系补偿功能 | 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 { } ``` 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.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() .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