CalibrationFile.cs 4.3 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Globalization;
  4. using System.IO;
  5. using System.Text;
  6. using System.Text.Json;
  7. namespace IvfTl.AutoFocus.Calib
  8. {
  9. /// <summary>单个 well 的标定结果。</summary>
  10. public class WellCalib
  11. {
  12. public int Well;
  13. public int HorizontalPulse; // 标定出的最居中水平位置(脉冲)
  14. public int Exposure; // 合适曝光(×100µs)
  15. public int FocusZ; // 最清晰层Z(脉冲)
  16. public double CenterOffsetPct; // 标定后圆心偏移(%),残留偏移
  17. public bool CircleFound;
  18. public double PeakSharp; // 对焦峰值清晰度
  19. public double PeakRatio; // max/mean,>1.2 视为有真实焦点
  20. public string Note = "";
  21. }
  22. /// <summary>单个舱室的标定结果。</summary>
  23. public class HouseCalib
  24. {
  25. public int House;
  26. public string Port;
  27. public int CcdIndex;
  28. public string CcdSn;
  29. public List<WellCalib> Wells = new List<WellCalib>();
  30. }
  31. /// <summary>
  32. /// 整机标定档案 + JSON 读写(手写,不依赖第三方库)。
  33. /// 日常使用直接读此档案的 HorizontalPulse/Exposure/FocusZ,不再逐项试。
  34. /// 也是移植进主项目的交接数据。
  35. /// 【移植说明 M2-01】逐字搬自 autofocustool/Calib/CalibrationFile.cs,仅改命名空间。
  36. /// ⚠ Load 的 IncludeFields=true 必须保留:本类用 public 字段而非属性,缺此选项
  37. /// System.Text.Json 反序列化得空对象、标定闭环静默失效(03 §4)。M2-04 据此扩展库镜像。
  38. /// </summary>
  39. public class CalibrationFile
  40. {
  41. public string TlSn = "";
  42. public string Date = "";
  43. public List<HouseCalib> Houses = new List<HouseCalib>();
  44. public WellCalib Find(int house, int well)
  45. => Houses.Find(h => h.House == house)?.Wells.Find(w => w.Well == well);
  46. /// <summary>从 JSON 文件加载标定档案。</summary>
  47. public static CalibrationFile Load(string path)
  48. {
  49. string json = File.ReadAllText(path, Encoding.UTF8);
  50. // 关键:本类用 public 字段而非属性,System.Text.Json 默认忽略字段,
  51. // 必须开 IncludeFields,否则反序列化得到空对象(舱数0),标定闭环静默失效。
  52. var options = new JsonSerializerOptions
  53. {
  54. PropertyNameCaseInsensitive = true,
  55. IncludeFields = true
  56. };
  57. return JsonSerializer.Deserialize<CalibrationFile>(json, options);
  58. }
  59. public void Save(string path)
  60. {
  61. var sb = new StringBuilder();
  62. sb.Append("{\n");
  63. sb.Append($" \"tlSn\": \"{Esc(TlSn)}\",\n");
  64. sb.Append($" \"date\": \"{Esc(Date)}\",\n");
  65. sb.Append(" \"houses\": [\n");
  66. for (int i = 0; i < Houses.Count; i++)
  67. {
  68. var h = Houses[i];
  69. sb.Append(" {\n");
  70. sb.Append($" \"house\": {h.House}, \"port\": \"{Esc(h.Port)}\", \"ccdIndex\": {h.CcdIndex}, \"ccdSn\": \"{Esc(h.CcdSn)}\",\n");
  71. sb.Append(" \"wells\": [\n");
  72. for (int j = 0; j < h.Wells.Count; j++)
  73. {
  74. var w = h.Wells[j];
  75. sb.Append(" {");
  76. sb.Append($"\"well\": {w.Well}, \"horizontalPulse\": {w.HorizontalPulse}, \"exposure\": {w.Exposure}, \"focusZ\": {w.FocusZ}, ");
  77. sb.Append($"\"centerOffsetPct\": {F(w.CenterOffsetPct)}, \"circleFound\": {(w.CircleFound ? "true" : "false")}, ");
  78. sb.Append($"\"peakSharp\": {F(w.PeakSharp)}, \"peakRatio\": {F(w.PeakRatio)}, \"note\": \"{Esc(w.Note)}\"");
  79. sb.Append(j < h.Wells.Count - 1 ? "},\n" : "}\n");
  80. }
  81. sb.Append(" ]\n");
  82. sb.Append(i < Houses.Count - 1 ? " },\n" : " }\n");
  83. }
  84. sb.Append(" ]\n}\n");
  85. File.WriteAllText(path, sb.ToString(), Encoding.UTF8);
  86. }
  87. static string F(double d) => d.ToString("0.###", CultureInfo.InvariantCulture);
  88. static string Esc(string s) => (s ?? "").Replace("\\", "\\\\").Replace("\"", "\\\"");
  89. }
  90. }