From ea8e76bff470bd232ec48807762cdcad42295747 Mon Sep 17 00:00:00 2001 From: xyy <544939200@qq.com> Date: Sat, 11 Apr 2026 20:47:41 +0800 Subject: [PATCH] --- Helpers/PoreDistributionAnalysis.cs | 67 +++++++++++--- ViewModels/PoreDistributionViewModel.cs | 118 ++++++++++++++++++++++-- 2 files changed, 164 insertions(+), 21 deletions(-) diff --git a/Helpers/PoreDistributionAnalysis.cs b/Helpers/PoreDistributionAnalysis.cs index c8d962d..26d0b4f 100644 --- a/Helpers/PoreDistributionAnalysis.cs +++ b/Helpers/PoreDistributionAnalysis.cs @@ -36,34 +36,71 @@ namespace MembranePoreTester.Helpers return 0; } + + + public static double CalculatePoreRangePercentage( - IEnumerable points, - string unit, - TestLiquid liquid, - double lowerPore, - double upperPore) + IEnumerable points, + string unit, + TestLiquid liquid, + double lowerPore, + double upperPore) { var sorted = points.OrderBy(p => p.Pressure).ToList(); if (sorted.Count < 2) return 0; - double[] pressures = sorted.Select(p => p.Pressure).ToArray(); - double[] wetFlows = sorted.Select(p => p.WetFlow).ToArray(); - double[] dryFlows = sorted.Select(p => p.DryFlow).ToArray(); + // 提取有效点(流量 > 0) + var wetValid = sorted.Where(p => p.WetFlow > 0).Select(p => (Pressure: p.Pressure, Flow: p.WetFlow)).ToList(); + var dryValid = sorted.Where(p => p.DryFlow > 0).Select(p => (Pressure: p.Pressure, Flow: p.DryFlow)).ToList(); - // 大孔径对应低压,小孔径对应高压 + if (wetValid.Count < 2 || dryValid.Count < 2) return 0; + + // 有效压力重叠区间 + double minPressure = Math.Max(wetValid.First().Pressure, dryValid.First().Pressure); + double maxPressure = Math.Min(wetValid.Last().Pressure, dryValid.Last().Pressure); + if (minPressure >= maxPressure) return 0; + + // 孔径转压力 double pLower = PoreCalculator.PoreToPressure(upperPore, unit, liquid); double pUpper = PoreCalculator.PoreToPressure(lowerPore, unit, liquid); - // 插值 - double qWetLower = Interpolation.Linear(pressures, wetFlows, pLower); - double qDryLower = Interpolation.Linear(pressures, dryFlows, pLower); - double qWetUpper = Interpolation.Linear(pressures, wetFlows, pUpper); - double qDryUpper = Interpolation.Linear(pressures, dryFlows, pUpper); + pLower = Math.Max(pLower, minPressure); + pUpper = Math.Min(pUpper, maxPressure); + if (pUpper <= pLower) return 0; + + // 在有效点集上插值 + double qWetLower = InterpolateOnValid(wetValid, pLower); + double qDryLower = InterpolateOnValid(dryValid, pLower); + double qWetUpper = InterpolateOnValid(wetValid, pUpper); + double qDryUpper = InterpolateOnValid(dryValid, pUpper); + + if (qDryLower <= 0 || qDryUpper <= 0) return 0; double ratioLow = qWetLower / qDryLower; double ratioHigh = qWetUpper / qDryUpper; + double result = (ratioHigh - ratioLow) * 100; + return result < 0 ? 0 : result; + } - return (ratioHigh - ratioLow) * 100; + // 辅助插值函数:基于有效点列表(已按压力升序) + private static double InterpolateOnValid(List<(double Pressure, double Flow)> validPoints, double pressure) + { + if (validPoints.Count == 0) return 0; + if (pressure <= validPoints[0].Pressure) return validPoints[0].Flow; + if (pressure >= validPoints[^1].Pressure) return validPoints[^1].Flow; + + for (int i = 0; i < validPoints.Count - 1; i++) + { + if (pressure >= validPoints[i].Pressure && pressure <= validPoints[i + 1].Pressure) + { + double p1 = validPoints[i].Pressure; + double f1 = validPoints[i].Flow; + double p2 = validPoints[i + 1].Pressure; + double f2 = validPoints[i + 1].Flow; + return f1 + (f2 - f1) * (pressure - p1) / (p2 - p1); + } + } + return 0; } } } \ No newline at end of file diff --git a/ViewModels/PoreDistributionViewModel.cs b/ViewModels/PoreDistributionViewModel.cs index 385e41c..c0cbb28 100644 --- a/ViewModels/PoreDistributionViewModel.cs +++ b/ViewModels/PoreDistributionViewModel.cs @@ -111,6 +111,22 @@ namespace MembranePoreTester.ViewModels float rawPressure = await _plcService.ReadPressureAsync(StationId); double pressure = Math.Round(rawPressure * _plcConfig.PressureFactor, 2); + + + + // 2. 读取当前工位的加压上限(实时) + ushort upperLimitAddress = StationId == 1 ? _plcConfig.PressureUpperLimit + : StationId == 2 ? _plcConfig.PressureUpperLimit2 + : _plcConfig.PressureUpperLimit3; + double pressureUpperLimit = await _plcService.ReadFloatAsync(upperLimitAddress); + + // 3. 如果压力已达到或超过上限,停止采集 + if (pressure >= pressureUpperLimit - 3) + { + StopCollecting(); + return; // 不再添加当前数据点 + } + // 2. 读取当前模式对应的流量 double flow = 0; if (TestMode.Contains("湿膜")) @@ -550,11 +566,18 @@ namespace MembranePoreTester.ViewModels } - // 修改 Calculate 方法: + //修改 Calculate 方法: private void Calculate() { // 先清洗数据并替换到绑定的集合中,确保DataGrid和曲线显示清洗后的数据 var originalPoints = Record.DataPoints.ToList(); + + System.Diagnostics.Debug.WriteLine("=== 清洗前的数据点 ==="); + foreach (var p in originalPoints) + { + System.Diagnostics.Debug.WriteLine($"P={p.Pressure}, Wet={p.WetFlow}, Dry={p.DryFlow}"); + } + var cleanedPoints = CleanDataPoints(originalPoints); if (cleanedPoints.Count < 2) @@ -587,22 +610,105 @@ namespace MembranePoreTester.ViewModels } } + //private void Calculate() + //{ + // // 直接使用原始数据,不清洗 + // if (Record.DataPoints.Count < 2) + // { + // MessageBox.Show("有效数据点不足,至少需要 2 个数据点进行计算。"); + // return; + // } + + // // 注意:不清洗,也不改变 Record.DataPoints 的顺序(但计算函数内部会自己排序) + // // 直接计算 + // AveragePoreSize = PoreDistributionAnalysis.CalculateAveragePore( + // Record.DataPoints, Record.PressureUnit, Record.Liquid); + + // RangePercentage = PoreDistributionAnalysis.CalculatePoreRangePercentage( + // Record.DataPoints, Record.PressureUnit, Record.Liquid, LowerPore, UpperPore); + + // // 可选:提示用户当前使用了原始数据(不清洗) + // // MessageBox.Show("已使用原始数据(未清洗)进行计算"); + //} + + //private List CleanDataPoints(IEnumerable points) + //{ + // return points + // .Where(p => p.Pressure > 0.001) // 移除压力接近0的点 + // .Where(p => !(Math.Abs(p.WetFlow) < 0.001 && Math.Abs(p.DryFlow) < 0.001)) // 移除双零流量点 + // .GroupBy(p => Math.Round(p.Pressure, 2)) // 按压力四舍五入分组(避免重复压力) + // .Select(g => new Models.DataPoint + // { + // Pressure = g.Key, + // WetFlow = g.Max(x => x.WetFlow), // 取最大值(避免0覆盖有效值) + // DryFlow = g.Max(x => x.DryFlow) + // }) + // .OrderBy(p => p.Pressure) + // .ToList(); + //} + + + private List CleanDataPoints(IEnumerable points) { - return points - .Where(p => p.Pressure > 0.001) // 移除压力接近0的点 - .Where(p => !(Math.Abs(p.WetFlow) < 0.001 && Math.Abs(p.DryFlow) < 0.001)) // 移除双零流量点 - .GroupBy(p => Math.Round(p.Pressure, 2)) // 按压力四舍五入分组(避免重复压力) + // 第一步:基础清洗(移除压力≤0、双零流量、合并相同压力取最大流量) + var cleaned = points + .Where(p => p.Pressure > 0.001) + .Where(p => !(Math.Abs(p.WetFlow) < 0.001 && Math.Abs(p.DryFlow) < 0.001)) + .GroupBy(p => Math.Round(p.Pressure, 2)) .Select(g => new Models.DataPoint { Pressure = g.Key, - WetFlow = g.Max(x => x.WetFlow), // 取最大值(避免0覆盖有效值) + WetFlow = g.Max(x => x.WetFlow), DryFlow = g.Max(x => x.DryFlow) }) .OrderBy(p => p.Pressure) .ToList(); + + if (cleaned.Count < 4) return cleaned; // 点数太少,不进行高级剔除 + + // 第二步:仅对湿膜流量进行异常孤立点剔除(干膜保持原样,因为干膜可能有很多0和少量非零) + var result = new List(); + const double threshold = 3.0; // 孤立高点阈值(倍),比之前放宽 + + for (int i = 0; i < cleaned.Count; i++) + { + var current = cleaned[i]; + bool isAbnormal = false; + + // 仅检查中间的点(非首尾),且只检查湿膜流量 + if (i > 0 && i < cleaned.Count - 1) + { + double prevWet = cleaned[i - 1].WetFlow; + double nextWet = cleaned[i + 1].WetFlow; + double maxNeighbor = Math.Max(prevWet, nextWet); + double minNeighbor = Math.Min(prevWet, nextWet); + + // 如果当前湿膜流量远大于前后邻居的最大值(孤立高峰),且前后邻居都不为0 + if (maxNeighbor > 0 && current.WetFlow > maxNeighbor * threshold) + isAbnormal = true; + + // 如果当前湿膜流量远小于前后邻居的最小值(孤立低谷),一般很少见,但保留 + if (minNeighbor > 0 && current.WetFlow < minNeighbor / threshold) + isAbnormal = true; + } + + // 不剔除干膜中的非零点(即使孤立也不删) + if (!isAbnormal) + result.Add(current); + } + + + + + + // 如果全部被误判为异常,则回退到原始清洗结果 + return result.Count == 0 ? cleaned : result; } + + + //private void GenerateReport() //{ // ReportGenerator.GeneratePoreDistributionReport(Record);