using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Text; using System.Text.Json; namespace AutoFocusTool.Calib { /// 单个 well 的标定结果。 public class WellCalib { public int Well; public int HorizontalPulse; // 标定出的最居中水平位置(脉冲) public int Exposure; // 合适曝光(×100µs) public int FocusZ; // 最清晰层Z(脉冲) public double CenterOffsetPct; // 标定后圆心偏移(%),残留偏移 public bool CircleFound; public double PeakSharp; // 对焦峰值清晰度 public double PeakRatio; // max/mean,>1.2 视为有真实焦点 public string Note = ""; } /// 单个舱室的标定结果。 public class HouseCalib { public int House; public string Port; public int CcdIndex; public string CcdSn; public List Wells = new List(); } /// /// 整机标定档案 + JSON 读写(手写,不依赖第三方库)。 /// 日常使用直接读此档案的 HorizontalPulse/Exposure/FocusZ,不再逐项试。 /// 也是移植进主项目的交接数据。 /// public class CalibrationFile { public string TlSn = ""; public string Date = ""; public List Houses = new List(); public WellCalib Find(int house, int well) => Houses.Find(h => h.House == house)?.Wells.Find(w => w.Well == well); /// 从 JSON 文件加载标定档案。 public static CalibrationFile Load(string path) { string json = File.ReadAllText(path, Encoding.UTF8); // 关键:本类用 public 字段而非属性,System.Text.Json 默认忽略字段, // 必须开 IncludeFields,否则反序列化得到空对象(舱数0),标定闭环静默失效。 var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true, IncludeFields = true }; return JsonSerializer.Deserialize(json, options); } public void Save(string path) { var sb = new StringBuilder(); sb.Append("{\n"); sb.Append($" \"tlSn\": \"{Esc(TlSn)}\",\n"); sb.Append($" \"date\": \"{Esc(Date)}\",\n"); sb.Append(" \"houses\": [\n"); for (int i = 0; i < Houses.Count; i++) { var h = Houses[i]; sb.Append(" {\n"); sb.Append($" \"house\": {h.House}, \"port\": \"{Esc(h.Port)}\", \"ccdIndex\": {h.CcdIndex}, \"ccdSn\": \"{Esc(h.CcdSn)}\",\n"); sb.Append(" \"wells\": [\n"); for (int j = 0; j < h.Wells.Count; j++) { var w = h.Wells[j]; sb.Append(" {"); sb.Append($"\"well\": {w.Well}, \"horizontalPulse\": {w.HorizontalPulse}, \"exposure\": {w.Exposure}, \"focusZ\": {w.FocusZ}, "); sb.Append($"\"centerOffsetPct\": {F(w.CenterOffsetPct)}, \"circleFound\": {(w.CircleFound ? "true" : "false")}, "); sb.Append($"\"peakSharp\": {F(w.PeakSharp)}, \"peakRatio\": {F(w.PeakRatio)}, \"note\": \"{Esc(w.Note)}\""); sb.Append(j < h.Wells.Count - 1 ? "},\n" : "}\n"); } sb.Append(" ]\n"); sb.Append(i < Houses.Count - 1 ? " },\n" : " }\n"); } sb.Append(" ]\n}\n"); File.WriteAllText(path, sb.ToString(), Encoding.UTF8); } static string F(double d) => d.ToString("0.###", CultureInfo.InvariantCulture); static string Esc(string s) => (s ?? "").Replace("\\", "\\\\").Replace("\"", "\\\""); } }