| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697 |
- using System;
- using System.Collections.Generic;
- using System.Globalization;
- using System.IO;
- using System.Text;
- using System.Text.Json;
- namespace IvfTl.AutoFocus.Calib
- {
- /// <summary>单个 well 的标定结果。</summary>
- 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 = "";
- }
- /// <summary>单个舱室的标定结果。</summary>
- public class HouseCalib
- {
- public int House;
- public string Port;
- public int CcdIndex;
- public string CcdSn;
- public List<WellCalib> Wells = new List<WellCalib>();
- }
- /// <summary>
- /// 整机标定档案 + JSON 读写(手写,不依赖第三方库)。
- /// 日常使用直接读此档案的 HorizontalPulse/Exposure/FocusZ,不再逐项试。
- /// 也是移植进主项目的交接数据。
- /// 【移植说明 M2-01】逐字搬自 autofocustool/Calib/CalibrationFile.cs,仅改命名空间。
- /// ⚠ Load 的 IncludeFields=true 必须保留:本类用 public 字段而非属性,缺此选项
- /// System.Text.Json 反序列化得空对象、标定闭环静默失效(03 §4)。M2-04 据此扩展库镜像。
- /// </summary>
- public class CalibrationFile
- {
- public string TlSn = "";
- public string Date = "";
- public List<HouseCalib> Houses = new List<HouseCalib>();
- public WellCalib Find(int house, int well)
- => Houses.Find(h => h.House == house)?.Wells.Find(w => w.Well == well);
- /// <summary>从 JSON 文件加载标定档案。</summary>
- 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<CalibrationFile>(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("\"", "\\\"");
- }
- }
|