using Modbus.Device; using Microsoft.Win32; using System; using System.Collections.Generic; using System.IO; using System.Net.Sockets; using System.Text; using System.Windows; using 头罩视野.Services.Data; public static class ModbusHelper { // 全局唯一连接,所有页面共用 public static TcpClient TcpClient => ModbusResourceManager.Instance.TcpClient; // 统一连接方法(全项目只调用一次) public static bool Connect(string ip, int port = 502) { return ModbusResourceManager.Instance.Init(ip, port); } // 判断是否连接成功 public static bool IsConnected => TcpClient != null && TcpClient.Connected; public static ushort ValidThreshold { get; private set; } // /// 公共保存方法(用户自选文件夹) /// /// 你的数据集合 /// 默认文件名 public static void SaveToCsv(List dataList, string defaultFileName) { if (dataList == null || dataList.Count == 0) { MessageBox.Show("无数据可保存!"); return; } // 选择文件夹 var folderDialog = new OpenFolderDialog { Title = "请选择保存路径" }; if (folderDialog.ShowDialog() != true) return; string folderPath = folderDialog.FolderName; string filePath = Path.Combine(folderPath, defaultFileName); // 写入 CSV using (var sw = new StreamWriter(filePath, false, Encoding.UTF8)) { var firstRow = (IDictionary)dataList[0]; sw.WriteLine(string.Join(",", firstRow.Keys)); foreach (var item in dataList) { var dict = (IDictionary)item; sw.WriteLine(string.Join(",", dict.Values)); } } MessageBox.Show("保存成功!\n" + filePath); } // 剔除异常值,用相邻数据插值 private static List RemoveOutliers(List data) { for (int i = 1; i < data.Count - 1; i++) { if (Math.Abs(data[i] - data[i - 1]) > 30 && Math.Abs(data[i] - data[i + 1]) > 30) { data[i] = (ushort)((data[i - 1] + data[i + 1]) / 2); } } return data; } //// 过滤无效信号 private const int ValidSignalThreshold = 10; private static void FilterInvalidSignals(List data) { for (int i = 0; i < data.Count; i++) { if (data[i] < ValidSignalThreshold) data[i] = 0; } } /// /// 计算单眼视野面积 /// /// 20组数据,每组72个通道 /// 有效亮度阈值(如80) /// 标准视野面积(如120) /// 计算好的面积 /// public static double CalculateEyeArea(List groupData, double threshold, double standardArea) { double[] avg = new double[72]; for (int c = 0; c < 72; c++) { double sum = 0; foreach (var g in groupData) sum += g[c]; avg[c] = sum / groupData.Count; } int valid = avg.Count(v => v >= threshold); return (valid / 72.0) * standardArea; } //计算单眼面积调用的方法 //double leftArea = CalculateEyeArea( // leftEye20Groups, // 左眼20组数据 // 80, // 阈值 // 120 // 标准面积 //); //double rightArea = .CalculateEyeArea( // rightEye20Groups, // 右眼20组数据 // 80, // 阈值 // 120 // 标准面积 //); //计算双目视野面积 /// /// 计算双目视野面积(左右眼同时可见) /// public static double CalcBinocularArea( List leftGroups, List rightGroups, double threshold, double standardArea) { // 1. 左眼平均数据 double[] leftAvg = new double[72]; for (int i = 0; i < 72; i++) { double sum = 0; foreach (var g in leftGroups) sum += g[i]; leftAvg[i] = sum / leftGroups.Count; } // 2. 右眼平均数据 double[] rightAvg = new double[72]; for (int i = 0; i < 72; i++) { double sum = 0; foreach (var g in rightGroups) sum += g[i]; rightAvg[i] = sum / rightGroups.Count; } // 3. 双目同时有效点数(左右都亮才算) int biValid = 0; for (int i = 0; i < 72; i++) { if (leftAvg[i] >= threshold && rightAvg[i] >= threshold) biValid++; } // 4. 双目视野面积 return (biValid / 72.0) * standardArea; } //调用公式 // 你从Modbus拿到的20组数据 //List left20Groups = ...; //List right20Groups = ...; //double threshold = 80; //double standardArea = 120; //// 左眼 //double left = VisionCalculator.CalcEyeArea(left20Groups, threshold, standardArea); //// 右眼 //double right = VisionCalculator.CalcEyeArea(right20Groups, threshold, standardArea); //// 双目视野面积 //double binocular = VisionCalculator.CalcBinocularArea(left20Groups, right20Groups, threshold, standardArea); //// 总视野面积 //double total = left + right - binocular; //下方视野角度 /// /// GB2890-2022 计算 单眼下方视野角度 /// eyeData:单眼72路平均数据数组 /// threshold:有效亮度阈值 /// public static double CalcLowerAngle(double[] eyeData, double threshold = 10) { // 总72点 每点5° int totalPoint = 72; double perAngle = 5; // 国标:最下方起始点位(第54号开始为正下方) int startDownIndex = 54; int validCount = 0; // 从最下方向上 连续检测有效点 for (int i = 0; i < 36; i++) { int idx = (startDownIndex + i) % totalPoint; if (eyeData[idx] >= threshold) { validCount++; } else { // 断开直接停止 break; } } // 下方视野角度 = 有效点数 × 单步角度 return validCount * perAngle; } /// /// 计算单眼72点通道平均值数组 /// /// 多组采样数据集合 /// 72点通道平均值数组 private static double[] GetEyeAvgArray(List eyeGroups) { if (eyeGroups == null || eyeGroups.Count == 0) return new double[72]; // 无数据时返回全0数组 double[] avg = new double[72]; for (int i = 0; i < 72; i++) { double sum = 0; foreach (var group in eyeGroups) { sum += group[i]; } avg[i] = sum / eyeGroups.Count; } return avg; } //下方视野计算方法 ////1. 先拿到左右眼 72点平均数组 //double[] leftAvg = GetLeftEyeAvgArray(); //double[] rightAvg = GetRightEyeAvgArray(); ////2. 分别算下方角度 //double leftLowerAngle = CalcLowerAngle(leftAvg, 10); //double rightLowerAngle = CalcLowerAngle(rightAvg, 10); ////3. 最终报告取值(国标取双眼较小值) //double finalLowerAngle = Math.Min(leftLowerAngle, rightLowerAngle); //bool lowerAngleOk = finalLowerAngle >= 35; //空白视野面积计算 //空白视野面积 = 标准视野总面积 − 实测总视野面积 //总视野面积 = 左眼面积 + 右眼面积 − 双目重叠面积 // 前面已经算出来的 //double leftArea = ...; //double rightArea = ...; //double binocularArea = ...; //// 总视野 //double totalVisionArea = leftArea + rightArea - binocularArea; //// 标准总面积(设备固定值,比如 120) //double standardTotalArea = 120; //// 空白视野面积 //double blankArea = standardTotalArea - totalVisionArea; //视野保存率 //double totalSaveRate = (总视野面积 / 标准总视野面积) * 100; public static class VisionCalculator { /// /// 计算视野保存率 /// /// 实测面积 /// 标准面积 /// 保存率 % public static double CalculateVisionSaveRate(double actualArea, double standardArea) { if (standardArea == 0) return 0; return (actualArea / standardArea) * 100; } } // 空头模(无面罩)标定的标准面积(GB2890-2022) //public static double StandardSingleEye = 5200; //// 双眼总标准面积(左+右) //public static double StandardTotalEye = 10400; //// 双目重叠标准面积 ////public static double StandardBinocular = 4200; //// 下方视野标准角度 //public static double StandardLowerAngle = 75; //===== 1. 你预先标定的 空模标准面积 ===== // 单眼标准、总标准(左+右)、双目重叠标准 public static double StandardLeftEye = 5180; public static double StandardRightEye = 5180; public static double StandardTotal = 10360; public static double StandardBinocular = 4150; //===== 2. 传入你采集的实测面积 ===== // leftArea:左眼实测 rightArea:右眼实测 binArea:双目重叠实测 public static double CalcVisionRate(double leftArea, double rightArea) { // 总视野实测 = 左+右 double totalSi = leftArea + rightArea; // 1. 总视野保存率 double ratioTotal = totalSi / StandardTotal; double gammaTotal = GetVisionGamma(ratioTotal); double totalRate = gammaTotal * ratioTotal * 100; return (totalRate); } /// /// GB2890-2022 自动获取 总视野/双目视野 校正系数γ /// /// 实测面积/标准面积 比值(0~1) /// 校正系数 γ public static double GetVisionGamma(double ratio) { // X:视野残存率 Si/S0 double[] xData = { 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0 }; // 总视野 γ 对应值 double[] gammaTotal = { 1.22, 1.18, 1.14, 1.10, 1.06, 1.03, 1.02, 1.01, 1.00 }; double[] yData = gammaTotal; // 边界限制 if (ratio <= xData[0]) return yData[0]; if (ratio >= xData.Last()) return 1.0; // 线性插值 for (int i = 0; i < xData.Length - 1; i++) { if (ratio >= xData[i] && ratio <= xData[i + 1]) { double t = (ratio - xData[i]) / (xData[i + 1] - xData[i]); return yData[i] + t * (yData[i + 1] - yData[i]); } } return 1.0; } internal static double CalculateEyeArea(List leftEyeDataList, int v1, int v2) { throw new NotImplementedException(); } internal static double CalcBinocularArea(List leftEyeDataList, List rightEyeDataList, int v1, int v2) { throw new NotImplementedException(); } internal static double[] GetEyeAvgArray(List leftEyeDataList) { throw new NotImplementedException(); } // 你算出来的实际面积 //double left = 4250; //double right = 4320; //double bin = 2860; //// 一键算出 国标保存率 //var result = CalcVisionRate(left, right, bin); //double 总视野保存率 = result.totalRate; //double 双目视野保存率 = result.binRate; } //// 1. 总视野保存率 //double gammaTotal = 查国标图D.4的总视野γ; //double totalRate = gammaTotal * totalArea / GlobalData.StandardTotalEye * 100; // // 2. 双目视野保存率 // double gammaBinoc = 查国标图D.4的双目视野γ; //double binocRate = gammaBinoc * binocArea / GlobalData.StandardBinocular * 100; //// 3. 下方视野(直接比角度,不用面积) //bool lowerPass = lowerAngle >= 35; // 四、关键澄清(你之前问的) //下方视野:国标是角度(°),不是面积 //按左右眼视野曲线下方交点位置直接读出角度 //合格:≥35° //总视野 = 左眼 + 右眼(国标明确) //双目视野 = 左右眼重叠部分(单独算面积) //五、最简总结(国标一句话) //总视野保存率 =(γ ×(左 + 右实测面积))/ 标准总面积 ×100% //双目视野保存率 =(γ × 重叠实测面积)/ 标准重叠面积 ×100% //下方视野:直接看角度 ≥35° //}