using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Media; using AutoFocusTool.Calib; using AutoFocusTool.Imaging; namespace AutoFocusTool { // well选择器 + 手动转well + 一键全自动初始化(勾选well + 独立窗口) public partial class MainWindow { private CancellationTokenSource _calibCts; private const int WELL_COUNT = 16; private readonly List _wellChecks = new List(); // 业务闭环:转well时优先应用 calibration.json 的标定结果(P0-1)。 private readonly CalibrationManager _calibMgr = new CalibrationManager(); /// 连接成功后填充:手动well下拉(1-16) + 16个勾选框。 private void InitWellSelectors() { // 手动控制的well下拉 CmbWell.Items.Clear(); for (int i = 1; i <= WELL_COUNT; i++) CmbWell.Items.Add($"well {i}"); CmbWell.SelectedIndex = 0; // 自动标定的16个勾选框(默认全选) if (_wellChecks.Count == 0) { WellCheckPanel.Children.Clear(); for (int i = 1; i <= WELL_COUNT; i++) { var cb = new CheckBox { Content = i.ToString(), IsChecked = true, Foreground = Brushes.White, Width = 46, Margin = new Thickness(2) }; _wellChecks.Add(cb); WellCheckPanel.Children.Add(cb); } } } // ── 需求1:手动转到选定well ── // P0-1 业务闭环:优先应用 calibration.json 的标定参数(水平/Z/曝光), // 标定合格(检到圆且峰比>1.2)才用JSON,否则降级到EEPROM。 private void BtnGoWell_Click(object sender, RoutedEventArgs e) { LogAction($"点击 转到well={CmbWell.SelectedIndex + 1}"); if (_motor == null) { Log("未连接。"); return; } if (_busy) { Log("设备忙。"); return; } int well = CmbWell.SelectedIndex + 1; int houseSn = _current?.HouseSn ?? 0; int motorDelay = ParseInt(TxtMotorDelay.Text, 1500); Task.Run(() => { _busy = true; try { _motor.MotorDelayMs = motorDelay; bool useCalib = _calibMgr.HasValidCalibration(houseSn, well); var (hpos, zpos, exp) = _calibMgr.GetWellParams(houseSn, well, _motor); if (hpos < 0) { Log($"读well{well}位置失败"); return; } _motor.HorizontalMoveTo(hpos); if (zpos > 0) _motor.VerticalMoveTo(zpos); if (useCalib && exp > 0 && _camera != null) _camera.SetExposure(exp); _curWell = well; string src = useCalib ? "标定结果" : "EEPROM(未标定/不合格)"; string expInfo = (useCalib && exp > 0) ? $" 曝光={exp}" : ""; Log($"已转到 well{well}[{src}]: 水平={hpos} Z={zpos}{expInfo}"); Dispatcher.Invoke(() => { TxtWellInfo.Text = $"well{well}[{src}]: 水平={hpos} Z={zpos}{expInfo}"; UpdateParamDisplay(); }); } catch (Exception ex) { Log($"转well异常: {ex.Message}"); } finally { _busy = false; } RefreshPositions(); }); } // ── 需求2:全选/全不选 ── private void BtnWellAll_Click(object sender, RoutedEventArgs e) { LogAction("点击 全选well"); foreach (var cb in _wellChecks) cb.IsChecked = true; } private void BtnWellNone_Click(object sender, RoutedEventArgs e) { LogAction("点击 全不选well"); foreach (var cb in _wellChecks) cb.IsChecked = false; } // ── 需求2、3:一键全自动初始化(勾选well + 独立窗口显示)── private void BtnAutoInit_Click(object sender, RoutedEventArgs e) { LogAction("点击 一键全自动初始化"); if (_camera == null || _motor == null) { Log("需要先连接相机+串口。"); return; } if (_busy) { Log("设备忙,请稍候。"); return; } // 收集勾选的well var wells = new List(); for (int i = 0; i < _wellChecks.Count; i++) if (_wellChecks[i].IsChecked == true) wells.Add(i + 1); if (wells.Count == 0) { Log("请至少勾选一个well。"); return; } int motorDelay = ParseInt(TxtMotorDelay.Text, 1500); int houseSn = _current?.HouseSn ?? 0; string port = _current?.PortName ?? ""; int ccdIdx = _current?.CcdIndex ?? -1; string ccdSn = _current?.CcdSn ?? ""; // 弹出标定窗口 var win = new CalibWindow { Owner = this }; _calibCts = new CancellationTokenSource(); var token = _calibCts.Token; win.StopRequested += () => _calibCts?.Cancel(); win.Show(); win.SetProgress("开始自动标定", $"舱{houseSn},共 {wells.Count} 个well: {string.Join(",", wells)}"); Task.Run(() => { _busy = true; try { _motor.MotorDelayMs = motorDelay; _motor.OpenLED(); Thread.Sleep(200); var engine = new CalibrationEngine(_motor, _camera) { Log = Log, OnFrame = buf => { try { var bmp = ImageConverter.ToBitmapSource(buf, _camWidth, _camHeight); Dispatcher.BeginInvoke((Action)(() => { ImgPreview.Source = bmp; win.SetCurrentFrame(bmp); })); } catch { } }, OnStep = (msg, circle, exp) => { string circlePart = circle != null && circle.Found ? $"圆心偏移 Y:{circle.OffsetYPct:F0}% X:{circle.OffsetXPct:F0}% 完整:{circle.Complete}" : "未检出well圆"; string expPart = exp != null ? $" 曝光信息:{exp}" : ""; // P2: 不再拼 _lastZ/_lastH——它们仅在 RefreshPositions 时更新,标定过程中是陈旧值, // 会误导排查(日志会全程显示标定前的旧 Z/水平)。msg 本身已含当前步骤的真实命令 Z。 string param = $"{circlePart}{expPart}"; win.SetCurrentInfo(param, msg); AutoFocusTool.Logging.FileLogger.Data("CALIB", $"{msg} | {param}"); } }; var houseCalib = new HouseCalib { House = houseSn, Port = port, CcdIndex = ccdIdx, CcdSn = ccdSn }; string resultDir = @"C:\claudeFile\TL\AutoFocusTool\calib_result"; System.IO.Directory.CreateDirectory(resultDir); int done = 0; foreach (int well in wells) { if (token.IsCancellationRequested) { Log("标定已停止。"); break; } done++; win.SetProgress($"正在标定 well {well}", $"进度 {done}/{wells.Count}"); win.MarkActive(well); int hpos = _motor.ReadWellHorizontalPos(well); int zZero = _motor.ReadWellFocusZero(well); Log($"\n=== well{well} EEPROM: 水平={hpos} Z零点={zZero} ==="); if (hpos < 0) { Log($"well{well} 读位置失败,跳过"); continue; } if (zZero < 0) zZero = 0; var wc = engine.CalibrateWell(well, hpos, zZero); houseCalib.Wells.Add(wc); // 存终图 + 在窗口对应格显示 try { _camera.SetExposure(wc.Exposure); _motor.HorizontalMoveTo(wc.HorizontalPulse); _motor.VerticalMoveTo(wc.FocusZ); Thread.Sleep(150); _camera.GrabRgb(); var buf = _camera.GetSourceBuffer(); if (buf == null) { Log($" well{well} 存图跳过(缓冲为空)"); continue; } string imgPath = $"{resultDir}\\house{houseSn}_well{well}_标定后.bmp"; ImageConverter.SaveBmp(buf, _camWidth, _camHeight, imgPath); var bmp = ImageConverter.ToBitmapSource(buf, _camWidth, _camHeight); bool ok = Math.Abs(wc.CenterOffsetPct) < 12 && wc.CircleFound && wc.PeakRatio > 1.2; win.SetWellResult(well, bmp, $"well{well} 水平{wc.HorizontalPulse} Y偏{wc.CenterOffsetPct:F0}% 曝{wc.Exposure} Z{wc.FocusZ}", ok); Log($" well{well} 标定后画面已存: {imgPath}"); } catch (Exception ex) { Log($" 存图失败: {ex.Message}"); } } _motor.CloseLED(); // 存档 string path = @"C:\claudeFile\TL\AutoFocusTool\calibration.json"; var file = new CalibrationFile { TlSn = $"house{houseSn}", Date = DateTime.Now.ToString("yyyy-MM-dd HH:mm") }; file.Houses.Add(houseCalib); file.Save(path); _calibMgr.RefreshCache(); // P0-1: 重写JSON后刷新缓存,转well立即用新结果 Log($"=== 标定完成,已存 {path} ==="); win.SetProgress("标定完成 ✓", $"已标定 {houseCalib.Wells.Count} 个well,结果已保存"); } catch (Exception ex) { Log($"标定异常: {ex.Message}"); win.SetProgress("标定异常", ex.Message); } finally { _busy = false; RefreshPositions(); } }, token); } } }