CalibrationFile.cs 3.9 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
  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 AutoFocusTool.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. /// </summary>
  36. public class CalibrationFile
  37. {
  38. public string TlSn = "";
  39. public string Date = "";
  40. public List<HouseCalib> Houses = new List<HouseCalib>();
  41. public WellCalib Find(int house, int well)
  42. => Houses.Find(h => h.House == house)?.Wells.Find(w => w.Well == well);
  43. /// <summary>从 JSON 文件加载标定档案。</summary>
  44. public static CalibrationFile Load(string path)
  45. {
  46. string json = File.ReadAllText(path, Encoding.UTF8);
  47. // 关键:本类用 public 字段而非属性,System.Text.Json 默认忽略字段,
  48. // 必须开 IncludeFields,否则反序列化得到空对象(舱数0),标定闭环静默失效。
  49. var options = new JsonSerializerOptions
  50. {
  51. PropertyNameCaseInsensitive = true,
  52. IncludeFields = true
  53. };
  54. return JsonSerializer.Deserialize<CalibrationFile>(json, options);
  55. }
  56. public void Save(string path)
  57. {
  58. var sb = new StringBuilder();
  59. sb.Append("{\n");
  60. sb.Append($" \"tlSn\": \"{Esc(TlSn)}\",\n");
  61. sb.Append($" \"date\": \"{Esc(Date)}\",\n");
  62. sb.Append(" \"houses\": [\n");
  63. for (int i = 0; i < Houses.Count; i++)
  64. {
  65. var h = Houses[i];
  66. sb.Append(" {\n");
  67. sb.Append($" \"house\": {h.House}, \"port\": \"{Esc(h.Port)}\", \"ccdIndex\": {h.CcdIndex}, \"ccdSn\": \"{Esc(h.CcdSn)}\",\n");
  68. sb.Append(" \"wells\": [\n");
  69. for (int j = 0; j < h.Wells.Count; j++)
  70. {
  71. var w = h.Wells[j];
  72. sb.Append(" {");
  73. sb.Append($"\"well\": {w.Well}, \"horizontalPulse\": {w.HorizontalPulse}, \"exposure\": {w.Exposure}, \"focusZ\": {w.FocusZ}, ");
  74. sb.Append($"\"centerOffsetPct\": {F(w.CenterOffsetPct)}, \"circleFound\": {(w.CircleFound ? "true" : "false")}, ");
  75. sb.Append($"\"peakSharp\": {F(w.PeakSharp)}, \"peakRatio\": {F(w.PeakRatio)}, \"note\": \"{Esc(w.Note)}\"");
  76. sb.Append(j < h.Wells.Count - 1 ? "},\n" : "}\n");
  77. }
  78. sb.Append(" ]\n");
  79. sb.Append(i < Houses.Count - 1 ? " },\n" : " }\n");
  80. }
  81. sb.Append(" ]\n}\n");
  82. File.WriteAllText(path, sb.ToString(), Encoding.UTF8);
  83. }
  84. static string F(double d) => d.ToString("0.###", CultureInfo.InvariantCulture);
  85. static string Esc(string s) => (s ?? "").Replace("\\", "\\\\").Replace("\"", "\\\"");
  86. }
  87. }