MainWindow.Calib.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Threading;
  5. using System.Threading.Tasks;
  6. using System.Windows;
  7. using System.Windows.Controls;
  8. using System.Windows.Media;
  9. using AutoFocusTool.Calib;
  10. using AutoFocusTool.Imaging;
  11. namespace AutoFocusTool
  12. {
  13. // well选择器 + 手动转well + 一键全自动初始化(勾选well + 独立窗口)
  14. public partial class MainWindow
  15. {
  16. private CancellationTokenSource _calibCts;
  17. private const int WELL_COUNT = 16;
  18. private readonly List<CheckBox> _wellChecks = new List<CheckBox>();
  19. // 业务闭环:转well时优先应用 calibration.json 的标定结果(P0-1)。
  20. private readonly CalibrationManager _calibMgr = new CalibrationManager();
  21. /// <summary>连接成功后填充:手动well下拉(1-16) + 16个勾选框。</summary>
  22. private void InitWellSelectors()
  23. {
  24. // 手动控制的well下拉
  25. CmbWell.Items.Clear();
  26. for (int i = 1; i <= WELL_COUNT; i++) CmbWell.Items.Add($"well {i}");
  27. CmbWell.SelectedIndex = 0;
  28. // 自动标定的16个勾选框(默认全选)
  29. if (_wellChecks.Count == 0)
  30. {
  31. WellCheckPanel.Children.Clear();
  32. for (int i = 1; i <= WELL_COUNT; i++)
  33. {
  34. var cb = new CheckBox
  35. {
  36. Content = i.ToString(),
  37. IsChecked = true,
  38. Foreground = Brushes.White,
  39. Width = 46,
  40. Margin = new Thickness(2)
  41. };
  42. _wellChecks.Add(cb);
  43. WellCheckPanel.Children.Add(cb);
  44. }
  45. }
  46. }
  47. // ── 需求1:手动转到选定well ──
  48. // P0-1 业务闭环:优先应用 calibration.json 的标定参数(水平/Z/曝光),
  49. // 标定合格(检到圆且峰比>1.2)才用JSON,否则降级到EEPROM。
  50. private void BtnGoWell_Click(object sender, RoutedEventArgs e)
  51. {
  52. LogAction($"点击 转到well={CmbWell.SelectedIndex + 1}");
  53. if (_motor == null) { Log("未连接。"); return; }
  54. if (_busy) { Log("设备忙。"); return; }
  55. int well = CmbWell.SelectedIndex + 1;
  56. int houseSn = _current?.HouseSn ?? 0;
  57. int motorDelay = ParseInt(TxtMotorDelay.Text, 1500);
  58. Task.Run(() =>
  59. {
  60. _busy = true;
  61. try
  62. {
  63. _motor.MotorDelayMs = motorDelay;
  64. bool useCalib = _calibMgr.HasValidCalibration(houseSn, well);
  65. var (hpos, zpos, exp) = _calibMgr.GetWellParams(houseSn, well, _motor);
  66. if (hpos < 0) { Log($"读well{well}位置失败"); return; }
  67. _motor.HorizontalMoveTo(hpos);
  68. if (zpos > 0) _motor.VerticalMoveTo(zpos);
  69. if (useCalib && exp > 0 && _camera != null) _camera.SetExposure(exp);
  70. _curWell = well;
  71. string src = useCalib ? "标定结果" : "EEPROM(未标定/不合格)";
  72. string expInfo = (useCalib && exp > 0) ? $" 曝光={exp}" : "";
  73. Log($"已转到 well{well}[{src}]: 水平={hpos} Z={zpos}{expInfo}");
  74. Dispatcher.Invoke(() =>
  75. {
  76. TxtWellInfo.Text = $"well{well}[{src}]: 水平={hpos} Z={zpos}{expInfo}";
  77. UpdateParamDisplay();
  78. });
  79. }
  80. catch (Exception ex) { Log($"转well异常: {ex.Message}"); }
  81. finally { _busy = false; }
  82. RefreshPositions();
  83. });
  84. }
  85. // ── 需求2:全选/全不选 ──
  86. private void BtnWellAll_Click(object sender, RoutedEventArgs e)
  87. { LogAction("点击 全选well"); foreach (var cb in _wellChecks) cb.IsChecked = true; }
  88. private void BtnWellNone_Click(object sender, RoutedEventArgs e)
  89. { LogAction("点击 全不选well"); foreach (var cb in _wellChecks) cb.IsChecked = false; }
  90. // ── 需求2、3:一键全自动初始化(勾选well + 独立窗口显示)──
  91. private void BtnAutoInit_Click(object sender, RoutedEventArgs e)
  92. {
  93. LogAction("点击 一键全自动初始化");
  94. if (_camera == null || _motor == null) { Log("需要先连接相机+串口。"); return; }
  95. if (_busy) { Log("设备忙,请稍候。"); return; }
  96. // 收集勾选的well
  97. var wells = new List<int>();
  98. for (int i = 0; i < _wellChecks.Count; i++)
  99. if (_wellChecks[i].IsChecked == true) wells.Add(i + 1);
  100. if (wells.Count == 0) { Log("请至少勾选一个well。"); return; }
  101. int motorDelay = ParseInt(TxtMotorDelay.Text, 1500);
  102. int houseSn = _current?.HouseSn ?? 0;
  103. string port = _current?.PortName ?? "";
  104. int ccdIdx = _current?.CcdIndex ?? -1;
  105. string ccdSn = _current?.CcdSn ?? "";
  106. // 弹出标定窗口
  107. var win = new CalibWindow { Owner = this };
  108. _calibCts = new CancellationTokenSource();
  109. var token = _calibCts.Token;
  110. win.StopRequested += () => _calibCts?.Cancel();
  111. win.Show();
  112. win.SetProgress("开始自动标定", $"舱{houseSn},共 {wells.Count} 个well: {string.Join(",", wells)}");
  113. Task.Run(() =>
  114. {
  115. _busy = true;
  116. try
  117. {
  118. _motor.MotorDelayMs = motorDelay;
  119. _motor.OpenLED();
  120. Thread.Sleep(200);
  121. var engine = new CalibrationEngine(_motor, _camera)
  122. {
  123. Log = Log,
  124. OnFrame = buf =>
  125. {
  126. try
  127. {
  128. var bmp = ImageConverter.ToBitmapSource(buf, _camWidth, _camHeight);
  129. Dispatcher.BeginInvoke((Action)(() => { ImgPreview.Source = bmp; win.SetCurrentFrame(bmp); }));
  130. }
  131. catch { }
  132. },
  133. OnStep = (msg, circle, exp) =>
  134. {
  135. string circlePart = circle != null && circle.Found
  136. ? $"圆心偏移 Y:{circle.OffsetYPct:F0}% X:{circle.OffsetXPct:F0}% 完整:{circle.Complete}"
  137. : "未检出well圆";
  138. string expPart = exp != null ? $" 曝光信息:{exp}" : "";
  139. // P2: 不再拼 _lastZ/_lastH——它们仅在 RefreshPositions 时更新,标定过程中是陈旧值,
  140. // 会误导排查(日志会全程显示标定前的旧 Z/水平)。msg 本身已含当前步骤的真实命令 Z。
  141. string param = $"{circlePart}{expPart}";
  142. win.SetCurrentInfo(param, msg);
  143. AutoFocusTool.Logging.FileLogger.Data("CALIB", $"{msg} | {param}");
  144. }
  145. };
  146. var houseCalib = new HouseCalib { House = houseSn, Port = port, CcdIndex = ccdIdx, CcdSn = ccdSn };
  147. string resultDir = @"C:\claudeFile\TL\AutoFocusTool\calib_result";
  148. System.IO.Directory.CreateDirectory(resultDir);
  149. int done = 0;
  150. foreach (int well in wells)
  151. {
  152. if (token.IsCancellationRequested) { Log("标定已停止。"); break; }
  153. done++;
  154. win.SetProgress($"正在标定 well {well}", $"进度 {done}/{wells.Count}");
  155. win.MarkActive(well);
  156. int hpos = _motor.ReadWellHorizontalPos(well);
  157. int zZero = _motor.ReadWellFocusZero(well);
  158. Log($"\n=== well{well} EEPROM: 水平={hpos} Z零点={zZero} ===");
  159. if (hpos < 0) { Log($"well{well} 读位置失败,跳过"); continue; }
  160. if (zZero < 0) zZero = 0;
  161. var wc = engine.CalibrateWell(well, hpos, zZero);
  162. houseCalib.Wells.Add(wc);
  163. // 存终图 + 在窗口对应格显示
  164. try
  165. {
  166. _camera.SetExposure(wc.Exposure);
  167. _motor.HorizontalMoveTo(wc.HorizontalPulse);
  168. _motor.VerticalMoveTo(wc.FocusZ);
  169. Thread.Sleep(150);
  170. _camera.GrabRgb();
  171. var buf = _camera.GetSourceBuffer();
  172. if (buf == null) { Log($" well{well} 存图跳过(缓冲为空)"); continue; }
  173. string imgPath = $"{resultDir}\\house{houseSn}_well{well}_标定后.bmp";
  174. ImageConverter.SaveBmp(buf, _camWidth, _camHeight, imgPath);
  175. var bmp = ImageConverter.ToBitmapSource(buf, _camWidth, _camHeight);
  176. bool ok = Math.Abs(wc.CenterOffsetPct) < 12 && wc.CircleFound && wc.PeakRatio > 1.2;
  177. win.SetWellResult(well, bmp,
  178. $"well{well} 水平{wc.HorizontalPulse} Y偏{wc.CenterOffsetPct:F0}% 曝{wc.Exposure} Z{wc.FocusZ}", ok);
  179. Log($" well{well} 标定后画面已存: {imgPath}");
  180. }
  181. catch (Exception ex) { Log($" 存图失败: {ex.Message}"); }
  182. }
  183. _motor.CloseLED();
  184. // 存档
  185. string path = @"C:\claudeFile\TL\AutoFocusTool\calibration.json";
  186. var file = new CalibrationFile
  187. {
  188. TlSn = $"house{houseSn}",
  189. Date = DateTime.Now.ToString("yyyy-MM-dd HH:mm")
  190. };
  191. file.Houses.Add(houseCalib);
  192. file.Save(path);
  193. _calibMgr.RefreshCache(); // P0-1: 重写JSON后刷新缓存,转well立即用新结果
  194. Log($"=== 标定完成,已存 {path} ===");
  195. win.SetProgress("标定完成 ✓", $"已标定 {houseCalib.Wells.Count} 个well,结果已保存");
  196. }
  197. catch (Exception ex) { Log($"标定异常: {ex.Message}"); win.SetProgress("标定异常", ex.Message); }
  198. finally { _busy = false; RefreshPositions(); }
  199. }, token);
  200. }
  201. }
  202. }