添加 MX-PD-盘古 项目文件

将 MX-PD-盘古 - new 目录下的所有文件添加到主仓库
This commit is contained in:
Shi.Ji
2026-05-18 11:43:09 +08:00
parent 03632a379d
commit e31d3560bb
739 changed files with 99783 additions and 0 deletions

View File

@@ -0,0 +1,56 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SemiPoint = SemiconductorVisionAlgorithm.SemiParams.Point;
namespace MainShell.AlgorithmCalib.Service
{
public static class CalibFileService
{
public static void SaveCalibPointsFile(string path, List<SemiPoint> real, List<SemiPoint> ruler)
{
using (var sw = new StreamWriter(path))
{
sw.WriteLine("# realX, realY, rulerX, rulerY");
int count = Math.Max(real.Count, ruler.Count);
for (int i = 0; i < count; i++)
{
var r = i < real.Count ? real[i] : new SemiPoint(0, 0);
var u = i < ruler.Count ? ruler[i] : new SemiPoint(0, 0);
sw.WriteLine($"{r.X},{r.Y},{u.X},{u.Y}");
}
}
}
public static void ReadCalibPointsFile(string path, out List<SemiPoint> real, out List<SemiPoint> ruler)
{
real = new List<SemiPoint>();
ruler = new List<SemiPoint>();
if (!File.Exists(path))
return;
foreach (var line in File.ReadLines(path))
{
var l = line.Trim();
if (string.IsNullOrEmpty(l)) continue;
if (l.StartsWith("#")) continue;
var sp = l.Split(',');
if (sp.Length < 4) continue;
real.Add(new SemiPoint(
double.Parse(sp[0]),
double.Parse(sp[1])
));
ruler.Add(new SemiPoint(
double.Parse(sp[2]),
double.Parse(sp[3])
));
}
}
}
}

View File

@@ -0,0 +1,300 @@
using MainShell.AlgorithmCalib.Common;
using MainShell.AlgorithmCalib.Model;
using MainShell.Common;
using MainShell.Hardware;
using MainShell.Log;
using MainShell.Motion;
using SemiconductorVisionAlgorithm.SemiParams;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using SemiPoint = SemiconductorVisionAlgorithm.SemiParams.Point;
namespace MainShell.AlgorithmCalib.Service
{
/// <summary>
/// 融合对位处理器
/// 3步对位流程
/// 1. 抓Pad点框选ROI识别Pad坐标
/// 2. Die网格飞拍首点+参数 → 蛇形扫描获取x*y个Die点
/// 3. 对位验证选择一个Die点 → 与Pad点对位 → 输出结果)
/// </summary>
public class FusionAlignProcessor
{
private readonly FusionCalibMotionService _motionService;
private readonly HardwareManager _hardware;
private readonly FusionCalibModuleItem _moduleItem;
private readonly ApproachAlignmentService _approachAlignmentService;
// 飞拍参数
private double _flyScanStartX;
private double _flyScanStartY;
private int _flyScanXCount;
private int _flyScanYCount;
private double _flyScanStep;
public event EventHandler<CalibrationProgressEventArgs> ProgressChanged;
private readonly Func<Rectangle1> _roiRectProvider; // ROI 提供者委托
public FusionAlignProcessor(
FusionCalibMotionService motionService,
HardwareManager hardware,
FusionCalibModuleItem moduleItem,
Func<Rectangle1> roiRectProvider)
{
_motionService = motionService;
_hardware = hardware;
_moduleItem = moduleItem;
_approachAlignmentService = IoC.Get<ApproachAlignmentService>();
_roiRectProvider = roiRectProvider;
}
/// <summary>
/// 设置飞拍参数
/// </summary>
public void SetFlyScanParams(double startX, double startY, int xCount, int yCount, double step)
{
_flyScanStartX = startX;
_flyScanStartY = startY;
_flyScanXCount = xCount;
_flyScanYCount = yCount;
_flyScanStep = step;
}
/// <summary>
/// 步骤1抓Pad点
/// 框选ROI区域识别Pad目标点坐标pad运动系X1,Y2
/// </summary>
public async Task CapturePadPointAsync(CancellationToken cancellationToken)
{
await Task.Run(async () =>
{
ReportProgress("抓Pad点...", 1, 3);
bool success = await ApproachAtPointAsync(cancellationToken).ConfigureAwait(false);
if (!success)
{
throw new InvalidOperationException($"逼近对齐失败");
}
_moduleItem.PadRealX = _hardware.Axis_X1.State.ActualPos;
_moduleItem.PadRealY = _hardware.Axis_Y2.State.ActualPos;
"抓Pad点完成".LogInfo();
}, cancellationToken);
}
/// <summary>
/// 步骤2a抓Die首点
/// 框选ROI区域识别Die首点坐标die运动系X2,Y1
/// </summary>
public async Task CaptureDieFirstPointAsync(CancellationToken cancellationToken)
{
await Task.Run(async() =>
{
ReportProgress("抓Die首点...", 1, 3);
bool success = await ApproachAtPointAsync(cancellationToken).ConfigureAwait(false);
if (!success)
{
throw new InvalidOperationException($"逼近对齐失败");
}
_moduleItem.DieRealX = _hardware.Axis_X2.State.ActualPos;
_moduleItem.DieRealY = _hardware.Axis_Y1.State.ActualPos;
"抓Die首点完成".LogInfo();
}, cancellationToken);
}
/// <summary>
/// 步骤2bDie网格飞拍
/// 以首点为起点按step和count执行蛇形网格飞拍获取x*y个Die点
/// </summary>
public async Task ExecuteDieGridFlyScanAsync(CancellationToken cancellationToken)
{
await Task.Run(async() =>
{
var sw = Stopwatch.StartNew();
int totalPoints = _flyScanXCount * _flyScanYCount;
int captured = 0;
ReportProgress($"开始飞拍,网格{_flyScanXCount}×{_flyScanYCount},共{totalPoints}点...", 0, totalPoints);
// 清空之前的数据
_moduleItem.DieGridPoints = new List<Tuple<int, int, SemiPoint>>();
for (int row = 0; row < _flyScanYCount; row++)
{
// 蛇形扫描:偶数行从左到右,奇数行从右到左
for (int col = 0; col < _flyScanXCount; col++)
{
cancellationToken.ThrowIfCancellationRequested();
int actualCol = row % 2 == 0 ? col : (_flyScanXCount - 1 - col);
// 计算目标位置
double targetX2 = _flyScanStartX + actualCol * _flyScanStep;
double targetY1 = _flyScanStartY + row * _flyScanStep;
// 移动轴到目标位置
_motionService.MoveWsAvoidance(targetX2, targetY1);
// 等待运动完成
Thread.Sleep(200);
bool success = await ApproachAtPointAsync(cancellationToken).ConfigureAwait(false);
if (!success)
{
throw new InvalidOperationException($"逼近对齐失败");
}
// 读取当前位置
double currentX2 = _hardware.Axis_X2.State.ActualPos;
double currentY1 = _hardware.Axis_Y1.State.ActualPos;
SemiPoint point = new SemiPoint(currentX2, currentY1);
_moduleItem.DieGridPoints.Add(Tuple.Create(row, actualCol, point));
captured++;
ReportProgress($"飞拍中... ({captured}/{totalPoints})", captured, totalPoints);
}
}
sw.Stop();
$"Die网格飞拍完成共{captured}点,耗时{sw.ElapsedMilliseconds}ms".LogInfo();
}, cancellationToken);
}
/// <summary>
/// 步骤3执行对位
/// 用户选择网格中的一个Die点row, col与Pad点执行对位
/// 对位流程Die X对齐Pad → Pad Y对齐Die → X2补偿
/// </summary>
public async Task ExecuteAlignAsync(CancellationToken ct, int selectedRow, int selectedCol)
{
await Task.Run(() =>
{
ReportProgress("执行对位...", 1, 6);
// 从网格中找到选中的Die点
SemiPoint selectedDie = null;
foreach (var pt in _moduleItem.DieGridPoints)
{
if (pt.Item1 == selectedRow && pt.Item2 == selectedCol)
{
selectedDie = pt.Item3;
break;
}
}
if (selectedDie == null)
{
throw new Exception($"未找到Die点[{selectedRow},{selectedCol}]");
}
// 使用选中的Die点作为对位基准
_moduleItem.DieRealX = selectedDie.X;
_moduleItem.DieRealY = selectedDie.Y;
_moduleItem.PadRealX = _moduleItem.PadRulerX;
_moduleItem.PadRealY = _moduleItem.PadRulerY;
ReportProgress("Die X方向对齐Pad...", 2, 6);
// === 步骤3aDie X方向对齐Pad ===
// 查die融合表将Die的X方向对齐到Pad的X方向
// 移动X2,Y1 → Y1会产生少许偏移
//
// TODO: 接入JM1融合表查询
// SemiPoint dieReal = new SemiPoint(_moduleItem.DieRealX, _moduleItem.DieRealY);
// SemiPoint padReal = new SemiPoint(_moduleItem.PadRealX, _moduleItem.PadRealY);
// SemiJM1Manager.Instance.get_ws_pos_die(dieReal, padReal, out SemiPoint wsTarget);
//
// 暂时模拟使用Die坐标作为目标
double wsTargetX = _moduleItem.DieRealX;
double wsTargetY = _moduleItem.DieRealY;
_motionService.MoveWsAvoidance(wsTargetX, wsTargetY);
// 读取此时Y1位置已产生偏移
double currentY1 = _hardware.Axis_Y1.State.ActualPos;
ReportProgress("Pad Y方向对齐Die...", 3, 6);
// === 步骤3bPad Y方向对齐Die ===
// 根据当前Y1值查pad融合表计算X1,Y2目标位置
// 移动X1,Y2 → X1会产生少许偏移
//
// TODO: 接入pad融合表查询
// SemiPoint padTarget = FusionTableLookupPad(padReal, currentY1);
//
// 暂时模拟
double padTargetX = _moduleItem.PadRealX;
double padTargetY = _moduleItem.PadRealY;
_motionService.MovePhsAvoidance(padTargetX, padTargetY);
// 读取此时X1位置已产生偏移
double currentX1 = _hardware.Axis_X1.State.ActualPos;
ReportProgress("X2补偿...", 4, 6);
// === 步骤3cX2补偿 ===
// X2跟上X1的移动量
double x1Delta = currentX1 - _moduleItem.PadRulerX;
double x2Target = wsTargetX + x1Delta;
_motionService.SafeMove(_hardware.Axis_X2, x2Target);
// 记录结果
_moduleItem.AlignResultX = currentX1;
_moduleItem.AlignResultY = currentY1;
_moduleItem.AlignResultX2 = x2Target;
ReportProgress("对位完成", 5, 6);
$"对位完成: Die[{selectedRow},{selectedCol}] → Pad, X1={currentX1:F4}, Y1={currentY1:F4}, X2 ={ x2Target: F4}".LogInfo();
}, ct);
}
private void ReportProgress(string message, int current, int total)
{
ProgressChanged?.Invoke(this, new CalibrationProgressEventArgs
{
Message = message,
Current = current,
Total = total
});
}
/// <summary>
/// 逼近对齐
/// </summary>
private async Task<bool> ApproachAtPointAsync(CancellationToken cancellationToken)
{
Rectangle1 roiRect = _roiRectProvider?.Invoke();
var request = new ApproachAlignmentRequest(
axes: new[]
{
new ApproachAlignmentAxis(_hardware.Axis_X2.Name, 0.005),
new ApproachAlignmentAxis(_hardware.Axis_Y1.Name, 0.005)
},
camera: CameraType.TopPositionCamera)
{
MaxIterations = 3,
RecognitionTimeoutMilliseconds = 5000,
RecognitionParameters = new CenterRecognitionParameters()
{
Type = CenterRecognitionType.EdgeCircle,
rectangle = roiRect
}
};
var result = await _approachAlignmentService.ApproachAlignmentAsync(request, cancellationToken).ConfigureAwait(false);
return result.Succeeded;
}
}
}

View File

@@ -0,0 +1,60 @@
using MainShell.Hardware;
using MainShell.Motion;
using MaxwellFramework.Core.Attributes;
using MwFramework.Device;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MainShell.AlgorithmCalib.Service
{
[Singleton]
public class FusionCalibMotionService
{
private readonly HardwareManager _hardware;
private readonly SafeAxisMotion _safeAxisMotion;
public FusionCalibMotionService(HardwareManager hardware, SafeAxisMotion safeAxisMotion)
{
_hardware = hardware ?? throw new ArgumentNullException(nameof(hardware));
_safeAxisMotion = safeAxisMotion ?? throw new ArgumentNullException(nameof(safeAxisMotion));
}
public void SafeMove(IAxis axis, double position)
{
_safeAxisMotion.SafeMove(
MotionMoveRequest.ForAxis(GetRequiredAxis(axis, nameof(axis)), position));
}
public void MoveWsAvoidance(double xPosition, double yPosition)
{
_safeAxisMotion.SafeMove(
MotionMoveRequest.ForAxis(GetRequiredAxis(_hardware.Axis_X2, nameof(_hardware.Axis_X2)), xPosition),
MotionMoveRequest.ForAxis(GetRequiredAxis(_hardware.Axis_Y1, nameof(_hardware.Axis_Y1)), yPosition));
}
public void MovePhsAvoidance(double xPosition, double yPosition)
{
_safeAxisMotion.SafeMove(
MotionMoveRequest.ForAxis(GetRequiredAxis(_hardware.Axis_X1, nameof(_hardware.Axis_X1)), xPosition),
MotionMoveRequest.ForAxis(GetRequiredAxis(_hardware.Axis_Y2, nameof(_hardware.Axis_Y2)), yPosition));
}
public void RotatePos(double rotatePosition)
{
_safeAxisMotion.SafeMove(
MotionMoveRequest.ForAxis(GetRequiredAxis(_hardware.Axis_WS_R, nameof(_hardware.Axis_WS_R)), rotatePosition));
}
private static MwFramework.Device.IAxis GetRequiredAxis(MwFramework.Device.IAxis axis, string axisPropertyName)
{
if (axis == null)
{
throw new InvalidOperationException(string.Format("Axis '{0}' is not available.", axisPropertyName));
}
return axis;
}
}
}

View File

@@ -0,0 +1,229 @@
using MainShell.AlgorithmCalib.Common;
using MainShell.AlgorithmCalib.Model;
using MainShell.Hardware;
using MainShell.Log;
using SemiconductorVisionAlgorithm.SemiParams;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace MainShell.AlgorithmCalib.Service
{
/// <summary>
/// 融合标定后处理器Die/Pad共用
/// 通过 IsWaferCalib 内部区分行为
/// </summary>
public class FusionCalibPostProcessor : IAlgorithmCalibrationPostProcessor
{
private readonly FusionCalibMotionService _motionService;
private readonly HardwareManager _hardware;
private readonly FusionCalibModuleItem _moduleItem;
private readonly Func<Rectangle1> _roiRectProvider;
public event EventHandler<CalibrationProgressEventArgs> ProgressChanged;
private Point[,] _initialwsPos;
private Dictionary<int, Point[,]> _calibResultPointData = new Dictionary<int, Point[,]>();
private List<double> _calibPosList = new List<double>();
public string Name => "FusionCalib";
public FusionCalibPostProcessor(
FusionCalibMotionService motionService,
HardwareManager hardware,
FusionCalibModuleItem moduleItem,
Func<Rectangle1> roiRectProvider)
{
_motionService = motionService ?? throw new ArgumentNullException(nameof(motionService));
_hardware = hardware ?? throw new ArgumentNullException(nameof(hardware));
_moduleItem = moduleItem ?? throw new ArgumentNullException(nameof(moduleItem));
_roiRectProvider = roiRectProvider;
}
public async Task ExecuteAsync(AlgorithmCalibExecutionResult calibrationResult, CancellationToken cancellationToken)
{
$"{(_moduleItem.IsWaferCalib ? "Die" : "Pad")}融合标定开始。".LogInfo();
try
{
_calibResultPointData.Clear();
_calibPosList.Clear();
double waferStep = _moduleItem.CalibStep;
string path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, _moduleItem.ModelPath);
int totalSteps = _moduleItem.Count + 1;
ReportProgress("采集初始网格点...", 0, totalSteps);
// 1. 初始网格
_initialwsPos = await Task.Run(() =>
ApprochTable(waferStep,
_moduleItem.MoveAxisPos,
GetCurrentY1(),
_moduleItem.ApproachXPos,
_moduleItem.ApproachYPos,
0, path, cancellationToken),
cancellationToken);
// 2. 循环采集
for (int i = 0; i < _moduleItem.Count; i++)
{
cancellationToken.ThrowIfCancellationRequested();
ReportProgress($"采集第 {i + 1}/{_moduleItem.Count} 组...", i + 1, totalSteps);
double x1, y1, x2, y2;
CalculatePositions(i, out x1, out y1, out x2, out y2);
Point[,] points = await Task.Run(() =>
ApprochTable(waferStep, x1, y1, x2, y2, i + 1, path, cancellationToken),
cancellationToken);
_calibResultPointData[i] = points;
_calibPosList.Add(GetCurrentMoveAxisPos());
}
// 3. 调用平台算法
ReportProgress("计算融合矩阵...", totalSteps, totalSteps);
await Task.Run(() =>
{
string filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "CalibData");
if (_moduleItem.IsWaferCalib)
{
SemiJM1Vision.SemiJM1Manager.Instance.cal_wafer_fusion_die(
_calibResultPointData, _calibPosList, _initialwsPos,
_moduleItem.CalibStep, waferStep, _moduleItem.CalibCount);
}
else
{
SemiJM1Vision.SemiJM1Manager.Instance.cal_wafer_fusion_pad(
_moduleItem.Index, _calibResultPointData, _calibPosList, _initialwsPos,
_moduleItem.CalibStep, waferStep, _moduleItem.CalibCount);
SemiJM1Vision.SemiJM1Manager.Instance.set_center(new Point(0, 0));
}
SemiJM1Vision.SemiJM1Manager.Instance.Save(filePath);
}, cancellationToken);
$"{(_moduleItem.IsWaferCalib ? "Die" : "Pad")}融合标定完成。".LogInfo();
}
catch (OperationCanceledException)
{
"融合标定已取消。".LogInfo();
throw;
}
catch (Exception ex)
{
$"融合标定失败:{ex.Message}".LogSysError();
throw;
}
}
public async Task VerifyAsync(CancellationToken cancellationToken)
{
// TODO: 验证流程
await Task.CompletedTask;
}
#region Die/Pad分支
private void CalculatePositions(int stepIndex, out double x1, out double y1, out double x2, out double y2)
{
if (_moduleItem.IsWaferCalib)
{
// Die: 移动X1逼近X2+Y1
x1 = _moduleItem.CameraAxisPos + _moduleItem.Step * stepIndex;
y1 = _moduleItem.StartY;
x2 = _moduleItem.StartX + _moduleItem.Step * stepIndex;
y2 = _hardware.Axis_Y2.State.ActualPos;
}
else
{
// Pad: 移动Y1逼近X1+Y2
x1 = _moduleItem.StartX;
y1 = _moduleItem.CameraAxisPos + _moduleItem.Step * stepIndex;
x2 = _hardware.Axis_X2.State.ActualPos;
y2 = _moduleItem.StartY + _moduleItem.Step * stepIndex;
}
}
private double GetCurrentY1()
{
return _hardware.Axis_Y1.State.ActualPos;
}
private double GetCurrentMoveAxisPos()
{
return _moduleItem.IsWaferCalib
? _hardware.Axis_X1.State.ActualPos
: _hardware.Axis_Y1.State.ActualPos;
}
#endregion
#region
private Point[,] ApprochTable(double calibStep, double x1, double y1, double x2, double y2,
int count, string path, CancellationToken ct)
{
int calibCount = _moduleItem.CalibCount;
Point[,] pointArray = new Point[calibCount, calibCount];
// 先移到起始位置
if (_moduleItem.IsWaferCalib)
_motionService.MoveWsAvoidance(x2, y1);
else
_motionService.MovePhsAvoidance(x1, y2);
for (int y = 0; y < calibCount; y++)
{
double Y1 = y * calibStep + y1;
double Y2 = y * calibStep + y2;
for (int x = 0; x < calibCount; x++)
{
ct.ThrowIfCancellationRequested();
int index = y % 2 == 0 ? x : calibCount - 1 - x;
double X2 = index * calibStep + x2;
double X1 = index * calibStep + x1;
if (_moduleItem.IsWaferCalib)
{
_motionService.MoveWsAvoidance(X2, Y1);
// TODO: 调用逼近识别
pointArray[index, y] = new Point(
_hardware.Axis_X2.State.ActualPos,
_hardware.Axis_Y1.State.ActualPos);
}
else
{
_motionService.MovePhsAvoidance(X1, Y2);
// TODO: 调用逼近识别
pointArray[index, y] = new Point(
_hardware.Axis_X1.State.ActualPos,
_hardware.Axis_Y2.State.ActualPos);
}
}
}
return pointArray;
}
#endregion
private void ReportProgress(string message, int current, int total)
{
ProgressChanged?.Invoke(this, new CalibrationProgressEventArgs
{
Message = message,
Current = current,
Total = total
});
}
}
}

View File

@@ -0,0 +1,58 @@
using SemiconductorVisionAlgorithm.SemiParams;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MainShell.AlgorithmCalib.Service
{
public static class MotionCalibFileService
{
public static void SaveCalibPointsFile(string path, List<Point> real, List<Point> ruler)
{
using (var sw = new StreamWriter(path))
{
sw.WriteLine("# realX, realY, rulerX, rulerY");
int count = Math.Max(real.Count, ruler.Count);
for (int i = 0; i < count; i++)
{
var r = i < real.Count ? real[i] : new Point(0, 0);
var u = i < ruler.Count ? ruler[i] : new Point(0, 0);
sw.WriteLine($"{r.X},{r.Y},{u.X},{u.Y}");
}
}
}
public static void ReadCalibPointsFile(string path, out List<Point> real, out List<Point> ruler)
{
real = new List<Point>();
ruler = new List<Point>();
if (!File.Exists(path))
return;
foreach (var line in File.ReadLines(path))
{
var l = line.Trim();
if (string.IsNullOrEmpty(l)) continue;
if (l.StartsWith("#")) continue;
var sp = l.Split(',');
if (sp.Length < 4) continue;
real.Add(new Point(
double.Parse(sp[0]),
double.Parse(sp[1])
));
ruler.Add(new Point(
double.Parse(sp[2]),
double.Parse(sp[3])
));
}
}
}
}

View File

@@ -0,0 +1,52 @@
using SemiconductorVisionAlgorithm.SemiParams;
using System;
using System.Collections.Generic;
using System.IO;
namespace MainShell.AlgorithmCalib.Service
{
/// <summary>
/// 旋转表标定文件服务
/// </summary>
public static class RotateMatixFormCalibFileService
{
/// <summary>
/// 保存验证点数据
/// </summary>
public static void SaveVerifyPointsFile(string path, List<Point> cal, List<Point> real)
{
using (var sw = new StreamWriter(path))
{
int count = cal.Count;
sw.WriteLine("Cal_X,Cal_Y,Real_X,Real_Y");
for (int i = 0; i < count; i++)
{
sw.WriteLine($"{cal[i].X},{cal[i].Y},{real[i].X},{real[i].Y}");
}
}
}
/// <summary>
/// 保存标定网格数据(用于调试)
/// </summary>
public static void SaveCalibrationGridData(string path, List<Point[,]> calibData,List<double> rotatePosList)
{
using (var sw = new StreamWriter(path))
{
sw.WriteLine("RotatePos,GridY,GridX,PointX,PointY");
for (int step = 0; step < calibData.Count; step++)
{
var grid = calibData[step];
int n = grid.GetLength(0);
for (int y = 0; y < n; y++)
{
for (int x = 0; x < n; x++)
{
sw.WriteLine($"{rotatePosList[step]:F4},{y},{x},{grid[y,x].X:F6},{grid[y, x].Y:F6}");
}
}
}
}
}
}
}

View File

@@ -0,0 +1,134 @@
using MainShell.Hardware;
using MainShell.Log;
using MainShell.Motion;
using MaxwellFramework.Core.Attributes;
using MwFramework.Device;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace MainShell.AlgorithmCalib.Service
{
/// <summary>
/// 旋转表标定运动服务
/// 对标 OriginCalibrationMotionService
/// </summary>
[Singleton]
public class RotateMatixFormCalibMotionService
{
private const int PositionSettleDelayMilliseconds = 200;
private readonly HardwareManager _hardware;
private readonly SafeAxisMotion _safeAxisMotion;
public RotateMatixFormCalibMotionService(HardwareManager hardware, SafeAxisMotion safeAxisMotion)
{
_hardware = hardware ?? throw new ArgumentNullException(nameof(hardware));
_safeAxisMotion = safeAxisMotion ?? throw new ArgumentNullException(nameof(safeAxisMotion));
}
#region
public void SafeMove(IAxis axis, double position)
{
_safeAxisMotion.SafeMove(
MotionMoveRequest.ForAxis(GetRequiredAxis(axis, nameof(axis)), position));
}
public async Task SafeMoveAsync(IAxis axis, double position, CancellationToken cancellationToken = default)
{
var result = await _safeAxisMotion.SafeMoveAsync(
cancellationToken,
MotionMoveRequest.ForAxis(GetRequiredAxis(axis, nameof(axis)),position)).ConfigureAwait(false);
result.EnsureSuccess();
}
public void MoveWsAvoidance(double xPosition, double yPosition)
{
_safeAxisMotion.SafeMove(
MotionMoveRequest.ForAxis(GetRequiredAxis(_hardware.Axis_X2,nameof(_hardware.Axis_X2)), xPosition),
MotionMoveRequest.ForAxis(GetRequiredAxis(_hardware.Axis_Y1,nameof(_hardware.Axis_Y1)), yPosition));
}
public async Task MoveWsAvoidanceAsync(double xPosition, double yPosition,CancellationToken cancellationToken = default)
{
var result = await _safeAxisMotion.SafeMoveAsync(
cancellationToken,
MotionMoveRequest.ForAxis(GetRequiredAxis(_hardware.Axis_X2,nameof(_hardware.Axis_X2)), xPosition),
MotionMoveRequest.ForAxis(GetRequiredAxis(_hardware.Axis_Y1,nameof(_hardware.Axis_Y1)), yPosition)).ConfigureAwait(false);
result.EnsureSuccess();
}
public void RotatePos(double rotatePosition)
{
_safeAxisMotion.SafeMove(
MotionMoveRequest.ForAxis(GetRequiredAxis(_hardware.Axis_WS_R,nameof(_hardware.Axis_WS_R)), rotatePosition));
}
public async Task RotatePosAsync(double rotatePosition, CancellationToken cancellationToken = default)
{
var result = await _safeAxisMotion.SafeMoveAsync(
cancellationToken,
MotionMoveRequest.ForAxis(GetRequiredAxis(_hardware.Axis_WS_R,nameof(_hardware.Axis_WS_R)), rotatePosition)).ConfigureAwait(false);
result.EnsureSuccess();
}
#endregion
#region OriginCalibrationMotionService
/// <summary>
/// 批量移动避让轴
/// </summary>
public async Task MoveAvoidanceAxesAsync(CancellationToken cancellationToken = default)
{
// 移动 X1 到避让位
if (_hardware.Axis_PHS_X1 != null)
{
string.Format("旋转表标定移动避让轴 X1 到位置。").LogInfo();
await SafeMoveAsync(_hardware.Axis_PHS_X1, 0,cancellationToken).ConfigureAwait(false);
}
}
/// <summary>
/// 等待轴稳定后采集实际位置
/// </summary>
public async Task<Dictionary<string, double>> CollectActualPositionsAsync(
IEnumerable<string> axisNames,
CancellationToken cancellationToken = default)
{
string.Format("旋转表标定等待 {0} ms 以稳定轴反馈。",PositionSettleDelayMilliseconds).LogInfo();
await Task.Delay(PositionSettleDelayMilliseconds,cancellationToken).ConfigureAwait(false);
var positions = new Dictionary<string, double>(StringComparer.Ordinal);
foreach (var axisName in axisNames)
{
if (string.IsNullOrWhiteSpace(axisName)) continue;
var axis = _hardware.GetAxisByName(axisName);
if (axis == null) continue;
positions[axisName] = GetActualPosition(axis);
string.Format("旋转表标定已采集轴'{0}'的实际位置:{1:F3}。", axisName,positions[axisName]).LogInfo();
}
return positions;
}
#endregion
private static double GetActualPosition(IAxis axis)
{
return axis.State != null ? axis.State.ActualPos : axis.GetPositionImmediate();
}
private static IAxis GetRequiredAxis(IAxis axis, string axisPropertyName)
{
if (axis == null)
throw new InvalidOperationException(string.Format("Axis '{0}' is not available.",axisPropertyName));
return axis;
}
}
}

View File

@@ -0,0 +1,598 @@
using JM1Vision;
using MainShell.AlgorithmCalib.Common;
using MainShell.AlgorithmCalib.Model;
using MainShell.Common;
using MainShell.Hardware;
using MainShell.Log;
using MainShell.Motion;
using MainShell.PageCalib.OriginCalib.Service;
using SemiconductorVisionAlgorithm.SemiCalib;
using SemiconductorVisionAlgorithm.SemiParams;
using SemiJM1Vision;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Point = SemiconductorVisionAlgorithm.SemiParams.Point;
namespace MainShell.AlgorithmCalib.Service
{
/// <summary>
/// 旋转表标定后处理器
/// 对标原 RotateFormCalibTask 的逻辑
/// </summary>
public class RotateMatixFormCalibPostProcessor : IAlgorithmCalibrationPostProcessor
{
private readonly RotateMatixFormCalibMotionService _motionService;
private readonly HardwareManager _hardware;
private readonly RotateMatixFormCalibModuleItem _moduleItem;
private readonly ApproachAlignmentService _approachAlignmentService;
private readonly Func<Rectangle1> _roiRectProvider; // ROI 提供者委托
public event EventHandler<CalibrationProgressEventArgs> ProgressChanged;
// 运行时数据
private Point[,] _initialwsPos;
private List<Point[,]> _calibWaferData = new List<Point[,]>();
private List<double> _rotatePosList = new List<double>();
private double _initialwsRotate;
public string Name => "RotateFormCalib";
public RotateMatixFormCalibPostProcessor(
RotateMatixFormCalibMotionService motionService,
HardwareManager hardware,
RotateMatixFormCalibModuleItem moduleItem,
ApproachAlignmentService approachAlignmentService,
Func<Rectangle1> roiRectProvider)
{
_motionService = motionService ?? throw new ArgumentNullException(nameof(motionService));
_hardware = hardware ?? throw new ArgumentNullException(nameof(hardware));
_moduleItem = moduleItem ?? throw new ArgumentNullException(nameof(moduleItem));
_approachAlignmentService = approachAlignmentService ?? throw new ArgumentNullException(nameof(approachAlignmentService));
_roiRectProvider = roiRectProvider;
}
/// <summary>
/// 标定主流程
/// 对标原 RotateFormCalibTask.RotateCalib()
/// </summary>
public async Task ExecuteAsync(AlgorithmCalibExecutionResult calibrationResult, CancellationToken cancellationToken)
{
"旋转表标定开始。".LogInfo();
try
{
// 清空历史数据
_calibWaferData.Clear();
_rotatePosList.Clear();
double waferStep = _moduleItem.WaferStep;
int rowCol = _moduleItem.RowCol;
// 1. 移动到起始位置,采集初始网格点
ReportProgress("采集初始网格点...", 0, 1);
_initialwsPos = await GetInitPointsAsync(
waferStep,
_moduleItem.X1AvoidancePosition,
_moduleItem.StartY,
_moduleItem.StartX,
cancellationToken).ConfigureAwait(false);
_initialwsRotate = _hardware.Axis_WS_R.State.ActualPos;
string.Format("旋转表标定:初始旋转位置 = {0:F4}。", _initialwsRotate).LogInfo();
// 2. 计算总旋转步数
double rotateStep = _moduleItem.RotateStep;
double negLimit = 0;
double posLimit = 0;
_hardware.Axis_WS_R.GetSoftMel(ref negLimit);
_hardware.Axis_WS_R.GetSoftPel(ref posLimit);
// 如果限位为0使用默认范围
if (negLimit == 0 && posLimit == 0)
{
negLimit = -180;
posLimit = 180;
"旋转表标定限位为0使用默认范围 [-180, 180]。".LogInfo();
}
double startAngle = negLimit;
double range = posLimit - startAngle;
int totalCount = (int)(range / rotateStep) + 1;
string.Format("旋转表标定:旋转步数 = {0},范围 = [{1:F4}, {2:F4}]。", totalCount, negLimit, posLimit).LogInfo();
// 初始化数据容器
for (int i = 0; i < totalCount; i++)
{
_rotatePosList.Add(0);
_calibWaferData.Add(new Point[rowCol, rowCol]);
}
// 3. 旋转轴先转到负限位
ReportProgress("旋转轴移动到负限位...", 0, totalCount);
await _motionService.RotatePosAsync(negLimit, cancellationToken).ConfigureAwait(false);
// 4. 从负限位开始,逐步旋转到正限位,每步采集网格
for (int stepIndex = 0; stepIndex < totalCount; stepIndex++)
{
double targetRotate = startAngle + rotateStep * stepIndex;
if (targetRotate > posLimit)
break;
cancellationToken.ThrowIfCancellationRequested();
ReportProgress($"旋转到 {targetRotate:F2}°", stepIndex, totalCount);
// 旋转到目标角度
await _motionService.RotatePosAsync(targetRotate, cancellationToken).ConfigureAwait(false);
_rotatePosList[stepIndex] = _hardware.Axis_WS_R.State.ActualPos;
// 计算旋转后的拍照位置
var rotateOffset = targetRotate - _initialwsRotate;
var resultPts = new Point[rowCol, rowCol];
SemiJM1Manager.Instance.RotatePts("", _initialwsPos, rotateOffset, out resultPts);
ReportProgress($"采集网格点 ({stepIndex + 1}/{totalCount})", stepIndex + 1, totalCount);
var resultPoints = await GetApproachCenterPointsAsync(resultPts, stepIndex, cancellationToken).ConfigureAwait(false);
_calibWaferData[stepIndex] = resultPoints;
}
// 5. 融合计算
ReportProgress("融合计算...", totalCount, totalCount);
SemiJM1Manager.Instance.MakeRotateTable(
_calibWaferData, _rotatePosList, _moduleItem.RotateStep);
// 6. 保存标定数据
ReportProgress("保存标定数据...", totalCount, totalCount);
string filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "CalibData");
Directory.CreateDirectory(filePath);
JM1Manager.Instance.Save(filePath);
string.Format("旋转表标定:标定数据已保存到 {0}。", filePath).LogInfo();
// 7. 保存调试数据
string debugPath = Path.Combine(filePath, "RotateFormCalibDebug.csv");
RotateMatixFormCalibFileService.SaveCalibrationGridData(debugPath, _calibWaferData, _rotatePosList);
ReportProgress("标定完成", totalCount, totalCount);
"旋转表标定完成。".LogInfo();
}
catch (OperationCanceledException)
{
"旋转表标定已取消。".LogInfo();
throw;
}
catch (Exception ex)
{
string.Format("旋转表标定失败:{0}", ex.Message).LogSysError();
throw;
}
}
/// <summary>
/// 验证流程
/// 对标原 RotateFormCalibTask.RotateCalibVerify()
/// </summary>
public async Task VerifyAsync(CancellationToken cancellationToken)
{
"旋转表验证开始。".LogInfo();
try
{
double verifyWaferStep = _moduleItem.VerifyWaferStep;
double initWsRotate = _hardware.Axis_WS_R.State.ActualPos;
double rotateOffset = _moduleItem.VerifyRotateRange;
int totalSteps = _moduleItem.VerifyRowCol * _moduleItem.VerifyRowCol + 2;
// 1. 获取验证初始点
ReportProgress("获取验证初始点...", 0, totalSteps);
var initVerifyPts = await GetInitListPointsAsync(
verifyWaferStep,
_moduleItem.VerifyStartX,
_moduleItem.VerifyStartY,
cancellationToken).ConfigureAwait(false);
// 2. 旋转轴转到目标位置
double targetRotate = initWsRotate + rotateOffset;
ReportProgress("旋转到目标角度...", 1, totalSteps);
await _motionService.RotatePosAsync(targetRotate, cancellationToken).ConfigureAwait(false);
// 3. 调用算法接口计算预期位置
ReportProgress("计算预期位置...", 2, totalSteps);
var calPts = new List<Point>();
SemiJM1Manager.Instance.GetRotateDstPos(initVerifyPts, initWsRotate, rotateOffset, out calPts);
// 4. 逐个移动并采集实际位置
ReportProgress("逐个采集实际位置...", 2, totalSteps);
var approachPts = await MoveAndGetPtsAsync(calPts, cancellationToken).ConfigureAwait(false);
// 5. 保存验证数据
ReportProgress("保存验证数据...", totalSteps, totalSteps);
string filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "RotateFormVerify.csv");
RotateMatixFormCalibFileService.SaveVerifyPointsFile(filePath, calPts, approachPts);
ReportProgress("验证完成", totalSteps, totalSteps);
"旋转表验证完成。".LogInfo();
}
catch (OperationCanceledException)
{
"旋转表验证已取消。".LogInfo();
throw;
}
catch (Exception ex)
{
string.Format("旋转表验证失败:{0}", ex.Message).LogSysError();
throw;
}
}
#region
///// <summary>
///// 蛇形扫描网格,返回 Point[RowCol, RowCol]
///// 对标原 RotateFormCalibTask.GetInitPoints()
///// </summary>
//private async Task<Point[,]> GetInitPointsAsync(
// double waferStep, double x1, double y1, double x2,
// CancellationToken cancellationToken)
//{
// int n = _moduleItem.RowCol;
// var pointArray = new Point[n, n];
// // 移动 X1 到避让位
// await _motionService.SafeMoveAsync(_hardware.Axis_PHS_X1, x1, cancellationToken).ConfigureAwait(false);
// for (int y = 0; y < n; y++)
// {
// for (int x = 0; x < n; x++)
// {
// int index = y % 2 == 0 ? x : n - 1 - x;
// double waferX = index * waferStep + x2;
// double waferY = y * waferStep + y1;
// cancellationToken.ThrowIfCancellationRequested();
// // 粗移到目标位置
// await _motionService.MoveWsAvoidanceAsync(waferX, waferY, cancellationToken).ConfigureAwait(false);
// // 逼近对齐
// bool success = await ApproachAtPointAsync(cancellationToken).ConfigureAwait(false);
// if (!success)
// {
// throw new InvalidOperationException(string.Format("逼近对齐失败 at ({0:F4}, {0:F4})。", waferX, waferY));
// }
// // 采集光栅尺位置
// var rulerX2 = _hardware.Axis_X2.State.ActualPos;
// var rulerY1 = _hardware.Axis_Y1.State.ActualPos;
// pointArray[y, index] = new Point(rulerX2, rulerY1);
// string.Format("初始网格点 [{0},{1}] = ({2:F4}, {3:F4})。", y, index, rulerX2,
// rulerY1).LogInfo();
// }
// }
// return pointArray;
//}
///// <summary>
///// 根据旋转后的拍照位置,蛇形逐个对齐
///// 对标原 RotateFormCalibTask.GetApproachCenterPoints()
///// </summary>
//private async Task<Point[,]> GetApproachCenterPointsAsync(
// Point[,] cameraPoints, int stepIndex,
// CancellationToken cancellationToken)
//{
// int n = _moduleItem.RowCol;
// var resultArray = new Point[n, n];
// for (int y = 0; y < n; y++)
// {
// for (int x = 0; x < n; x++)
// {
// int index = y % 2 == 0 ? x : n - 1 - x;
// cancellationToken.ThrowIfCancellationRequested();
// double px = cameraPoints[y, index].X;
// double py = cameraPoints[y, index].Y;
// // 粗移到目标位置
// await _motionService.MoveWsAvoidanceAsync(px, py, cancellationToken).ConfigureAwait(false);
// // 逼近对齐
// bool success = await ApproachAtPointAsync(cancellationToken).ConfigureAwait(false);
// if (!success)
// {
// throw new InvalidOperationException(string.Format("逼近对齐失败 at ({0:F4}, {1:F4})。", px, py));
// }
// // 采集光栅尺位置
// var rulerX2 = _hardware.Axis_X2.State.ActualPos;
// var rulerY1 = _hardware.Axis_Y1.State.ActualPos;
// resultArray[y, index] = new Point(rulerX2, rulerY1);
// }
// }
// return resultArray;
//}
///// <summary>
///// 蛇形扫描网格,返回 List&lt;Point&gt;
///// 对标原 RotateFormCalibTask.GetInitListPoints()
///// </summary>
//private async Task<List<Point>> GetInitListPointsAsync(
// double waferStep, double x2, double y1,
// CancellationToken cancellationToken)
//{
// int n = _moduleItem.VerifyRowCol;
// // 移动 X1 到避让位
// await _motionService.SafeMoveAsync(_hardware.Axis_PHS_X1, _moduleItem.X1AvoidancePosition, cancellationToken).ConfigureAwait(false);
// var pointArray = new List<Point>();
// for (int y = 0; y < n; y++)
// {
// for (int x = 0; x < n; x++)
// {
// int index = y % 2 == 0 ? x : n - 1 - x;
// double waferX = index * waferStep + x2;
// double waferY = y * waferStep + y1;
// cancellationToken.ThrowIfCancellationRequested();
// // 粗移到目标位置
// await _motionService.MoveWsAvoidanceAsync(waferX, waferY, cancellationToken).ConfigureAwait(false);
// // 逼近对齐
// bool success = await ApproachAtPointAsync(cancellationToken).ConfigureAwait(false);
// if (!success)
// {
// throw new InvalidOperationException(string.Format("逼近对齐失败 at ({0:F4},{1:F4})。", waferX, waferY));
// }
// // 采集光栅尺位置
// var rulerX2 = _hardware.Axis_X2.State.ActualPos;
// var rulerY1 = _hardware.Axis_Y1.State.ActualPos;
// pointArray.Add(new Point(rulerX2, rulerY1));
// }
// }
// return pointArray;
//}
///// <summary>
///// 逐个移动到目标点并采集实际位置
///// 对标原 RotateFormCalibTask.MoveAndGetPts()
///// </summary>
//private async Task<List<Point>> MoveAndGetPtsAsync(
// List<Point> targetPts,
// CancellationToken cancellationToken)
//{
// var pointArray = new List<Point>();
// foreach (var pt in targetPts)
// {
// cancellationToken.ThrowIfCancellationRequested();
// // 粗移到目标位置
// await _motionService.MoveWsAvoidanceAsync(pt.X, pt.Y,
//cancellationToken).ConfigureAwait(false);
// // 逼近对齐
// bool success = await ApproachAtPointAsync(cancellationToken).ConfigureAwait(false);
// if (!success)
// {
// throw new InvalidOperationException(string.Format("逼近对齐失败 at ({0:F4},{1:F4})。", pt.X, pt.Y));
// }
// // 采集光栅尺位置
// var rulerX2 = _hardware.Axis_X2.State.ActualPos;
// var rulerY1 = _hardware.Axis_Y1.State.ActualPos;
// pointArray.Add(new Point(rulerX2, rulerY1));
// }
// return pointArray;
//}
/// <summary>
/// 移动逼近公共方法
/// </summary>
/// <param name="targetX"></param>
/// <param name="targetY"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
/// <exception cref="InvalidOperationException"></exception>
private async Task<Point> MoveApproachAndCollectAsync(
double targetX, double targetY, CancellationToken cancellationToken)
{
await _motionService.MoveWsAvoidanceAsync(targetX, targetY,
cancellationToken).ConfigureAwait(false);
bool success = await ApproachAtPointAsync(cancellationToken).ConfigureAwait(false);
if (!success)
{
throw new InvalidOperationException($"逼近对齐失败 at ({targetX:F4}, {targetY:F4})。");
}
var rulerX2 = _hardware.Axis_X2.State.ActualPos;
var rulerY1 = _hardware.Axis_Y1.State.ActualPos;
return new Point(rulerX2, rulerY1);
}
/// <summary>
/// 获取标定初始点组
/// </summary>
/// <param name="waferStep"></param>
/// <param name="x1"></param>
/// <param name="y1"></param>
/// <param name="x2"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
private async Task<Point[,]> GetInitPointsAsync(
double waferStep, double x1, double y1, double x2,
CancellationToken cancellationToken)
{
int n = _moduleItem.RowCol;
var pointArray = new Point[n, n];
await _motionService.SafeMoveAsync(_hardware.Axis_PHS_X1, x1,
cancellationToken).ConfigureAwait(false);
for (int y = 0; y < n; y++)
{
for (int x = 0; x < n; x++)
{
int index = y % 2 == 0 ? x : n - 1 - x;
double waferX = index * waferStep + x2;
double waferY = y * waferStep + y1;
cancellationToken.ThrowIfCancellationRequested();
pointArray[y, index] = await MoveApproachAndCollectAsync(
waferX, waferY, cancellationToken).ConfigureAwait(false);
string.Format("初始网格点 [{0},{1}] = ({2:F4}, {3:F4})。",
y, index, pointArray[y, index].X, pointArray[y, index].Y).LogInfo();
}
}
return pointArray;
}
/// <summary>
/// 标定流程内获取点
/// </summary>
/// <param name="cameraPoints"></param>
/// <param name="stepIndex"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
private async Task<Point[,]> GetApproachCenterPointsAsync(
Point[,] cameraPoints, int stepIndex,
CancellationToken cancellationToken)
{
int n = _moduleItem.RowCol;
var resultArray = new Point[n, n];
for (int y = 0; y < n; y++)
{
for (int x = 0; x < n; x++)
{
int index = y % 2 == 0 ? x : n - 1 - x;
cancellationToken.ThrowIfCancellationRequested();
resultArray[y, index] = await MoveApproachAndCollectAsync(
cameraPoints[y, index].X, cameraPoints[y, index].Y,
cancellationToken).ConfigureAwait(false);
}
}
return resultArray;
}
/// <summary>
/// 验证流程获取初始点
/// </summary>
/// <param name="waferStep"></param>
/// <param name="x2"></param>
/// <param name="y1"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
private async Task<List<Point>> GetInitListPointsAsync(
double waferStep, double x2, double y1,
CancellationToken cancellationToken)
{
int n = _moduleItem.VerifyRowCol;
await _motionService.SafeMoveAsync(_hardware.Axis_PHS_X1, _moduleItem.X1AvoidancePosition,
cancellationToken).ConfigureAwait(false);
var pointArray = new List<Point>();
for (int y = 0; y < n; y++)
{
for (int x = 0; x < n; x++)
{
int index = y % 2 == 0 ? x : n - 1 - x;
double waferX = index * waferStep + x2;
double waferY = y * waferStep + y1;
cancellationToken.ThrowIfCancellationRequested();
pointArray.Add(await MoveApproachAndCollectAsync(
waferX, waferY, cancellationToken).ConfigureAwait(false));
}
}
return pointArray;
}
/// <summary>
/// 验证流程获取旋转后验证点
/// </summary>
/// <param name="targetPts"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
private async Task<List<Point>> MoveAndGetPtsAsync(
List<Point> targetPts,
CancellationToken cancellationToken)
{
var pointArray = new List<Point>();
foreach (var pt in targetPts)
{
cancellationToken.ThrowIfCancellationRequested();
pointArray.Add(await MoveApproachAndCollectAsync(
pt.X, pt.Y, cancellationToken).ConfigureAwait(false));
}
return pointArray;
}
/// <summary>
/// 逼近对齐
/// </summary>
private async Task<bool> ApproachAtPointAsync(CancellationToken cancellationToken)
{
Rectangle1 roiRect = _roiRectProvider?.Invoke();
var request = new ApproachAlignmentRequest(
axes: new[]
{
new ApproachAlignmentAxis("X2轴", _moduleItem.AlignTolerance),
new ApproachAlignmentAxis("Y1轴", _moduleItem.AlignTolerance)
},
camera: CameraType.TopPositionCamera)
{
MaxIterations = 3,
RecognitionTimeoutMilliseconds = 5000,
RecognitionParameters = new CenterRecognitionParameters()
{
Type = CenterRecognitionType.EdgeCircle,
rectangle = roiRect
}
};
var result = await _approachAlignmentService.ApproachAlignmentAsync(request, cancellationToken).ConfigureAwait(false);
return result.Succeeded;
}
private void ReportProgress(string message, int current, int total)
{
ProgressChanged?.Invoke(this, new CalibrationProgressEventArgs
{
Message = message,
Current = current,
Total = total
});
}
#endregion
}
}