From 18b8f3e38eff5605873b0d5bec81bb41e205b131 Mon Sep 17 00:00:00 2001 From: "DeepSeek V4.1 Flash" <> Date: Sat, 10 Oct 2026 10:21:57 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E9=9D=9E=E5=9B=BA=E5=AE=9A?= =?UTF-8?q?=E5=91=A8=E6=9C=9F=E9=87=87=E6=A0=B7=E6=95=B0=E6=8D=AE=E6=94=BE?= =?UTF-8?q?=E5=A4=A7=E5=90=8E=E6=B3=A2=E5=BD=A2=E5=87=BA=E7=8E=B0=E6=96=AD?= =?UTF-8?q?=E5=BC=80=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 问题:加载 Problem_Data.csv(约 27µs 且不固定的采样周期、含 100–277µs 空档),勾选 .UD\SLC_1ms\rtb_BodyIAC_K40+0x2,放大到主时间轴一大格 10ms 时波形大量断开。 根因:该缩放级别下波形走「像素列上下包络」渲染,而包络是按像素列聚合的, 每列只统计「落在本列内的采样点」。本文件的采样间隔(约 27µs)与一个像素列的宽度 (约 60µs)是同量级,因此一次跃变的两个端点常常分别落在相邻两列中:对每一列而言 列内采样点都在同一边,于是这段竖直折线在两侧的包络里都不出现,被整段丢掉, 看上去就是波形断开(与逐点描边渲染对照,1224 列中有 32 列完全没有波形像素)。 修复:在列包络中补上「跨越列边界的两段折线在边界处的取值」。新增 PolylineValueAt() 取折线在列左/右边界处的线性插值并计入该列包络。由于折线在列内是线性的,其极值必然 出现在采样点或列边界上,因此「列内采样点 + 两侧边界取值」就是该列包络的精确解; 相邻两列在公共边界取到同一个值,包络带因而严丝合缝。该处理同时覆盖了「整列没有采样点」 的情况(此时两侧边界取自同一条线段),因此也一并消除了采样稀疏处原有的断口。 验证: - 用临时开关强制走逐点描边路径作为对照基准,按像素列比对覆盖率: 修复前 32 列缺失,修复后 0 列缺失(1224/1224 全覆盖),像素总量与对照基准相当; - 同一区域的 12 倍放大截图显示波形已恢复为连续的方波,与逐点描边结果一致; - 回归 VERY_BIG_DATA.CSV(稠密包络)与 BIG_DATA.csv(稀疏描边 + 包络)渲染正常, make clean && make all 无警告。 --- Components/PlotView/Src/PlotView.cpp | 99 ++++++++++++++++++++++++---- 1 file changed, 88 insertions(+), 11 deletions(-) diff --git a/Components/PlotView/Src/PlotView.cpp b/Components/PlotView/Src/PlotView.cpp index 600e7ba..46a300e 100644 --- a/Components/PlotView/Src/PlotView.cpp +++ b/Components/PlotView/Src/PlotView.cpp @@ -59,6 +59,51 @@ namespace jdi const wchar_t* const kMeanFilterBuildLabel = L"构建为新信号"; /**< 均值滤波面板底部按钮文案。 */ const std::size_t kEdgeCountCacheLimit = 48; /**< 边沿统计缓存条目上限(超出时淘汰最早的一条)。 */ + /** + * @brief 取折线(相邻采样点之间线性连接)在给定时刻的取值。 + * + * 包络是按像素列聚合的,因此某一段折线可能**跨越列边界**:跨越边界的那一小段 + * 在两个列里都不属于"落在本列内的采样点",只看列内采样点就会把它整段丢掉。 + * 该函数用于在列边界处取回这段折线的取值,使相邻两列的包络严丝合缝。 + * + * @param [IN] time 时间轴。 + * @param [IN] values 采样值。 + * @param [IN] count 可用采样点数量。 + * @param [IN] firstIndex 第一个时间不小于查询时刻的采样下标(相交线段为 firstIndex-1 → firstIndex)。 + * @param [IN] t 查询时刻。 + * @param [OUT] out 输出取值。 + * @return 折线覆盖该时刻且取到有效值返回 true。 + */ + bool PolylineValueAt(const std::vector& time, const std::vector& values, + std::size_t count, std::size_t firstIndex, double t, double& out) + { + if (firstIndex == 0 || firstIndex >= count) + { + // 该时刻之前(或之后)没有采样点,折线并未延伸到这里。 + return false; + } + const double t0 = time[firstIndex - 1]; + const double t1 = time[firstIndex]; + const double v0 = values[firstIndex - 1]; + const double v1 = values[firstIndex]; + if (!std::isfinite(t0) || !std::isfinite(t1) || !std::isfinite(v0) || !std::isfinite(v1) || + !(t1 > t0)) + { + return false; + } + double ratio = (t - t0) / (t1 - t0); + if (ratio < 0.0) + { + ratio = 0.0; + } + else if (ratio > 1.0) + { + ratio = 1.0; + } + out = v0 + (v1 - v0) * ratio; + return true; + } + /** * @brief 统计相邻采样对 (k-1, k) 中穿越判定基准的边沿数量。 * @@ -3929,13 +3974,6 @@ namespace jdi std::lower_bound(time.begin() + static_cast(i0), rangeEnd, t1) - time.begin()); - if (i1 <= i0) - { - // 该列没有采样点:说明数据在此处存在时间间隙,断开当前折线。 - inSegment = false; - flushBandSegment(); - continue; - } double low = std::numeric_limits::infinity(); double high = -std::numeric_limits::infinity(); @@ -3943,8 +3981,8 @@ namespace jdi std::size_t highOrder = 0; bool broken = false; - // 一列至少覆盖两个分组时才用分组层(否则直接扫原始采样更准且更省)。 - if (hasCoarse && (i1 - i0) >= 2 * coarseStride) + // (一)落在本列区间内的采样点。 + if (i1 > i0 && hasCoarse && (i1 - i0) >= 2 * coarseStride) { const std::size_t firstBucket = i0 / coarseStride; const std::size_t lastBucket = @@ -3971,7 +4009,7 @@ namespace jdi } } } - else + else if (i1 > i0) { for (std::size_t index = i0; index < i1; ++index) { @@ -3995,9 +4033,48 @@ namespace jdi } } + // (二)跨越列边界的两段折线在边界处的取值。 + // + // 波形按相邻采样点之间的**直线**绘制,而包络是按像素列聚合的。采样间隔 + // 可能大于一个像素列(本仓库的 Problem_Data.csv 相邻采样间隔约 27µs 且不固定, + // 放大到 10ms/大格时一个像素列约 60µs),此时一次跃变的两个端点会分别落在 + // 相邻两列里:对每一列来说"落在本列内的采样点"都在同一边,于是这段竖向折线 + // 在两侧的包络里都不出现 → 波形在这些跃变处被整段丢掉,看上去就是"断开"。 + // + // 把列边界处的折线取值计入本列包络即可消除该现象:折线在列内是线性的, + // 其极值必然出现在采样点或列边界上,因此「列内采样点 + 两侧边界取值」就是 + // 该列包络的精确解,且相邻两列在边界处取到同一个值,包络带严丝合缝。 + double boundary = 0.0; + if (PolylineValueAt(time, values, count, i0, t0, boundary)) + { + if (boundary < low) + { + low = boundary; + lowOrder = (i0 > 0) ? (i0 - 1) : 0; + } + if (boundary > high) + { + high = boundary; + highOrder = (i0 > 0) ? (i0 - 1) : 0; + } + } + if (PolylineValueAt(time, values, count, i1, t1, boundary)) + { + if (boundary < low) + { + low = boundary; + lowOrder = i1; + } + if (boundary > high) + { + high = boundary; + highOrder = i1; + } + } + if (!(low <= high)) { - // 本列全是无效值。 + // 本列没有任何有效取值:折线尚未开始、已经结束,或该处数据缺失。 inSegment = false; flushBandSegment(); continue;