MainWindow.Calib.cs 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  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. if (_motor == null) { Log("未连接。"); return; }
  53. if (_busy) { Log("设备忙。"); return; }
  54. int well = CmbWell.SelectedIndex + 1;
  55. int houseSn = _current?.HouseSn ?? 0;
  56. int motorDelay = ParseInt(TxtMotorDelay.Text, 1500);
  57. Task.Run(() =>
  58. {
  59. _busy = true;
  60. try
  61. {
  62. _motor.MotorDelayMs = motorDelay;
  63. bool useCalib = _calibMgr.HasValidCalibration(houseSn, well);
  64. var (hpos, zpos, exp) = _calibMgr.GetWellParams(houseSn, well, _motor);
  65. if (hpos < 0) { Log($"读well{well}位置失败"); return; }
  66. _motor.HorizontalMoveTo(hpos);
  67. if (zpos > 0) _motor.VerticalMoveTo(zpos);
  68. if (useCalib && exp > 0 && _camera != null) _camera.SetExposure(exp);
  69. _curWell = well;
  70. string src = useCalib ? "标定结果" : "EEPROM(未标定/不合格)";
  71. string expInfo = (useCalib && exp > 0) ? $" 曝光={exp}" : "";
  72. Log($"已转到 well{well}[{src}]: 水平={hpos} Z={zpos}{expInfo}");
  73. Dispatcher.Invoke(() =>
  74. {
  75. TxtWellInfo.Text = $"well{well}[{src}]: 水平={hpos} Z={zpos}{expInfo}";
  76. UpdateParamDisplay();
  77. });
  78. }
  79. catch (Exception ex) { Log($"转well异常: {ex.Message}"); }
  80. finally { _busy = false; }
  81. RefreshPositions();
  82. });
  83. }
  84. // ── 需求2:全选/全不选 ──
  85. private void BtnWellAll_Click(object sender, RoutedEventArgs e)
  86. { foreach (var cb in _wellChecks) cb.IsChecked = true; }
  87. private void BtnWellNone_Click(object sender, RoutedEventArgs e)
  88. { foreach (var cb in _wellChecks) cb.IsChecked = false; }
  89. // ── 需求2、3:一键全自动初始化(勾选well + 独立窗口显示)──
  90. private void BtnAutoInit_Click(object sender, RoutedEventArgs e)
  91. {
  92. if (_camera == null || _motor == null) { Log("需要先连接相机+串口。"); return; }
  93. if (_busy) { Log("设备忙,请稍候。"); return; }
  94. // 收集勾选的well
  95. var wells = new List<int>();
  96. for (int i = 0; i < _wellChecks.Count; i++)
  97. if (_wellChecks[i].IsChecked == true) wells.Add(i + 1);
  98. if (wells.Count == 0) { Log("请至少勾选一个well。"); return; }
  99. int motorDelay = ParseInt(TxtMotorDelay.Text, 1500);
  100. int houseSn = _current?.HouseSn ?? 0;
  101. string port = _current?.PortName ?? "";
  102. int ccdIdx = _current?.CcdIndex ?? -1;
  103. string ccdSn = _current?.CcdSn ?? "";
  104. // 弹出标定窗口
  105. var win = new CalibWindow { Owner = this };
  106. _calibCts = new CancellationTokenSource();
  107. var token = _calibCts.Token;
  108. win.StopRequested += () => _calibCts?.Cancel();
  109. win.Show();
  110. win.SetProgress("开始自动标定", $"舱{houseSn},共 {wells.Count} 个well: {string.Join(",", wells)}");
  111. Task.Run(() =>
  112. {
  113. _busy = true;
  114. try
  115. {
  116. _motor.MotorDelayMs = motorDelay;
  117. _motor.OpenLED();
  118. Thread.Sleep(200);
  119. var engine = new CalibrationEngine(_motor, _camera)
  120. {
  121. Log = Log,
  122. OnFrame = buf =>
  123. {
  124. try
  125. {
  126. var bmp = ImageConverter.ToBitmapSource(buf, _camWidth, _camHeight);
  127. Dispatcher.BeginInvoke((Action)(() => { ImgPreview.Source = bmp; win.SetCurrentFrame(bmp); }));
  128. }
  129. catch { }
  130. },
  131. OnStep = (msg, circle, exp) =>
  132. {
  133. string param = circle != null && circle.Found
  134. ? $"圆心偏移 Y:{circle.OffsetYPct:F0}% X:{circle.OffsetXPct:F0}% 完整:{circle.Complete}"
  135. : "未检出well圆";
  136. win.SetCurrentInfo(param, msg);
  137. }
  138. };
  139. var houseCalib = new HouseCalib { House = houseSn, Port = port, CcdIndex = ccdIdx, CcdSn = ccdSn };
  140. string resultDir = @"C:\claudeFile\TL\AutoFocusTool\calib_result";
  141. System.IO.Directory.CreateDirectory(resultDir);
  142. int done = 0;
  143. foreach (int well in wells)
  144. {
  145. if (token.IsCancellationRequested) { Log("标定已停止。"); break; }
  146. done++;
  147. win.SetProgress($"正在标定 well {well}", $"进度 {done}/{wells.Count}");
  148. win.MarkActive(well);
  149. int hpos = _motor.ReadWellHorizontalPos(well);
  150. int zZero = _motor.ReadWellFocusZero(well);
  151. Log($"\n=== well{well} EEPROM: 水平={hpos} Z零点={zZero} ===");
  152. if (hpos < 0) { Log($"well{well} 读位置失败,跳过"); continue; }
  153. if (zZero < 0) zZero = 0;
  154. var wc = engine.CalibrateWell(well, hpos, zZero);
  155. houseCalib.Wells.Add(wc);
  156. // 存终图 + 在窗口对应格显示
  157. try
  158. {
  159. _camera.SetExposure(wc.Exposure);
  160. _motor.HorizontalMoveTo(wc.HorizontalPulse);
  161. _motor.VerticalMoveTo(wc.FocusZ);
  162. Thread.Sleep(150);
  163. _camera.GrabRgb();
  164. var buf = _camera.GetSourceBuffer();
  165. string imgPath = $"{resultDir}\\house{houseSn}_well{well}_标定后.bmp";
  166. ImageConverter.SaveBmp(buf, _camWidth, _camHeight, imgPath);
  167. var bmp = ImageConverter.ToBitmapSource(buf, _camWidth, _camHeight);
  168. bool ok = Math.Abs(wc.CenterOffsetPct) < 12 && wc.CircleFound && wc.PeakRatio > 1.2;
  169. win.SetWellResult(well, bmp,
  170. $"well{well} Y偏{wc.CenterOffsetPct:F0}% 曝{wc.Exposure} Z{wc.FocusZ}", ok);
  171. Log($" well{well} 标定后画面已存: {imgPath}");
  172. }
  173. catch (Exception ex) { Log($" 存图失败: {ex.Message}"); }
  174. }
  175. _motor.CloseLED();
  176. // 存档
  177. string path = @"C:\claudeFile\TL\AutoFocusTool\calibration.json";
  178. var file = new CalibrationFile
  179. {
  180. TlSn = $"house{houseSn}",
  181. Date = DateTime.Now.ToString("yyyy-MM-dd HH:mm")
  182. };
  183. file.Houses.Add(houseCalib);
  184. file.Save(path);
  185. _calibMgr.RefreshCache(); // P0-1: 重写JSON后刷新缓存,转well立即用新结果
  186. Log($"=== 标定完成,已存 {path} ===");
  187. win.SetProgress("标定完成 ✓", $"已标定 {houseCalib.Wells.Count} 个well,结果已保存");
  188. }
  189. catch (Exception ex) { Log($"标定异常: {ex.Message}"); win.SetProgress("标定异常", ex.Message); }
  190. finally { _busy = false; RefreshPositions(); }
  191. }, token);
  192. }
  193. }
  194. }