| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161 |
- using System;
- using System.Linq;
- using System.Threading;
- using AutoFocusTool.Devices;
- using AutoFocusTool.Serial;
- using AutoFocusTool.Imaging;
- using AutoFocusTool.Calib;
- using SerialCamera = AutoFocusTool.Camera.Camera;
- namespace AutoFocusTool
- {
- /// <summary>
- /// 真机冒烟测试:验证第二轮修复,不跑完整标定(不大幅驱动电机)。
- /// 用法:SmokeTest.exe [testHouse=1] 1号舱=空皿测试舱,9号舱=有皿(已标定)
- /// 验证项:
- /// 1) 设备扫描 + COM/相机映射
- /// 2) P0-1: CalibrationManager 对已标定舱返回JSON参数;未标定降级EEPROM
- /// 3) P0-2/3: 设两档曝光,确认"丢帧后第2帧"亮度跟随曝光(旧帧污染会导致不跟随)
- /// 4) 相机抓帧通路
- /// </summary>
- internal class SmokeTest
- {
- const int W = 2592, H = 1944;
- const string JsonPath = @"C:\claudeFile\TL\AutoFocusTool\calibration.json";
- static int pass = 0, fail = 0;
- static void Ok(string m) { Console.WriteLine($" [PASS] {m}"); pass++; }
- static void No(string m) { Console.WriteLine($" [FAIL] {m}"); fail++; }
- static void Info(string m) { Console.WriteLine($" · {m}"); }
- static void Main(string[] args)
- {
- Console.OutputEncoding = System.Text.Encoding.UTF8;
- int testHouse = args.Length > 0 && int.TryParse(args[0], out int t) ? t : 1;
- Console.WriteLine($"==== 真机冒烟测试 (测试舱={testHouse}, 空皿用1号舱) ====\n");
- // ── 1) 设备扫描 ──
- Console.WriteLine("【1】设备扫描 + 映射");
- var scanner = new DeviceScanner { Log = m => Info(m) };
- var houses = scanner.ScanAll();
- if (houses.Count == 0) { No("未发现任何舱室,硬件未连接?终止"); Done(); return; }
- Ok($"发现 {houses.Count} 个舱室: {string.Join(",", houses.Select(h => h.HouseSn))}");
- foreach (var h in houses)
- Info($"舱{h.HouseSn} @ {h.PortName} 相机#{h.CcdIndex} CCDSN={h.CcdSn} 有相机={h.HasCamera}");
- // ── 2) P0-1 业务闭环:CalibrationManager 参数来源 ──
- Console.WriteLine("\n【2】P0-1 标定闭环 (CalibrationManager)");
- TestCalibManager();
- // ── 3+4) 相机通路 + P0-2/3 丢帧曝光跟随 ──
- Console.WriteLine($"\n【3】相机通路 + P0-2/3 丢帧曝光跟随 (舱{testHouse})");
- var th = houses.FirstOrDefault(x => x.HouseSn == testHouse && x.HasCamera)
- ?? houses.FirstOrDefault(x => x.HasCamera);
- if (th == null) { No("无可用相机舱,跳过相机测试"); Done(); return; }
- if (th.HouseSn != testHouse) Info($"舱{testHouse}不可用,改用舱{th.HouseSn}");
- TestCameraExposure(th);
- Done();
- }
- static void Done()
- {
- Console.WriteLine($"\n==== 结果: {pass} 通过 / {fail} 失败 ====");
- Console.WriteLine(fail == 0 ? "SMOKE TEST PASS" : "SMOKE TEST HAS FAILURES");
- }
- // P0-1: 验证 CalibrationManager 优先用JSON、降级EEPROM
- static void TestCalibManager()
- {
- if (!System.IO.File.Exists(JsonPath)) { No($"calibration.json 不存在: {JsonPath}"); return; }
- var file = CalibrationFile.Load(JsonPath);
- Info($"JSON: tlSn={file.TlSn} date={file.Date} 舱数={file.Houses.Count}");
- var mgr = new CalibrationManager();
- // 对JSON里第一个舱的well1: 应判定为已标定并返回JSON的水平脉冲(不读EEPROM,故传null motor安全?)
- // GetWellParams 合格时不触发EEPROM读取,可传null验证纯JSON路径
- var hc = file.Houses.FirstOrDefault();
- if (hc == null) { No("JSON无舱室数据"); return; }
- var w1 = hc.Wells.FirstOrDefault(w => w.CircleFound && w.PeakRatio > 1.2);
- if (w1 == null) { Info("JSON中无合格well(都PeakRatio<=1.2),闭环逻辑会全部降级EEPROM——符合空皿预期"); }
- // 验证 HasValidCalibration 判据
- if (w1 != null)
- {
- bool valid = mgr.HasValidCalibration(hc.House, w1.Well);
- if (valid) Ok($"舱{hc.House} well{w1.Well} 判定为已标定(峰比{w1.PeakRatio:F2}) → 转well将用JSON参数 H={w1.HorizontalPulse} Z={w1.FocusZ} 曝光={w1.Exposure}");
- else No($"舱{hc.House} well{w1.Well} 应合格却判不合格");
- // 合格路径不读EEPROM,传null motor验证不抛异常
- try {
- var (hp, z, exp) = mgr.GetWellParams(hc.House, w1.Well, null);
- if (hp == w1.HorizontalPulse && z == w1.FocusZ && exp == w1.Exposure)
- Ok($"GetWellParams 返回JSON参数(未触碰EEPROM): H={hp} Z={z} 曝光={exp}");
- else No($"GetWellParams 返回值与JSON不符: H={hp}/{w1.HorizontalPulse} Z={z}/{w1.FocusZ} exp={exp}/{w1.Exposure}");
- } catch (Exception ex) { No($"合格well不应读EEPROM却抛异常: {ex.Message}"); }
- }
- // 未标定舱(如99): 应降级,exp返回-1
- bool invalid = mgr.HasValidCalibration(99, 1);
- if (!invalid) Ok("未标定舱99 判定为不合格 → 将降级EEPROM (符合预期)");
- else No("未标定舱99 不应判合格");
- }
- // P0-2/3: 设两档曝光,丢帧后第2帧亮度应跟随曝光
- static void TestCameraExposure(HouseDevice h)
- {
- HouseMotor motor = null; SerialCamera cam = null;
- try
- {
- motor = new HouseMotor(h.PortName) { Log = _ => { }, MotorDelayMs = 1500 };
- if (!motor.Open()) { No($"{h.PortName} 打开失败"); return; }
- cam = new SerialCamera(h.CcdIndex, W, H, 120);
- if (cam.Init() != 0) { No("相机初始化失败"); return; }
- cam.SetOpMode(0);
- motor.OpenLED();
- Thread.Sleep(300);
- // 抓帧通路
- if (cam.GrabRgb() != 0) { No("抓帧失败"); return; }
- var b0 = cam.GetSourceBuffer();
- if (b0 == null || b0.Length != W * H * 3) { No($"帧尺寸异常: {b0?.Length}"); return; }
- Ok($"抓帧成功,帧大小 {b0.Length} 字节 ({W}x{H}x3)");
- // P0-2/3 核心: 模拟引擎"设曝光→丢帧→第2帧"序列,验证亮度跟随
- double lowMean = SetExpGrabStable(cam, 30);
- double highMean = SetExpGrabStable(cam, 150);
- Info($"曝光30 → 亮度均值={lowMean:F1}");
- Info($"曝光150 → 亮度均值={highMean:F1}");
- if (highMean > lowMean + 3)
- Ok($"丢帧后第2帧亮度随曝光上升({lowMean:F1}→{highMean:F1}),无旧帧污染");
- else
- No($"曝光升高但亮度未跟随({lowMean:F1}→{highMean:F1}),可能旧帧污染或光路异常");
- motor.CloseLED();
- }
- catch (Exception ex) { No($"相机测试异常: {ex.Message}"); }
- finally
- {
- try { cam?.UnInit(); cam?.Dispose(); } catch { }
- try { motor?.Close(); motor?.Dispose(); } catch { }
- }
- }
- // 复刻 CalibrationEngine 曝光二分里的稳定抓帧: 设曝光→等待→丢第1帧→用第2帧
- static double SetExpGrabStable(SerialCamera cam, int e)
- {
- cam.SetExposure(e);
- Thread.Sleep(Math.Max(200, e / 5));
- cam.GrabRgb(); // 丢弃第1帧(可能旧曝光)
- cam.GrabRgb(); // 第2帧
- return Mean(cam.GetSourceBuffer());
- }
- static double Mean(byte[] bgr)
- {
- long sum = 0; int n = 0;
- for (int i = 0; i + 2 < bgr.Length; i += 300) { sum += bgr[i] + bgr[i + 1] + bgr[i + 2]; n += 3; }
- return n > 0 ? sum / (double)n : 0;
- }
- }
- }
|