实验性功能新增「差异放大」,并修正「构建为新信号」按钮的间距
1. 新增「实验性功能 → 差异放大」(与其它实验功能一样是带勾选的开关)
- 用途:纹波类信号某个周期(甚至半个周期)的 M 形下凹明显偏弱时,
把这段下凹按比例拉回周边振幅,恢复正常周期的对比度,便于看出波形异常。
- 算法(全 O(n),不额外占用与信号长度成正比以外的内存):
以半径约半个纹波周期的滑动平均作为纹波中心线,用邻域内「交流分量的最大值」
作为局部纹波振幅,取全信号振幅的 75 分位作为目标振幅,
逐点增益 = 目标振幅 / 局部振幅(夹在 1 到放大倍数之间),
输出 = 中心线 + 增益 × 交流分量,再夹在周边包络内。
- 关键性质(均已用合成正弦逐点验证):
* 增益下界恒为 1,因此**只有被判定为"偏弱"的采样点会改变**,
其余采样点输出与原值逐位相同 —— 实测逐周期表格中只有一个周期发生变化;
* 放大结果夹在周边包络内,因此**任何一点都不超出原有的振幅范围**,
整体值域与其它周期的峰峰值都不变;
* 端点自动收敛到不放大,避免记录首尾被整片抬升。
- 参数「窗口点数 X」(窗口长度,建议略大于纹波周期的一半)与
「放大倍数 N」(增益上限)都可在浮动面板内按信号编辑。
2. 浮动面板
- 与「均值滤波」完全同一套排版:自上而下逐信号条目,
第一排信号名与复选框、第二排两个参数输入框;取消勾选即不显示该信号的参数与预览曲线;
启用时默认只勾选网格内第一个可见信号。
- 新增「构建为新信号」按钮,按各信号自己的参数把结果插入到源信号下方,
与「均值滤波」的构建机制完全一致(统一走同一套请求与插入流程)。
3. 光标交互
- 纵向光标额外标注差异放大曲线在该时刻的取值,配色与原始信号一致,
数值尾部括号内容为「差异化」,双光标时同样带增量。
4. 修复「构建为新信号」按钮与面板下边界的间距
- 原按钮行直接贴着面板下边框,与面板左右内边距及内容间距都不协调;
现在按钮上方与下方各保留一个与左右一致的内边距(均值滤波与差异放大同时生效)。
测试:离屏验证套件新增两组检查(合成正弦上算法只改动一个周期、放大不越界、
参数上限生效、面板排版与按钮间距;再写一份合成纹波文件真正加载渲染并核对曲线数值),
合计 289 项检查全部通过;验证过程不显示窗口、不移动鼠标键盘、不抓屏。
This commit is contained in:
1 parent
8801c8f019
commit
d4206a8d67
6 files changed
+1188
-185
No files matched your search
@@ -1461,8 +1461,8 @@ namespace jdi
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Application::BuildMeanFilterSignal(int fileIndex, int signalIndex, int points, int order,
|
||||
bool makeVisible)
|
||||
bool Application::BuildDerivedSignal(int fileIndex, int signalIndex, int points, int order,
|
||||
bool diffAmp, bool makeVisible)
|
||||
{
|
||||
const DataFile* file = m_dataSet.FileAt(fileIndex);
|
||||
if (file == nullptr || signalIndex < 0 || signalIndex >= static_cast<int>(file->signals.size()))
|
||||
@@ -1472,21 +1472,37 @@ namespace jdi
|
||||
const SignalData& source = file->signals[static_cast<std::size_t>(signalIndex)];
|
||||
const std::wstring baseName = source.Title();
|
||||
|
||||
// 1. 计算多阶均值滤波采样值(与预览曲线共用同一实现)。
|
||||
std::vector<float> filtered;
|
||||
ComputeMovingAverageIterated(source.values, points, order, filtered);
|
||||
|
||||
// 2. 组装新信号:名称形如「<原信号名称> (N点均值x阶)」。
|
||||
SignalData derived;
|
||||
derived.name = baseName + L" (" + std::to_wstring(points) + L"点均值";
|
||||
if (order > 1)
|
||||
// 1. 计算派生采样值(与网格内的预览曲线共用同一实现,因此逐位一致)。
|
||||
std::vector<float> derived_values;
|
||||
if (diffAmp)
|
||||
{
|
||||
derived.name += L"×" + std::to_wstring(order) + L"阶";
|
||||
ComputeDiffAmplify(source.values, points, order, derived_values);
|
||||
}
|
||||
else
|
||||
{
|
||||
ComputeMovingAverageIterated(source.values, points, order, derived_values);
|
||||
}
|
||||
|
||||
// 2. 组装新信号:均值滤波形如「<原信号名称> (N点均值×M阶)」,
|
||||
// 差异放大形如「<原信号名称> (X点差异放大×N倍)」。
|
||||
SignalData derived;
|
||||
if (diffAmp)
|
||||
{
|
||||
derived.name = baseName + L" (" + std::to_wstring(points) + L"点差异放大×" +
|
||||
std::to_wstring(order) + L"倍)";
|
||||
}
|
||||
else
|
||||
{
|
||||
derived.name = baseName + L" (" + std::to_wstring(points) + L"点均值";
|
||||
if (order > 1)
|
||||
{
|
||||
derived.name += L"×" + std::to_wstring(order) + L"阶";
|
||||
}
|
||||
derived.name += L")";
|
||||
}
|
||||
derived.name += L")";
|
||||
derived.rawHeader = derived.name;
|
||||
derived.unit = source.unit;
|
||||
derived.values.assign(filtered.begin(), filtered.end());
|
||||
derived.values.assign(derived_values.begin(), derived_values.end());
|
||||
|
||||
// 3. 插入到原信号的下方(数据层 + 各标签页引用重映射)。
|
||||
int newIndex = -1;
|
||||
@@ -1518,24 +1534,67 @@ namespace jdi
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Application::ApplyPendingMeanFilterBuild()
|
||||
bool Application::ApplyPendingDerivedBuild()
|
||||
{
|
||||
int laneIndex = -1;
|
||||
std::vector<PlotView::MeanFilterBuild> items;
|
||||
if (!m_plot.TakePendingMeanFilterBuild(laneIndex, items))
|
||||
// 两类「构建为新信号」请求(均值滤波 / 差异放大)机制完全一致,
|
||||
// 统一整理成同一份构建清单后按下标降序处理。
|
||||
struct BuildRequest
|
||||
{
|
||||
int fileIndex = -1;
|
||||
int signalIndex = -1;
|
||||
int first = 0;
|
||||
int second = 0;
|
||||
bool diffAmp = false;
|
||||
};
|
||||
|
||||
std::vector<BuildRequest> requests;
|
||||
{
|
||||
int laneIndex = -1;
|
||||
std::vector<PlotView::MeanFilterBuild> items;
|
||||
if (m_plot.TakePendingMeanFilterBuild(laneIndex, items))
|
||||
{
|
||||
for (const PlotView::MeanFilterBuild& item : items)
|
||||
{
|
||||
BuildRequest request;
|
||||
request.fileIndex = item.fileIndex;
|
||||
request.signalIndex = item.signalIndex;
|
||||
request.first = item.points;
|
||||
request.second = item.order;
|
||||
request.diffAmp = false;
|
||||
requests.push_back(request);
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
int laneIndex = -1;
|
||||
std::vector<PlotView::DiffAmpBuild> items;
|
||||
if (m_plot.TakePendingDiffAmpBuild(laneIndex, items))
|
||||
{
|
||||
for (const PlotView::DiffAmpBuild& item : items)
|
||||
{
|
||||
BuildRequest request;
|
||||
request.fileIndex = item.fileIndex;
|
||||
request.signalIndex = item.signalIndex;
|
||||
request.first = item.points;
|
||||
request.second = item.gain;
|
||||
request.diffAmp = true;
|
||||
requests.push_back(request);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (requests.empty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (m_tabs.empty() || laneIndex < 0 ||
|
||||
laneIndex >= static_cast<int>(ActiveTab().Lanes().size()))
|
||||
if (m_tabs.empty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// **同一文件内必须按下标降序处理**:每次插入只让插入点之后的信号下标 +1,
|
||||
// 降序处理可保证尚未处理的信号下标始终有效(升序会把新插入的信号当成原信号)。
|
||||
std::sort(items.begin(), items.end(),
|
||||
[](const PlotView::MeanFilterBuild& a, const PlotView::MeanFilterBuild& b)
|
||||
std::sort(requests.begin(), requests.end(),
|
||||
[](const BuildRequest& a, const BuildRequest& b)
|
||||
{
|
||||
if (a.fileIndex != b.fileIndex)
|
||||
{
|
||||
@@ -1545,27 +1604,38 @@ namespace jdi
|
||||
});
|
||||
|
||||
int built = 0;
|
||||
int lastPoints = 0;
|
||||
int lastOrder = 1;
|
||||
for (const PlotView::MeanFilterBuild& item : items)
|
||||
int lastFirst = 0;
|
||||
int lastSecond = 1;
|
||||
bool lastDiffAmp = false;
|
||||
for (const BuildRequest& request : requests)
|
||||
{
|
||||
if (BuildMeanFilterSignal(item.fileIndex, item.signalIndex, item.points, item.order, true))
|
||||
if (BuildDerivedSignal(request.fileIndex, request.signalIndex, request.first,
|
||||
request.second, request.diffAmp, true))
|
||||
{
|
||||
++built;
|
||||
lastPoints = item.points;
|
||||
lastOrder = item.order;
|
||||
lastFirst = request.first;
|
||||
lastSecond = request.second;
|
||||
lastDiffAmp = request.diffAmp;
|
||||
}
|
||||
}
|
||||
|
||||
if (built > 0)
|
||||
{
|
||||
std::wstring text = L"已构建 " + std::to_wstring(built) + L" 条 " +
|
||||
std::to_wstring(lastPoints) + L"点均值";
|
||||
if (lastOrder > 1)
|
||||
std::wstring text = L"已构建 " + std::to_wstring(built) + L" 条 ";
|
||||
if (lastDiffAmp)
|
||||
{
|
||||
text += L"×" + std::to_wstring(lastOrder) + L"阶";
|
||||
text += std::to_wstring(lastFirst) + L"点差异放大×" +
|
||||
std::to_wstring(lastSecond) + L"倍信号。";
|
||||
}
|
||||
else
|
||||
{
|
||||
text += std::to_wstring(lastFirst) + L"点均值";
|
||||
if (lastSecond > 1)
|
||||
{
|
||||
text += L"×" + std::to_wstring(lastSecond) + L"阶";
|
||||
}
|
||||
text += L"滤波信号。";
|
||||
}
|
||||
text += L"滤波信号。";
|
||||
SetStatusMessage(text, false);
|
||||
}
|
||||
return built > 0;
|
||||
@@ -1698,6 +1768,12 @@ namespace jdi
|
||||
meanFilter.id = PLOT_CMD_LANE_MEAN_FILTER;
|
||||
meanFilter.checked = m_plot.LaneMeanFilter(laneIndex);
|
||||
experimental.submenu.push_back(meanFilter);
|
||||
|
||||
MenuItem diffAmp;
|
||||
diffAmp.label = L"差异放大";
|
||||
diffAmp.id = PLOT_CMD_LANE_DIFF_AMP;
|
||||
diffAmp.checked = m_plot.LaneDiffAmp(laneIndex);
|
||||
experimental.submenu.push_back(diffAmp);
|
||||
}
|
||||
items.push_back(experimental);
|
||||
|
||||
@@ -1812,6 +1888,10 @@ namespace jdi
|
||||
// 「实验性功能 → 均值滤波」:切换均值滤波曲线与参数面板。
|
||||
m_plot.ToggleLaneMeanFilter(laneIndex);
|
||||
break;
|
||||
case PLOT_CMD_LANE_DIFF_AMP:
|
||||
// 「实验性功能 → 差异放大」:切换差异放大曲线与参数面板。
|
||||
m_plot.ToggleLaneDiffAmp(laneIndex);
|
||||
break;
|
||||
case PLOT_CMD_LANE_SPLIT:
|
||||
// 「拆分当前网格」:信号按个拆成独立网格,新网格顺次插入其下方。
|
||||
m_plot.SplitLane(laneIndex);
|
||||
@@ -2311,7 +2391,7 @@ namespace jdi
|
||||
// 绘图区内的点击也可能提交图例改名,需要立刻落到数据层。
|
||||
ApplyPendingRename();
|
||||
// 「均值滤波 → 构建为新信号」按钮:立即构建新信号并同步各视图。
|
||||
ApplyPendingMeanFilterBuild();
|
||||
ApplyPendingDerivedBuild();
|
||||
InvalidateRect(hwnd, nullptr, FALSE);
|
||||
}
|
||||
// 拖动期间 Windows 不再派发 WM_SETCURSOR,需在按下后立即刷新一次,
|
||||
|
||||
Reference in new issue
Block a user