实验性功能:移除「差异放大」、新增「弱波增强」;修复放大后拖拽光标越出视界
- 移除差异放大:ComputeDiffAmplify、EdgePanelKind::DIFF_AMP 面板、DIFF_AMP_*
编辑目标、菜单项与相关缓存,并删除仅供它使用的 SlidingMax / SlidingExtrema /
float 版滑动平均。
- 新增弱波增强 ComputeWeakWaveBoost(values, level, out)(O(n)):
1) 对轻度平滑(5 点滑动平均)后的信号扫描单调段,方向翻转处即为波峰/波谷;
2) 波峰低于左右两个波峰均值(波谷高于左右两个波谷均值)即判为弱波;
3) 波峰目标 = 较小波峰 + 等级×(较大−较小);
波谷目标 = 较大波谷 − 等级×(较大−较小);
4) 极值点取目标值,两侧数据点在相邻极值点的偏移量之间用平滑步进过渡,
因此健康周期逐位不变、数据端点原样保留。
- 弱波增强面板:逐信号复选框 + 「弱波增强等级」滑块(0–100%,整个网格共用)
+ 「构建为新信号」按钮;构建名形如「<原信号> (弱波增强 M%)」。
等级进 SceneContentHash,改动即触发重绘;Lane 新增
weakBoost/weakBoostLevel/weakBoostItems,并在 LaneCarry、SplitLane、
RemapAfterSignalInsert/Removal 中同步。
- 修复:放大到一定程度后左右拖拽时间光标(尤其抓取较宽的读数标签、抓取偏移较大时)
会把光标时间推到视界之外;现在 CURSOR_MOVE 与 CURSOR_SPAN_MOVE 结束后
按可见区间夹取(视图变化仍不移动光标,保留贴边读数与方向三角)。
- 离屏验证(窗口隐藏,不占用鼠标/键盘/屏幕):295 项检查全部通过,
含等级 0/50/100% 的目标值与健康区逐位不变、滑块拖拽与左右钳制、
合成纹波端到端恢复(弱周期峰峰值 100→160,健康周期 200 不变)、
光标拖拽始终落在可见区间内。
This commit is contained in:
1 parent
d4206a8d67
commit
67f659ad23
6 files changed
+781
-734
No files matched your search
@@ -239,28 +239,36 @@ namespace jdi
|
||||
bool ApplyPendingRename();
|
||||
|
||||
/**
|
||||
* @brief 应用绘图区提交的「均值滤波 → 构建为新信号」请求。
|
||||
* @brief 应用绘图区提交的「实验性功能 → 构建为新信号」请求。
|
||||
*
|
||||
* 为指定网格内的每条可见信号各构建一条 N 点均值滤波信号,命名为
|
||||
* `<原信号名称> (N点均值)`,插入到原信号所在组的原信号位置**下方**,
|
||||
* 并把新信号所在的网格移动到原信号网格的下方。
|
||||
* 为指定网格内的每条被勾选信号各构建一条派生信号(多阶均值滤波或弱波增强),
|
||||
* 命名为 `<原信号名称> (N点均值…)` / `<原信号名称> (弱波增强 M%)`,
|
||||
* 插入到原信号所在组的原信号位置**下方**,并把新信号所在的网格移动到原信号网格的下方。
|
||||
*
|
||||
* @return 实际构建了新信号返回 true。
|
||||
*/
|
||||
bool ApplyPendingDerivedBuild();
|
||||
|
||||
/**
|
||||
* @brief 把一条多阶均值滤波派生信号插入数据层并同步各视图。
|
||||
* @param [IN] fileIndex 原信号所属文件下标。
|
||||
* @param [IN] signalIndex 原信号下标。
|
||||
* @param [IN] points 均值点数 X(滑平半径)。
|
||||
* @param [IN] order 阶数 N(迭代次数)。
|
||||
* @brief 派生信号构建请求(多阶均值滤波 / 弱波增强共用同一套插入流程)。
|
||||
*/
|
||||
struct DerivedSignalRequest
|
||||
{
|
||||
int fileIndex = -1; /**< 原信号所属文件下标。 */
|
||||
int signalIndex = -1; /**< 原信号下标。 */
|
||||
int points = 5; /**< 多阶均值滤波:均值点数 X(滑平半径)。 */
|
||||
int order = 1; /**< 多阶均值滤波:阶数 N(迭代次数)。 */
|
||||
int levelPercent = 50; /**< 弱波增强:增强等级(0–100)。 */
|
||||
bool weakBoost = false; /**< true = 弱波增强,false = 多阶均值滤波。 */
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 把一条派生信号(多阶均值滤波 / 弱波增强)插入数据层并同步各视图。
|
||||
* @param [IN] request 构建请求。
|
||||
* @param [IN] makeVisible 是否在当前标签页中勾选新信号。
|
||||
* @return 构建并插入成功返回 true。
|
||||
*/
|
||||
bool BuildDerivedSignal(int fileIndex, int signalIndex, int points, int order,
|
||||
bool diffAmp,
|
||||
bool makeVisible);
|
||||
bool BuildDerivedSignal(const DerivedSignalRequest& request, bool makeVisible);
|
||||
|
||||
/**
|
||||
* @brief 弹出文件选择对话框并加载所选文件。
|
||||
|
||||
@@ -1461,71 +1461,67 @@ namespace jdi
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Application::BuildDerivedSignal(int fileIndex, int signalIndex, int points, int order,
|
||||
bool diffAmp, bool makeVisible)
|
||||
bool Application::BuildDerivedSignal(const DerivedSignalRequest& request, bool makeVisible)
|
||||
{
|
||||
const DataFile* file = m_dataSet.FileAt(fileIndex);
|
||||
if (file == nullptr || signalIndex < 0 || signalIndex >= static_cast<int>(file->signals.size()))
|
||||
const DataFile* file = m_dataSet.FileAt(request.fileIndex);
|
||||
if (file == nullptr || request.signalIndex < 0 ||
|
||||
request.signalIndex >= static_cast<int>(file->signals.size()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
const SignalData& source = file->signals[static_cast<std::size_t>(signalIndex)];
|
||||
const SignalData& source = file->signals[static_cast<std::size_t>(request.signalIndex)];
|
||||
const std::wstring baseName = source.Title();
|
||||
|
||||
// 1. 计算派生采样值(与网格内的预览曲线共用同一实现,因此逐位一致)。
|
||||
// 多阶均值滤波形如「<原信号名称> (N点均值×M阶)」,
|
||||
// 弱波增强形如「<原信号名称> (弱波增强 M%)」。
|
||||
std::vector<float> derived_values;
|
||||
if (diffAmp)
|
||||
{
|
||||
ComputeDiffAmplify(source.values, points, order, derived_values);
|
||||
}
|
||||
else
|
||||
{
|
||||
ComputeMovingAverageIterated(source.values, points, order, derived_values);
|
||||
}
|
||||
|
||||
// 2. 组装新信号:均值滤波形如「<原信号名称> (N点均值×M阶)」,
|
||||
// 差异放大形如「<原信号名称> (X点差异放大×N倍)」。
|
||||
SignalData derived;
|
||||
if (diffAmp)
|
||||
if (request.weakBoost)
|
||||
{
|
||||
derived.name = baseName + L" (" + std::to_wstring(points) + L"点差异放大×" +
|
||||
std::to_wstring(order) + L"倍)";
|
||||
ComputeWeakWaveBoost(source.values, static_cast<double>(request.levelPercent) / 100.0,
|
||||
derived_values);
|
||||
derived.name = baseName + L" (弱波增强 " + std::to_wstring(request.levelPercent) + L"%)";
|
||||
}
|
||||
else
|
||||
{
|
||||
derived.name = baseName + L" (" + std::to_wstring(points) + L"点均值";
|
||||
if (order > 1)
|
||||
ComputeMovingAverageIterated(source.values, request.points, request.order, derived_values);
|
||||
derived.name = baseName + L" (" + std::to_wstring(request.points) + L"点均值";
|
||||
if (request.order > 1)
|
||||
{
|
||||
derived.name += L"×" + std::to_wstring(order) + L"阶";
|
||||
derived.name += L"×" + std::to_wstring(request.order) + L"阶";
|
||||
}
|
||||
derived.name += L")";
|
||||
}
|
||||
|
||||
// 2. 组装新信号。
|
||||
derived.rawHeader = derived.name;
|
||||
derived.unit = source.unit;
|
||||
derived.values.assign(derived_values.begin(), derived_values.end());
|
||||
|
||||
// 3. 插入到原信号的下方(数据层 + 各标签页引用重映射)。
|
||||
int newIndex = -1;
|
||||
if (!m_dataSet.InsertDerivedSignal(fileIndex, signalIndex, std::move(derived), newIndex))
|
||||
if (!m_dataSet.InsertDerivedSignal(request.fileIndex, request.signalIndex, std::move(derived),
|
||||
newIndex))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
for (TabConfig& tab : m_tabs)
|
||||
{
|
||||
tab.RemapAfterSignalInsert(fileIndex, newIndex);
|
||||
tab.RemapAfterSignalInsert(request.fileIndex, newIndex);
|
||||
}
|
||||
|
||||
// 4. 在当前标签页中勾选新信号,并把它所在的网格移到原信号网格的下方。
|
||||
if (makeVisible && !m_tabs.empty())
|
||||
{
|
||||
SignalRef derivedRef;
|
||||
derivedRef.fileIndex = fileIndex;
|
||||
derivedRef.fileIndex = request.fileIndex;
|
||||
derivedRef.signalIndex = newIndex;
|
||||
ActiveTab().SetVisible(derivedRef, true);
|
||||
|
||||
SignalRef sourceRef;
|
||||
sourceRef.fileIndex = fileIndex;
|
||||
sourceRef.signalIndex = signalIndex;
|
||||
sourceRef.fileIndex = request.fileIndex;
|
||||
sourceRef.signalIndex = request.signalIndex;
|
||||
ActiveTab().MoveLaneOfSignalBelow(derivedRef, sourceRef);
|
||||
}
|
||||
|
||||
@@ -1536,18 +1532,9 @@ namespace jdi
|
||||
|
||||
bool Application::ApplyPendingDerivedBuild()
|
||||
{
|
||||
// 两类「构建为新信号」请求(均值滤波 / 差异放大)机制完全一致,
|
||||
// 两类「构建为新信号」请求(均值滤波 / 弱波增强)机制完全一致,
|
||||
// 统一整理成同一份构建清单后按下标降序处理。
|
||||
struct BuildRequest
|
||||
{
|
||||
int fileIndex = -1;
|
||||
int signalIndex = -1;
|
||||
int first = 0;
|
||||
int second = 0;
|
||||
bool diffAmp = false;
|
||||
};
|
||||
|
||||
std::vector<BuildRequest> requests;
|
||||
std::vector<DerivedSignalRequest> requests;
|
||||
{
|
||||
int laneIndex = -1;
|
||||
std::vector<PlotView::MeanFilterBuild> items;
|
||||
@@ -1555,29 +1542,28 @@ namespace jdi
|
||||
{
|
||||
for (const PlotView::MeanFilterBuild& item : items)
|
||||
{
|
||||
BuildRequest request;
|
||||
DerivedSignalRequest request;
|
||||
request.fileIndex = item.fileIndex;
|
||||
request.signalIndex = item.signalIndex;
|
||||
request.first = item.points;
|
||||
request.second = item.order;
|
||||
request.diffAmp = false;
|
||||
request.points = item.points;
|
||||
request.order = item.order;
|
||||
request.weakBoost = false;
|
||||
requests.push_back(request);
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
int laneIndex = -1;
|
||||
std::vector<PlotView::DiffAmpBuild> items;
|
||||
if (m_plot.TakePendingDiffAmpBuild(laneIndex, items))
|
||||
std::vector<PlotView::WeakBoostBuild> items;
|
||||
if (m_plot.TakePendingWeakBoostBuild(laneIndex, items))
|
||||
{
|
||||
for (const PlotView::DiffAmpBuild& item : items)
|
||||
for (const PlotView::WeakBoostBuild& item : items)
|
||||
{
|
||||
BuildRequest request;
|
||||
DerivedSignalRequest request;
|
||||
request.fileIndex = item.fileIndex;
|
||||
request.signalIndex = item.signalIndex;
|
||||
request.first = item.points;
|
||||
request.second = item.gain;
|
||||
request.diffAmp = true;
|
||||
request.levelPercent = item.levelPercent;
|
||||
request.weakBoost = true;
|
||||
requests.push_back(request);
|
||||
}
|
||||
}
|
||||
@@ -1594,7 +1580,7 @@ namespace jdi
|
||||
// **同一文件内必须按下标降序处理**:每次插入只让插入点之后的信号下标 +1,
|
||||
// 降序处理可保证尚未处理的信号下标始终有效(升序会把新插入的信号当成原信号)。
|
||||
std::sort(requests.begin(), requests.end(),
|
||||
[](const BuildRequest& a, const BuildRequest& b)
|
||||
[](const DerivedSignalRequest& a, const DerivedSignalRequest& b)
|
||||
{
|
||||
if (a.fileIndex != b.fileIndex)
|
||||
{
|
||||
@@ -1604,35 +1590,29 @@ namespace jdi
|
||||
});
|
||||
|
||||
int built = 0;
|
||||
int lastFirst = 0;
|
||||
int lastSecond = 1;
|
||||
bool lastDiffAmp = false;
|
||||
for (const BuildRequest& request : requests)
|
||||
DerivedSignalRequest last;
|
||||
for (const DerivedSignalRequest& request : requests)
|
||||
{
|
||||
if (BuildDerivedSignal(request.fileIndex, request.signalIndex, request.first,
|
||||
request.second, request.diffAmp, true))
|
||||
if (BuildDerivedSignal(request, true))
|
||||
{
|
||||
++built;
|
||||
lastFirst = request.first;
|
||||
lastSecond = request.second;
|
||||
lastDiffAmp = request.diffAmp;
|
||||
last = request;
|
||||
}
|
||||
}
|
||||
|
||||
if (built > 0)
|
||||
{
|
||||
std::wstring text = L"已构建 " + std::to_wstring(built) + L" 条 ";
|
||||
if (lastDiffAmp)
|
||||
if (last.weakBoost)
|
||||
{
|
||||
text += std::to_wstring(lastFirst) + L"点差异放大×" +
|
||||
std::to_wstring(lastSecond) + L"倍信号。";
|
||||
text += L"弱波增强 " + std::to_wstring(last.levelPercent) + L"% 信号。";
|
||||
}
|
||||
else
|
||||
{
|
||||
text += std::to_wstring(lastFirst) + L"点均值";
|
||||
if (lastSecond > 1)
|
||||
text += std::to_wstring(last.points) + L"点均值";
|
||||
if (last.order > 1)
|
||||
{
|
||||
text += L"×" + std::to_wstring(lastSecond) + L"阶";
|
||||
text += L"×" + std::to_wstring(last.order) + L"阶";
|
||||
}
|
||||
text += L"滤波信号。";
|
||||
}
|
||||
@@ -1769,11 +1749,11 @@ namespace jdi
|
||||
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);
|
||||
MenuItem weakBoost;
|
||||
weakBoost.label = L"弱波增强";
|
||||
weakBoost.id = PLOT_CMD_LANE_WEAK_BOOST;
|
||||
weakBoost.checked = m_plot.LaneWeakBoost(laneIndex);
|
||||
experimental.submenu.push_back(weakBoost);
|
||||
}
|
||||
items.push_back(experimental);
|
||||
|
||||
@@ -1888,9 +1868,9 @@ namespace jdi
|
||||
// 「实验性功能 → 均值滤波」:切换均值滤波曲线与参数面板。
|
||||
m_plot.ToggleLaneMeanFilter(laneIndex);
|
||||
break;
|
||||
case PLOT_CMD_LANE_DIFF_AMP:
|
||||
// 「实验性功能 → 差异放大」:切换差异放大曲线与参数面板。
|
||||
m_plot.ToggleLaneDiffAmp(laneIndex);
|
||||
case PLOT_CMD_LANE_WEAK_BOOST:
|
||||
// 「实验性功能 → 弱波增强」:切换弱波增强曲线与参数面板。
|
||||
m_plot.ToggleLaneWeakBoost(laneIndex);
|
||||
break;
|
||||
case PLOT_CMD_LANE_SPLIT:
|
||||
// 「拆分当前网格」:信号按个拆成独立网格,新网格顺次插入其下方。
|
||||
@@ -2390,7 +2370,7 @@ namespace jdi
|
||||
{
|
||||
// 绘图区内的点击也可能提交图例改名,需要立刻落到数据层。
|
||||
ApplyPendingRename();
|
||||
// 「均值滤波 → 构建为新信号」按钮:立即构建新信号并同步各视图。
|
||||
// 「构建为新信号」按钮(均值滤波 / 弱波增强):立即构建新信号并同步各视图。
|
||||
ApplyPendingDerivedBuild();
|
||||
InvalidateRect(hwnd, nullptr, FALSE);
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ namespace jdi
|
||||
MOVE_SIGNAL, /**< 拖拽信号曲线到其它网格。 */
|
||||
EDGE_MOVE, /**< 拖动「边沿计数」的横向光标(上下移动幅值)。 */
|
||||
EDGE_PANEL_MOVE, /**< 拖动边沿计数浮动面板。 */
|
||||
BOOST_LEVEL, /**< 拖动「弱波增强」面板中的等级滑块。 */
|
||||
LANE_REORDER /**< 拖动网格图例中的 GX 编号徽标:上下调整网格次序。 */
|
||||
};
|
||||
|
||||
@@ -57,9 +58,7 @@ namespace jdi
|
||||
EDGE_CENTER_POINTS, /**< 编辑「中心轴边沿计数」的均值点数 X。 */
|
||||
EDGE_CENTER_ORDER, /**< 编辑「中心轴边沿计数」的阶数 N。 */
|
||||
MEAN_FILTER_POINTS, /**< 编辑「均值滤波」的均值点数 X。 */
|
||||
MEAN_FILTER_ORDER, /**< 编辑「均值滤波」的阶数 N。 */
|
||||
DIFF_AMP_POINTS, /**< 编辑「差异放大」的窗口点数 X。 */
|
||||
DIFF_AMP_ORDER /**< 编辑「差异放大」的放大倍数 N。 */
|
||||
MEAN_FILTER_ORDER /**< 编辑「均值滤波」的阶数 N。 */
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -77,7 +76,7 @@ namespace jdi
|
||||
PLOT_CMD_LANE_EDGE_CENTER = 8, /**< 切换本网格的「实验性功能 → 边沿计数(中心轴)」开关。 */
|
||||
PLOT_CMD_LANE_SPLIT = 9, /**< 拆分当前网格:把网格内信号按个拆成独立网格并顺次下移编号。 */
|
||||
PLOT_CMD_LANE_MEAN_FILTER = 10, /**< 切换本网格的「实验性功能 → 均值滤波」开关。 */
|
||||
PLOT_CMD_LANE_DIFF_AMP = 11 /**< 切换本网格的「实验性功能 → 差异放大」开关。 */
|
||||
PLOT_CMD_LANE_WEAK_BOOST = 11 /**< 切换本网格的「实验性功能 → 弱波增强」开关。 */
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -88,7 +87,7 @@ namespace jdi
|
||||
COUNT = 0, /**< 边沿计数。 */
|
||||
CENTER = 1, /**< 边沿计数(中心轴)。 */
|
||||
MEAN = 2, /**< 均值滤波。 */
|
||||
DIFF_AMP = 3 /**< 差异放大。 */
|
||||
BOOST = 3 /**< 弱波增强。 */
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -523,84 +522,96 @@ namespace jdi
|
||||
bool TakePendingMeanFilterBuild(int& laneIndex, std::vector<MeanFilterBuild>& items);
|
||||
|
||||
/**
|
||||
* @brief 「实验性功能 → 差异放大」的「构建为新信号」单个请求
|
||||
* (一个被勾选的信号 + 自己的参数)。
|
||||
* @brief 「实验性功能 → 弱波增强」的「构建为新信号」单个请求
|
||||
* (一个被勾选的信号 + 整个网格共用的增强等级)。
|
||||
*/
|
||||
struct DiffAmpBuild
|
||||
struct WeakBoostBuild
|
||||
{
|
||||
int fileIndex = -1; /**< 信号所属文件下标。 */
|
||||
int signalIndex = -1; /**< 文件内信号下标。 */
|
||||
int points = 80; /**< 窗口点数 X(略大于纹波周期的一半)。 */
|
||||
int gain = 2; /**< 放大倍数 N。 */
|
||||
int levelPercent = 50; /**< 弱波增强等级(0–100)。 */
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 取出「差异放大 → 构建为新信号」请求(由面板底部按钮触发)。
|
||||
* @brief 取出「弱波增强 → 构建为新信号」请求(由面板底部按钮触发)。
|
||||
* @param [OUT] laneIndex 触发该请求的网格下标。
|
||||
* @param [OUT] items 需要构建的信号及其参数(按信号在网格内的顺序)。
|
||||
* @return 存在待处理请求返回 true(取出后清空)。
|
||||
*/
|
||||
bool TakePendingDiffAmpBuild(int& laneIndex, std::vector<DiffAmpBuild>& items);
|
||||
bool TakePendingWeakBoostBuild(int& laneIndex, std::vector<WeakBoostBuild>& items);
|
||||
|
||||
/**
|
||||
* @brief 切换某网格的「差异放大」开关。
|
||||
* @brief 切换某网格的「弱波增强」开关。
|
||||
* @param [IN] laneIndex 网格下标。
|
||||
* @param [IN] enabled 是否开启。
|
||||
* @return 状态发生变化返回 true。
|
||||
*/
|
||||
bool SetLaneDiffAmp(int laneIndex, bool enabled);
|
||||
bool SetLaneWeakBoost(int laneIndex, bool enabled);
|
||||
|
||||
/**
|
||||
* @brief 查询某网格的「差异放大」开关。
|
||||
* @brief 查询某网格的「弱波增强」开关。
|
||||
* @param [IN] laneIndex 网格下标。
|
||||
* @return 已开启返回 true。
|
||||
*/
|
||||
bool LaneDiffAmp(int laneIndex) const;
|
||||
bool LaneWeakBoost(int laneIndex) const;
|
||||
|
||||
/**
|
||||
* @brief 切换某网格的「差异放大」开关。
|
||||
* @brief 切换某网格的「弱波增强」开关。
|
||||
* @param [IN] laneIndex 网格下标。
|
||||
* @return 切换后的状态。
|
||||
*/
|
||||
bool ToggleLaneDiffAmp(int laneIndex);
|
||||
bool ToggleLaneWeakBoost(int laneIndex);
|
||||
|
||||
/**
|
||||
* @brief 获取差异放大曲线(按「信号 + 窗口点数 + 放大倍数」缓存)。
|
||||
* @brief 读取某网格的弱波增强等级(0–1)。
|
||||
* @param [IN] laneIndex 网格下标。
|
||||
* @return 增强等级;网格不存在时返回 0.5。
|
||||
*/
|
||||
double LaneWeakBoostLevel(int laneIndex) const;
|
||||
|
||||
/**
|
||||
* @brief 设置某网格的弱波增强等级(0–1,超出范围会被夹取)。
|
||||
* @param [IN] laneIndex 网格下标。
|
||||
* @param [IN] level 增强等级。
|
||||
* @return 等级发生变化返回 true。
|
||||
*/
|
||||
bool SetLaneWeakBoostLevel(int laneIndex, double level);
|
||||
|
||||
/**
|
||||
* @brief 获取弱波增强曲线(按「信号 + 增强等级」缓存)。
|
||||
* @param [IN] signal 目标信号。
|
||||
* @param [IN] points 窗口点数 X。
|
||||
* @param [IN] gain 放大倍数 N。
|
||||
* @param [IN] levelPercent 弱波增强等级(0–100,整数百分比)。
|
||||
* @return 曲线数值数组(长度与 `signal.values` 相同;缺失处为 NaN)。
|
||||
*/
|
||||
const std::vector<float>& DiffAmpCurveFor(const SignalData& signal, int points, int gain) const;
|
||||
const std::vector<float>& WeakBoostCurveFor(const SignalData& signal, int levelPercent) const;
|
||||
|
||||
/**
|
||||
* @brief 取某个时刻的差异放大曲线取值(用于纵向光标的取值标注)。
|
||||
* @brief 取某个时刻的弱波增强曲线取值(用于纵向光标的取值标注)。
|
||||
* @param [IN] file 信号所属文件。
|
||||
* @param [IN] signal 目标信号。
|
||||
* @param [IN] points 窗口点数 X(略大于纹波周期的一半)。
|
||||
* @param [IN] gain 放大倍数 N。
|
||||
* @param [IN] levelPercent 弱波增强等级(0–100)。
|
||||
* @param [IN] time 采样时刻。
|
||||
* @param [OUT] value 输出取值。
|
||||
* @return 取到有效值返回 true。
|
||||
*/
|
||||
bool DiffAmpValueAt(const DataFile& file, const SignalData& signal, int points, int gain,
|
||||
bool WeakBoostValueAt(const DataFile& file, const SignalData& signal, int levelPercent,
|
||||
double time, double& value) const;
|
||||
|
||||
/**
|
||||
* @brief 切换某个信号在「差异放大」面板中的预览勾选状态。
|
||||
* @brief 切换某个信号在「弱波增强」面板中的预览勾选状态。
|
||||
* @param [IN] laneIndex 网格下标。
|
||||
* @param [IN] ref 目标信号。
|
||||
* @return 切换后的勾选状态。
|
||||
*/
|
||||
bool ToggleLaneDiffAmpSignal(int laneIndex, const SignalRef& ref);
|
||||
bool ToggleLaneWeakBoostSignal(int laneIndex, const SignalRef& ref);
|
||||
|
||||
/**
|
||||
* @brief 查询某个信号在「差异放大」面板中的预览勾选状态。
|
||||
* @brief 查询某个信号在「弱波增强」面板中的预览勾选状态。
|
||||
* @param [IN] laneIndex 网格下标。
|
||||
* @param [IN] ref 目标信号。
|
||||
* @return 已勾选返回 true。
|
||||
*/
|
||||
bool LaneDiffAmpSignalEnabled(int laneIndex, const SignalRef& ref) const;
|
||||
bool LaneWeakBoostSignalEnabled(int laneIndex, const SignalRef& ref) const;
|
||||
|
||||
/**
|
||||
* @brief 直接把文本解析为网格 Y 轴上限/下限并应用(供菜单内联输入框提交使用)。
|
||||
@@ -1052,6 +1063,7 @@ namespace jdi
|
||||
gd::RectF close; /**< ✕ 关闭按钮。 */
|
||||
gd::RectF edit; /**< 面板底部编辑行(无编辑内容时宽度为 0)。 */
|
||||
gd::RectF button; /**< 面板底部按钮(无按钮时宽度为 0)。 */
|
||||
gd::RectF slider; /**< 「弱波增强」等级滑块的轨道矩形(无滑块时宽度为 0)。 */
|
||||
int rows = 0; /**< 统计行数(每条可见信号一行)。 */
|
||||
/**
|
||||
* @brief 「逐条目」面板的矩形(MEAN 与 CENTER 面板共用同一套排布)。
|
||||
@@ -1176,15 +1188,14 @@ namespace jdi
|
||||
int points, int order) const;
|
||||
|
||||
/**
|
||||
* @brief 绘制某条信号的差异放大曲线(与均值滤波曲线同一配色派生规则)。
|
||||
* @brief 绘制某条信号的弱波增强曲线(与均值滤波曲线同一配色派生规则)。
|
||||
* @param [IN,OUT] g 绘图上下文。
|
||||
* @param [IN] resolved 信号视图。
|
||||
* @param [IN] lane 网格绘制信息。
|
||||
* @param [IN] points 窗口点数 X(局部差异强度统计半径)。
|
||||
* @param [IN] gain 放大倍数 N(最大增益)。
|
||||
* @param [IN] levelPercent 弱波增强等级(0–100)。
|
||||
*/
|
||||
void PaintDiffAmpCurve(gd::Graphics& g, const ResolvedSignal& resolved, const LaneVisual& lane,
|
||||
int points, int gain) const;
|
||||
void PaintWeakBoostCurve(gd::Graphics& g, const ResolvedSignal& resolved, const LaneVisual& lane,
|
||||
int levelPercent) const;
|
||||
|
||||
/**
|
||||
* @brief 数据点标记的位置与"越界方向"(用于决定画圆点还是贴边三角)。
|
||||
@@ -1400,9 +1411,10 @@ namespace jdi
|
||||
bool m_pendingRename = false; /**< 是否存在待应用的重命名请求。 */
|
||||
|
||||
int m_pendingMeanFilterLane = -1; /**< 待构建的均值滤波信号所属网格下标(-1 表示无请求)。 */
|
||||
int m_pendingDiffAmpLane = -1; /**< 待构建的差异放大信号所属网格下标(-1 表示无请求)。 */
|
||||
int m_pendingWeakBoostLane = -1; /**< 待构建的弱波增强信号所属网格下标(-1 表示无请求)。 */
|
||||
std::vector<MeanFilterBuild> m_pendingMeanFilterItems; /**< 待构建的信号及其参数。 */
|
||||
std::vector<DiffAmpBuild> m_pendingDiffAmpItems; /**< 待构建的差异放大信号及其参数。 */
|
||||
std::vector<WeakBoostBuild> m_pendingWeakBoostItems; /**< 待构建的弱波增强信号及其参数。 */
|
||||
int m_boostDragLane = -1; /**< 正在拖动等级滑块的面板所属网格。 */
|
||||
|
||||
/**
|
||||
* @brief 实验性功能浮动面板的 ✕ 处于"已按下、等待抬起确认"状态。
|
||||
@@ -1458,15 +1470,14 @@ namespace jdi
|
||||
};
|
||||
mutable std::deque<MeanCurveEntry> m_meanCurveCache;
|
||||
|
||||
/** 多阶均值滤波曲线缓存项(按「信号指针 + 均值点数 + 阶数」标识)。 */
|
||||
struct DiffAmpCurveEntry
|
||||
/** 弱波增强曲线缓存项(按「信号指针 + 增强等级百分比」标识)。 */
|
||||
struct WeakBoostCurveEntry
|
||||
{
|
||||
const SignalData* signal = nullptr;
|
||||
int points = 80; /**< 窗口点数 X。 */
|
||||
int gain = 2; /**< 放大倍数 N。 */
|
||||
int levelPercent = 50; /**< 弱波增强等级(0–100)。 */
|
||||
std::vector<float> values; /**< 与 signal->values 等长;缺失处为 NaN。 */
|
||||
};
|
||||
mutable std::deque<DiffAmpCurveEntry> m_diffAmpCurveCache;
|
||||
mutable std::deque<WeakBoostCurveEntry> m_weakBoostCurveCache;
|
||||
|
||||
/** 中心轴边沿统计缓存项(增量统计机制与 `EdgeCountEntry` 完全一致)。 */
|
||||
struct EdgeCenterCountEntry
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
+56
-71
@@ -374,13 +374,13 @@ namespace jdi
|
||||
using EdgeCenterItem = MeanFilterItem;
|
||||
|
||||
/**
|
||||
* @brief 「差异放大」中某个信号自己的参数。
|
||||
* @brief 「弱波增强」中某个信号自己的条目。
|
||||
*
|
||||
* 字段含义与均值滤波/中心轴不同(但同样是「一个信号 + 两个整数参数」,
|
||||
* 因此复用同一结构以共享面板、缓存与引用重映射的实现):
|
||||
* `points` = 窗口点数 X(趋势窗口半径),`order` = 放大倍数 N。
|
||||
* 目前只用到 `ref`(增强等级属于整个网格,统一保存在 `weakBoostLevel` 中);
|
||||
* 复用 `MeanFilterItem` 结构是为了与均值滤波/中心轴共享
|
||||
* 「条目查找 + 信号插入/删除时的引用重映射」等既有实现。
|
||||
*/
|
||||
using DiffAmpItem = MeanFilterItem;
|
||||
using WeakBoostItem = MeanFilterItem;
|
||||
|
||||
std::wstring name; /**< 网格名称。 */
|
||||
std::vector<SignalRef> signals; /**< 该网格包含的信号(按加入顺序)。 */
|
||||
@@ -501,46 +501,44 @@ namespace jdi
|
||||
double meanFilterPanelY = -1.0;
|
||||
|
||||
/**
|
||||
* @brief 「实验性功能 → 差异放大」开关。
|
||||
* @brief 「实验性功能 → 弱波增强」开关。
|
||||
*
|
||||
* 开启后为本网格内**被勾选**的信号额外绘制一条「差异放大」曲线:
|
||||
* 把信号与局部趋势线的差值按“局部差异强度”归一化后放大,
|
||||
* 使不明显的下凹(如纹波里的弱 M 形)变明显,而差异已经很大的区域**逐点不变**。
|
||||
* 开启后为本网格内**被勾选**的信号额外绘制一条「弱波增强」曲线:
|
||||
* 识别纹波的波峰/波谷,把“比左右同类极值更弱”的波峰上拉、波谷下拉,
|
||||
* 并按「弱波增强等级」在左右两个同类极值之间取目标值,使其恢复与周边一致的振幅。
|
||||
* 这条曲线只是叠加显示,不会作为信号加入左侧列表。
|
||||
*/
|
||||
bool diffAmp = false;
|
||||
bool weakBoost = false;
|
||||
|
||||
/**
|
||||
* @brief 参与差异放大预览的信号及其参数(只包含被勾选的信号)。
|
||||
*
|
||||
* `points` = 窗口点数 X(局部差异强度统计半径,约取纹波周期的一半),
|
||||
* `order` = 放大倍数 N(最大放大倍数)。
|
||||
*/
|
||||
std::vector<DiffAmpItem> diffAmpItems;
|
||||
/** 弱波增强等级(0–1),0% = 目标取左右极值中较小/较大的那一个,100% = 取另一个。 */
|
||||
double weakBoostLevel = 0.5;
|
||||
|
||||
/** 取某个信号的差异放大参数(未勾选时返回空指针)。 */
|
||||
const DiffAmpItem* FindDiffAmpItem(const SignalRef& ref) const;
|
||||
/** 参与弱波增强预览的信号条目(只包含被勾选的信号)。 */
|
||||
std::vector<WeakBoostItem> weakBoostItems;
|
||||
|
||||
/** 取得或创建某信号的差异放大参数(用于勾选)。 */
|
||||
DiffAmpItem& EnsureDiffAmpItem(const SignalRef& ref);
|
||||
/** 取某个信号的弱波增强条目(未勾选时返回空指针)。 */
|
||||
const WeakBoostItem* FindWeakBoostItem(const SignalRef& ref) const;
|
||||
|
||||
/** 取消某信号的差异放大勾选。 */
|
||||
void RemoveDiffAmpItem(const SignalRef& ref);
|
||||
/** 取得或创建某信号的弱波增强条目(用于勾选)。 */
|
||||
WeakBoostItem& EnsureWeakBoostItem(const SignalRef& ref);
|
||||
|
||||
/** 取某信号的差异放大勾选状态。 */
|
||||
bool DiffAmpEnabled(const SignalRef& ref) const
|
||||
/** 取消某信号的弱波增强勾选。 */
|
||||
void RemoveWeakBoostItem(const SignalRef& ref);
|
||||
|
||||
/** 取某信号的弱波增强勾选状态。 */
|
||||
bool WeakBoostEnabled(const SignalRef& ref) const
|
||||
{
|
||||
return FindDiffAmpItem(ref) != nullptr;
|
||||
return FindWeakBoostItem(ref) != nullptr;
|
||||
}
|
||||
|
||||
/** 差异放大浮动面板是否展开。 */
|
||||
bool diffAmpPanelOpen = true;
|
||||
/** 弱波增强浮动面板是否展开。 */
|
||||
bool weakBoostPanelOpen = true;
|
||||
|
||||
/** 差异放大面板左上角位置(客户区像素;<0 表示自动定位)。 */
|
||||
double diffAmpPanelX = -1.0;
|
||||
/** 弱波增强面板左上角位置(客户区像素;<0 表示自动定位)。 */
|
||||
double weakBoostPanelX = -1.0;
|
||||
|
||||
/** 差异放大面板左上角纵坐标(<0 表示自动定位)。 */
|
||||
double diffAmpPanelY = -1.0;
|
||||
/** 弱波增强面板左上角纵坐标(<0 表示自动定位)。 */
|
||||
double weakBoostPanelY = -1.0;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -570,45 +568,6 @@ namespace jdi
|
||||
*/
|
||||
void ComputeMovingAverage(const std::vector<double>& values, int radius, std::vector<float>& out);
|
||||
|
||||
/**
|
||||
* @brief 计算 N 点滑动平均(float 输入版本)。
|
||||
*
|
||||
* 与 `ComputeMovingAverage(const std::vector<double>&, ...)` 完全同一实现,
|
||||
* 供「差异放大」在 float 缓冲上二次滑动平均使用:亿级采样下若用 double 中转
|
||||
* 会多占一倍内存,而曲线本身只要求 float 精度。
|
||||
*
|
||||
* @param [IN] values 原始采样值。
|
||||
* @param [IN] radius 平滑半径 N(0 表示原样返回)。
|
||||
* @param [OUT] out 输出数组(长度与 values 相同)。
|
||||
*/
|
||||
void ComputeMovingAverage(const std::vector<float>& values, int radius, std::vector<float>& out);
|
||||
|
||||
/**
|
||||
* @brief 「差异放大」:把不明显的局部差异放大,而差异已经很大的区域逐点不变。
|
||||
*
|
||||
* 算法(三部分,全部 O(n)):
|
||||
* 1. **趋势线** `trend` = 以 8×window 为半径的滑动平均 —— 把“整体”(低频成分)分离出来;
|
||||
* 该半径必须远大于纹波周期,趋势线才不带着纹波。
|
||||
* 2. **局部差异强度** `strength` = |采样值 − 趋势线| 的滑动平均(半径 = window)
|
||||
* —— 衡量该处“纹波有多明显”。窗口恰好覆盖一个纹波周期时该值与相位无关
|
||||
* (正弦为 2A/π),因此 `window` 应取**纹波周期的一半左右**;
|
||||
* 3. **逐点增益** `gain = clamp(目标强度 / 该处强度, 1, 放大倍数)`,
|
||||
* 目标强度取全信号差异强度的 75 分位(用直方图统计,O(1) 额外内存;
|
||||
* 用均值会被个别尖峰抬高,用最大值则完全被最尖处主导)。
|
||||
*
|
||||
* 输出 `out = trend + gain × (采样值 − trend)`。
|
||||
* **增益下界为 1**,所以差异强度不低于目标强度的采样点(即绝大多数“正常”数据点)
|
||||
* 输出与原值逐位相同 —— 这正是“不影响其它数据点的振幅范围”的实现方式;
|
||||
* 只有强度低于目标强度的区域(如被削弱的那个周期)才被放大,最多放大到 `gain` 倍。
|
||||
*
|
||||
* @param [IN] values 原始采样值。
|
||||
* @param [IN] window 窗口点数 X(局部差异强度统计半径,应取纹波周期的一半左右)。
|
||||
* @param [IN] gain 放大倍数 N(最大增益,至少 1)。
|
||||
* @param [OUT] out 输出数组(长度与 values 相同;原值为 NaN 处保持 NaN)。
|
||||
*/
|
||||
void ComputeDiffAmplify(const std::vector<double>& values, int window, int gain,
|
||||
std::vector<float>& out);
|
||||
|
||||
/**
|
||||
* @brief 计算多阶(迭代)滑动平均:把 N 点滑动平均重复应用 `order` 次。
|
||||
*
|
||||
@@ -623,6 +582,32 @@ namespace jdi
|
||||
void ComputeMovingAverageIterated(const std::vector<double>& values, int radius, int order,
|
||||
std::vector<float>& out);
|
||||
|
||||
/**
|
||||
* @brief 「弱波增强」:把比周边更弱的纹波波峰/波谷恢复(自适应弱波增强)。
|
||||
*
|
||||
* 思路(全部 O(n)):
|
||||
* 1. **识别趋势与极值**:对轻度平滑后的信号扫描单调段,方向发生翻转处即为
|
||||
* 波峰(先升后降)或波谷(先降后升);波峰与波谷交替出现。
|
||||
* 2. **判定“弱”**:对第 k 个波峰,取同类的左右相邻波峰(k−2、k+2)。
|
||||
* 若本波峰低于两者的均值,说明它是被削弱的那个;波谷反之(高于两者均值)。
|
||||
* 3. **目标值**(`level` = 弱波增强等级,0–1):
|
||||
* * 波峰:左右两个波峰中较小者为 0%、较大者为 100%,
|
||||
* 目标 = min + level × (max − min);
|
||||
* * 波谷:左右两个波谷中较大者为 0%、较小者为 100%,
|
||||
* 目标 = max − level × (max − min)。
|
||||
* 4. **平滑偏移**:极值点本身取其目标值,两侧的数据点按相邻两个极值点的偏移量
|
||||
* 平滑过渡(平滑步进,两端导数为 0),使波形整体趋势不变、只在弱波附近被推回。
|
||||
*
|
||||
* 与“全局缩放/局部放大”不同,这里**只改动被判为弱波的极值及其两侧**,
|
||||
* 正常周期的极值不满足条件、偏移量恒为 0,因此输出与输入逐位相同。
|
||||
*
|
||||
* @param [IN] values 原始采样值。
|
||||
* @param [IN] level 弱波增强等级(0–1,超出范围会被夹取)。
|
||||
* @param [OUT] out 输出数组(长度与 values 相同;原值为 NaN 处保持 NaN)。
|
||||
*/
|
||||
void ComputeWeakWaveBoost(const std::vector<double>& values, double level,
|
||||
std::vector<float>& out);
|
||||
|
||||
/**
|
||||
* @brief 在指定文件的时间轴上查找与给定时刻最接近的采样点。
|
||||
* @param [IN] file 目标文件。
|
||||
|
||||
+210
-324
@@ -134,11 +134,12 @@ namespace jdi
|
||||
bool meanFilterPanelOpen = true;
|
||||
double meanFilterPanelX = -1.0;
|
||||
double meanFilterPanelY = -1.0;
|
||||
bool diffAmp = false;
|
||||
std::vector<Lane::DiffAmpItem> diffAmpItems;
|
||||
bool diffAmpPanelOpen = true;
|
||||
double diffAmpPanelX = -1.0;
|
||||
double diffAmpPanelY = -1.0;
|
||||
bool weakBoost = false;
|
||||
std::vector<Lane::WeakBoostItem> weakBoostItems;
|
||||
double weakBoostLevel = 0.5;
|
||||
bool weakBoostPanelOpen = true;
|
||||
double weakBoostPanelX = -1.0;
|
||||
double weakBoostPanelY = -1.0;
|
||||
};
|
||||
|
||||
// 先记下每个信号原来所在网格的网格自身状态:
|
||||
@@ -164,11 +165,12 @@ namespace jdi
|
||||
carry.meanFilterPanelOpen = lane.meanFilterPanelOpen;
|
||||
carry.meanFilterPanelX = lane.meanFilterPanelX;
|
||||
carry.meanFilterPanelY = lane.meanFilterPanelY;
|
||||
carry.diffAmp = lane.diffAmp;
|
||||
carry.diffAmpItems = lane.diffAmpItems;
|
||||
carry.diffAmpPanelOpen = lane.diffAmpPanelOpen;
|
||||
carry.diffAmpPanelX = lane.diffAmpPanelX;
|
||||
carry.diffAmpPanelY = lane.diffAmpPanelY;
|
||||
carry.weakBoost = lane.weakBoost;
|
||||
carry.weakBoostItems = lane.weakBoostItems;
|
||||
carry.weakBoostLevel = lane.weakBoostLevel;
|
||||
carry.weakBoostPanelOpen = lane.weakBoostPanelOpen;
|
||||
carry.weakBoostPanelX = lane.weakBoostPanelX;
|
||||
carry.weakBoostPanelY = lane.weakBoostPanelY;
|
||||
for (const SignalRef& ref : lane.signals)
|
||||
{
|
||||
previousCarry.emplace_back(ref, carry);
|
||||
@@ -205,11 +207,12 @@ namespace jdi
|
||||
lane.meanFilterPanelOpen = carry.meanFilterPanelOpen;
|
||||
lane.meanFilterPanelX = carry.meanFilterPanelX;
|
||||
lane.meanFilterPanelY = carry.meanFilterPanelY;
|
||||
lane.diffAmp = carry.diffAmp;
|
||||
lane.diffAmpItems = carry.diffAmpItems;
|
||||
lane.diffAmpPanelOpen = carry.diffAmpPanelOpen;
|
||||
lane.diffAmpPanelX = carry.diffAmpPanelX;
|
||||
lane.diffAmpPanelY = carry.diffAmpPanelY;
|
||||
lane.weakBoost = carry.weakBoost;
|
||||
lane.weakBoostItems = carry.weakBoostItems;
|
||||
lane.weakBoostLevel = carry.weakBoostLevel;
|
||||
lane.weakBoostPanelOpen = carry.weakBoostPanelOpen;
|
||||
lane.weakBoostPanelX = carry.weakBoostPanelX;
|
||||
lane.weakBoostPanelY = carry.weakBoostPanelY;
|
||||
};
|
||||
|
||||
// 收集全部可见信号引用。
|
||||
@@ -917,60 +920,10 @@ namespace jdi
|
||||
* @brief 滑动平均的共用实现(输入只要可转成 double)。
|
||||
*
|
||||
* 窗口 = [max(0,i-N), min(count-1,i+N)],每前进一步只增减边界处的两个采样点,
|
||||
* 整体 O(n),不额外占用前缀和内存。“差异放大”需要在 float 缓冲上再做一次
|
||||
* 滑动平均,若先转成 double 会因为亿级采样多占一倍内存,因此做成模板。
|
||||
* 整体 O(n),不额外占用前缀和内存。
|
||||
*/
|
||||
template <typename T>
|
||||
void MovingAverageImpl(const std::vector<T>& values, int radius, std::vector<float>& out);
|
||||
/**
|
||||
* @brief 滑动窗口最大值(只要最大值)。
|
||||
*
|
||||
* 与 `SlidingExtrema` 同一个单调队列思路,但**不额外分配最小值数组**:
|
||||
* 亿级采样下一次全量计算会多占 100 MB 以上,而“差异放大”只需要最大值。
|
||||
*
|
||||
* @param [IN] values 采样值。
|
||||
* @param [IN] radius 邻域半径(窗口 = [i-radius, i+radius],两端按实际长度)。
|
||||
* @param [OUT] outMax 邻域最大值(整窗都是 NaN 时为 NaN)。
|
||||
*/
|
||||
template <typename T>
|
||||
void SlidingMax(const std::vector<T>& values, std::size_t radius, std::vector<float>& outMax)
|
||||
{
|
||||
const std::size_t count = values.size();
|
||||
outMax.assign(count, std::numeric_limits<float>::quiet_NaN());
|
||||
if (count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
std::deque<std::size_t> maxQueue; // 递减:队首最大。
|
||||
std::size_t filled = 0;
|
||||
for (std::size_t index = 0; index < count; ++index)
|
||||
{
|
||||
const std::size_t wantHigh = std::min(count - 1, index + radius);
|
||||
while (filled <= wantHigh)
|
||||
{
|
||||
const double incoming = values[filled];
|
||||
if (std::isfinite(incoming))
|
||||
{
|
||||
while (!maxQueue.empty() && values[maxQueue.back()] <= incoming)
|
||||
{
|
||||
maxQueue.pop_back();
|
||||
}
|
||||
maxQueue.push_back(filled);
|
||||
}
|
||||
++filled;
|
||||
}
|
||||
const std::size_t low = (index > radius) ? (index - radius) : 0;
|
||||
while (!maxQueue.empty() && maxQueue.front() < low)
|
||||
{
|
||||
maxQueue.pop_front();
|
||||
}
|
||||
if (!maxQueue.empty())
|
||||
{
|
||||
outMax[index] = static_cast<float>(values[maxQueue.front()]);
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void ComputeMovingAverage(const std::vector<double>& values, int radius, std::vector<float>& out)
|
||||
@@ -978,11 +931,6 @@ namespace jdi
|
||||
MovingAverageImpl(values, radius, out);
|
||||
}
|
||||
|
||||
void ComputeMovingAverage(const std::vector<float>& values, int radius, std::vector<float>& out)
|
||||
{
|
||||
MovingAverageImpl(values, radius, out);
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
template <typename T>
|
||||
@@ -1067,73 +1015,6 @@ namespace jdi
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 滑动窗口极值:一次求出每个中心点邻域(±radius)内的最小值与最大值。
|
||||
*
|
||||
* 两个单调队列(最小值队列递增、最大值队列递减)同步滑动,O(n) 时间,
|
||||
* 内存只有窗口长度。NaN 不参与(整窗都是 NaN 时输出 NaN)。
|
||||
*
|
||||
* @param [IN] values 采样值。
|
||||
* @param [IN] radius 邻域半径(窗口 = [i-radius, i+radius],两端按实际长度)。
|
||||
* @param [OUT] outMin 邻域最小值。
|
||||
* @param [OUT] outMax 邻域最大值。
|
||||
*/
|
||||
template <typename T>
|
||||
void SlidingExtrema(const std::vector<T>& values, std::size_t radius,
|
||||
std::vector<float>& outMin, std::vector<float>& outMax)
|
||||
{
|
||||
const std::size_t count = values.size();
|
||||
outMin.assign(count, std::numeric_limits<float>::quiet_NaN());
|
||||
outMax.assign(count, std::numeric_limits<float>::quiet_NaN());
|
||||
if (count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
std::deque<std::size_t> minQueue; // 递增:队首最小。
|
||||
std::deque<std::size_t> maxQueue; // 递减:队首最大。
|
||||
std::size_t filled = 0;
|
||||
for (std::size_t index = 0; index < count; ++index)
|
||||
{
|
||||
const std::size_t wantHigh = std::min(count - 1, index + radius);
|
||||
while (filled <= wantHigh)
|
||||
{
|
||||
const double incoming = values[filled];
|
||||
if (std::isfinite(incoming))
|
||||
{
|
||||
while (!minQueue.empty() && values[minQueue.back()] >= incoming)
|
||||
{
|
||||
minQueue.pop_back();
|
||||
}
|
||||
minQueue.push_back(filled);
|
||||
while (!maxQueue.empty() && values[maxQueue.back()] <= incoming)
|
||||
{
|
||||
maxQueue.pop_back();
|
||||
}
|
||||
maxQueue.push_back(filled);
|
||||
}
|
||||
++filled;
|
||||
}
|
||||
const std::size_t low = (index > radius) ? (index - radius) : 0;
|
||||
while (!minQueue.empty() && minQueue.front() < low)
|
||||
{
|
||||
minQueue.pop_front();
|
||||
}
|
||||
while (!maxQueue.empty() && maxQueue.front() < low)
|
||||
{
|
||||
maxQueue.pop_front();
|
||||
}
|
||||
if (!minQueue.empty())
|
||||
{
|
||||
outMin[index] = static_cast<float>(values[minQueue.front()]);
|
||||
}
|
||||
if (!maxQueue.empty())
|
||||
{
|
||||
outMax[index] = static_cast<float>(values[maxQueue.front()]);
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void ComputeMovingAverageIterated(const std::vector<double>& values, int radius, int order,
|
||||
@@ -1162,8 +1043,54 @@ namespace jdi
|
||||
}
|
||||
}
|
||||
|
||||
void ComputeDiffAmplify(const std::vector<double>& values, int window, int gain,
|
||||
std::vector<float>& out)
|
||||
namespace
|
||||
{
|
||||
/**
|
||||
* @brief 「弱波增强」内部使用的极值点(波峰 / 波谷)。
|
||||
*/
|
||||
struct BoostExtremum
|
||||
{
|
||||
std::size_t index = 0; /**< 采样下标。 */
|
||||
double value = 0.0; /**< 采样值。 */
|
||||
int type = 0; /**< +1 = 波峰,-1 = 波谷。 */
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 取用于“识别波峰/波谷”的轻度平滑值。
|
||||
*
|
||||
* 直接对原始采样做方向判定时,采样噪声会被当成大量假波峰/波谷,使增强曲线抖动;
|
||||
* 因此先看一下 5 点滑动平均(半径 2)之后的“形状”。这里按需就地计算,
|
||||
* 不额外分配与采样数同量级的缓冲。
|
||||
*
|
||||
* @param [IN] values 原始采样值。
|
||||
* @param [IN] index 采样下标。
|
||||
* @return 轻度平滑后的取值;邻域内没有有效值时返回 NaN。
|
||||
*/
|
||||
double BoostShapeAt(const std::vector<double>& values, std::size_t index)
|
||||
{
|
||||
const std::size_t count = values.size();
|
||||
const std::size_t low = (index > 2) ? (index - 2) : 0;
|
||||
const std::size_t high = std::min(count - 1, index + 2);
|
||||
double sum = 0.0;
|
||||
int used = 0;
|
||||
for (std::size_t i = low; i <= high; ++i)
|
||||
{
|
||||
const double value = values[i];
|
||||
if (std::isfinite(value))
|
||||
{
|
||||
sum += value;
|
||||
++used;
|
||||
}
|
||||
}
|
||||
if (used == 0)
|
||||
{
|
||||
return std::numeric_limits<double>::quiet_NaN();
|
||||
}
|
||||
return sum / static_cast<double>(used);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void ComputeWeakWaveBoost(const std::vector<double>& values, double level, std::vector<float>& out)
|
||||
{
|
||||
const std::size_t count = values.size();
|
||||
out.assign(count, std::numeric_limits<float>::quiet_NaN());
|
||||
@@ -1172,99 +1099,7 @@ namespace jdi
|
||||
return;
|
||||
}
|
||||
|
||||
// 窗口点数 X:局部“差异强度”的统计**窗口长度**(点数),窗口 = [i−r, i+r],
|
||||
// r = (X−1)/2。窗口长度**应略大于纹波周期的一半**:
|
||||
// 只有当窗口跨越的相位达到 180° 时,窗口内才必定包含一个波峰或波谷,
|
||||
// “局部 |交流分量| 的峰值”因而恒等于纹波振幅 A,与相位无关
|
||||
//(正常区域恒为 A、弱区恒为 A/2,比值恰为 2);窗口再小就会跟随相位起伏
|
||||
//(把正常区域的波峰也一起放大),远大于一个周期时又会把弱区与邻近正常区一起统计掉。
|
||||
// 窗口越短、混叠区(弱区边界各 ±r)越窄,对“只有一两个周期(甚至半周期)变弱”的
|
||||
// 情形越有效。
|
||||
const int span = std::max(3, std::min(window, 1000000));
|
||||
const int strengthRadius = std::max(1, (span - 1) / 2);
|
||||
// 趋势窗口取局部窗口的 2 倍(≈ 一个完整纹波周期):只有按整周期取平均,
|
||||
// 趋势线才恰好是纹波的中心线(交流分量均值为零),待放大的弱区也就**不会**
|
||||
// 因为自己变“扁”而把趋势线一起拉偏。
|
||||
const int trendRadius = std::max(1, strengthRadius * 2);
|
||||
// 包络窗口固定取局部窗口的 8 倍:用来给出“周边本来的振幅范围”,
|
||||
// 既作为放大基准(中心值)也作为输出的夹取上下界。
|
||||
const std::size_t envelopeRadius =
|
||||
static_cast<std::size_t>(std::max(1, std::min(strengthRadius * 8, 16384)));
|
||||
const double maxGain = static_cast<double>(std::max(1, gain));
|
||||
|
||||
// 1) 周边包络:邻域内采样值的最小/最大值(单调队列,O(n))。
|
||||
// 邻域**必须明显大于一个纹波周期**,否则包络会跟着纹波起伏,
|
||||
// 放大后会把相邻正常区域的相位也一并扭曲。
|
||||
std::vector<float> envMin;
|
||||
std::vector<float> envMax;
|
||||
SlidingExtrema(values, envelopeRadius, envMin, envMax);
|
||||
|
||||
// 2) 局部差异强度 = 邻域内 |采样值 − 趋势线| 的**最大值**(即局部纹波振幅)。
|
||||
//
|
||||
// 这里刻意**不用**另外两种看起来更自然的做法:
|
||||
// * “|采样值 − 趋势线| 的平均”:趋势线是低频量,会被待放大的弱区本身拉偏,
|
||||
// 于是弱区周边本来正常的采样点也会被判成“差异偏小”而被一起放大;
|
||||
// * “原始采样值的峰峰值”:峰峰值要同时覆盖波峰与波谷才与相位无关,
|
||||
// 这要求窗口跨越 360°,窗口会大到把弱区与邻近正常区平均掉
|
||||
// (实测一个周期宽的弱区只能恢复三分之一)。
|
||||
// |交流分量| 的最大值只需要窗口跨越 180°(窗口约半个周期)就与相位无关,
|
||||
// 既能准确识别弱区,又因为窗口很短而具备足够好的定位精度。
|
||||
//
|
||||
// 又因为增益下界恒为 1(见第 4 步),正常区域的增益恰好是 1,
|
||||
// 输出与输入**逐位相同**,因此对其它数据点真正没有任何影响。
|
||||
//
|
||||
// 极值窗口按**实际长度**(端点截断)统计,端点因此会低估振幅、
|
||||
// 导致增益被高估;这一端点效应由第 4 步的收敛系数消除。
|
||||
std::vector<float> trend;
|
||||
ComputeMovingAverage(values, trendRadius, trend);
|
||||
std::vector<float> strength;
|
||||
{
|
||||
// `ac` 只在求局部最大值时用到:单独放在一个作用域里,
|
||||
// 亿级采样下这一份 100 MB 级的中转数组能尽早归还给系统。
|
||||
std::vector<float> ac(count, std::numeric_limits<float>::quiet_NaN());
|
||||
for (std::size_t index = 0; index < count; ++index)
|
||||
{
|
||||
const double value = values[index];
|
||||
if (std::isfinite(value) && std::isfinite(trend[index]))
|
||||
{
|
||||
ac[index] = static_cast<float>(std::fabs(value - static_cast<double>(trend[index])));
|
||||
}
|
||||
}
|
||||
SlidingMax(ac, static_cast<std::size_t>(strengthRadius), strength);
|
||||
}
|
||||
|
||||
// 3) 目标强度:差异强度的 75 分位。用直方图统计 → O(1) 额外内存,
|
||||
// 且不会被个别尖峰抬高(用均值会),也不会被最尖处完全主导(用最大值会)。
|
||||
//
|
||||
// ⚠️ 统计时**排除首尾的端点收敛区**(见第 4 步):那里的趋势窗口被截断,
|
||||
// “|交流分量| 的最大值”整体偏大,会把分位数抬高、导致目标强度偏大。
|
||||
// 待统计的采样点数不足时退回全段统计。
|
||||
const std::size_t edgeGuard =
|
||||
static_cast<std::size_t>(2) * static_cast<std::size_t>(trendRadius);
|
||||
// 剩下足够多的采样点时才有必要排除端点区(短信号就直接全段统计)。
|
||||
constexpr std::size_t kEdgeGuardMinimum = 256;
|
||||
const bool useGuard = (count > edgeGuard * 2 + kEdgeGuardMinimum);
|
||||
const auto inGuard = [count, edgeGuard, useGuard](std::size_t index)
|
||||
{
|
||||
if (!useGuard)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return (index >= edgeGuard) && ((count - 1 - index) >= edgeGuard);
|
||||
};
|
||||
|
||||
float maxStrength = 0.0f;
|
||||
for (std::size_t index = 0; index < count; ++index)
|
||||
{
|
||||
const float value = strength[index];
|
||||
if (inGuard(index) && std::isfinite(value) && value > maxStrength)
|
||||
{
|
||||
maxStrength = value;
|
||||
}
|
||||
}
|
||||
if (!(maxStrength > 0.0f))
|
||||
{
|
||||
// 完全平坦(或全为 NaN)的信号:无差异可放大,等价于原样输出。
|
||||
// 先把有效采样原样拷入输出,之后按“极值点之间的平滑偏移”逐点叠加。
|
||||
for (std::size_t index = 0; index < count; ++index)
|
||||
{
|
||||
const double value = values[index];
|
||||
@@ -1273,91 +1108,146 @@ namespace jdi
|
||||
out[index] = static_cast<float>(value);
|
||||
}
|
||||
}
|
||||
|
||||
// 增强等级:波峰取左右两个波峰中较小者为 0%、较大者为 100%,
|
||||
// 目标值 = 较小 + 等级 × (较大 − 较小)(波谷相反)。
|
||||
const double strength = std::max(0.0, std::min(1.0, level));
|
||||
|
||||
// 1) 识别波峰 / 波谷:扫描单调段,方向翻转处的上一个采样点即为极值点。
|
||||
// 波峰(先升后降)与波谷(先降后升)必然交替出现,因此同类极值就是下标 ±2 的两个。
|
||||
std::vector<BoostExtremum> extremes;
|
||||
{
|
||||
int direction = 0; // 当前单调段方向(+1 上升、-1 下降)。
|
||||
bool hasPrevious = false;
|
||||
std::size_t previousIndex = 0;
|
||||
double previousShape = 0.0;
|
||||
for (std::size_t index = 0; index < count; ++index)
|
||||
{
|
||||
if (!std::isfinite(values[index]))
|
||||
{
|
||||
// 无效采样作为断点:不跨越它判定极值。
|
||||
hasPrevious = false;
|
||||
continue;
|
||||
}
|
||||
const double shape = BoostShapeAt(values, index);
|
||||
if (!std::isfinite(shape))
|
||||
{
|
||||
hasPrevious = false;
|
||||
continue;
|
||||
}
|
||||
if (!hasPrevious)
|
||||
{
|
||||
hasPrevious = true;
|
||||
previousIndex = index;
|
||||
previousShape = shape;
|
||||
continue;
|
||||
}
|
||||
const int step = (shape > previousShape) ? 1 : ((shape < previousShape) ? -1 : 0);
|
||||
if (step != 0)
|
||||
{
|
||||
if (direction == 0)
|
||||
{
|
||||
direction = step;
|
||||
}
|
||||
else if (step != direction)
|
||||
{
|
||||
// 方向在此翻转:上一个采样点就是极值(原方向向上则为波峰)。
|
||||
BoostExtremum extreme;
|
||||
extreme.index = previousIndex;
|
||||
extreme.value = values[previousIndex];
|
||||
extreme.type = direction;
|
||||
extremes.push_back(extreme);
|
||||
direction = step;
|
||||
}
|
||||
}
|
||||
previousIndex = index;
|
||||
previousShape = shape;
|
||||
}
|
||||
}
|
||||
|
||||
if (extremes.size() < 3)
|
||||
{
|
||||
// 找不到足够的极值(例如常量或单调信号):无可增强之处。
|
||||
return;
|
||||
}
|
||||
|
||||
constexpr int kBinCount = 2048;
|
||||
std::vector<int> histogram(static_cast<std::size_t>(kBinCount), 0);
|
||||
std::size_t sampleCount = 0;
|
||||
for (std::size_t index = 0; index < count; ++index)
|
||||
// 2) 逐个极值点判定“是否偏弱”,并算出它自己的目标偏移量。
|
||||
const std::size_t extremeCount = extremes.size();
|
||||
std::vector<double> shift(extremeCount, 0.0);
|
||||
for (std::size_t k = 2; k + 2 < extremeCount; ++k)
|
||||
{
|
||||
const float value = strength[index];
|
||||
if (!inGuard(index) || !std::isfinite(value))
|
||||
const BoostExtremum& current = extremes[k];
|
||||
const BoostExtremum& left = extremes[k - 2];
|
||||
const BoostExtremum& right = extremes[k + 2];
|
||||
if (left.type != current.type || right.type != current.type)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
int bin = static_cast<int>(static_cast<double>(value) /
|
||||
static_cast<double>(maxStrength) * kBinCount);
|
||||
bin = std::max(0, std::min(bin, kBinCount - 1));
|
||||
++histogram[static_cast<std::size_t>(bin)];
|
||||
++sampleCount;
|
||||
}
|
||||
double target = static_cast<double>(maxStrength);
|
||||
if (sampleCount > 0)
|
||||
if (!std::isfinite(current.value) || !std::isfinite(left.value) ||
|
||||
!std::isfinite(right.value))
|
||||
{
|
||||
const std::size_t wanted = (sampleCount * 75) / 100;
|
||||
std::size_t accumulated = 0;
|
||||
for (int bin = 0; bin < kBinCount; ++bin)
|
||||
{
|
||||
accumulated += static_cast<std::size_t>(histogram[static_cast<std::size_t>(bin)]);
|
||||
if (accumulated > wanted)
|
||||
{
|
||||
// 取桶中心,避免目标强度贴到桶的下边界而整体偏小。
|
||||
target = (static_cast<double>(bin) + 0.5) / kBinCount *
|
||||
static_cast<double>(maxStrength);
|
||||
break;
|
||||
continue;
|
||||
}
|
||||
const double mean = (left.value + right.value) * 0.5;
|
||||
const double lower = std::min(left.value, right.value);
|
||||
const double upper = std::max(left.value, right.value);
|
||||
double destination = 0.0;
|
||||
if (current.type > 0)
|
||||
{
|
||||
// 波峰:低于左右两个波峰的均值 → 判定为弱波,上拉到等级对应的目标值。
|
||||
if (!(current.value < mean))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
destination = lower + strength * (upper - lower);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 波谷:高于左右两个波谷的均值 → 判定为弱波,下拉到等级对应的目标值。
|
||||
if (!(current.value > mean))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
destination = upper - strength * (upper - lower);
|
||||
}
|
||||
shift[k] = destination - current.value;
|
||||
}
|
||||
|
||||
// 4) 逐点增益并合成输出。
|
||||
// * 基准 = 趋势线(纹波的中心线,与信号整体同步,纹波在其中上下摆动);
|
||||
// * 增益下界为 1 → 局部振幅不比目标值小的采样点**输出与原值逐位相同**
|
||||
// (基准被 1 倍增益完全消掉,因此对其它数据点没有任何影响);
|
||||
// * 放大后的取值再夹到周边包络内 → **任何一点都不会超出原有的振幅范围**
|
||||
// (尤其重要:被放大区域两侧自身的振幅本来就很大,不应被继续抬升)。
|
||||
for (std::size_t index = 0; index < count; ++index)
|
||||
// 3) 把偏移量平滑地摊到极值点之间的数据点上:
|
||||
// 相邻两个极值点之间用平滑步进过渡(两端导数为 0,波形不会出现折角),
|
||||
// 数据首尾则从 0 渐变到最近的极值点偏移,保证端点原样不动。
|
||||
const auto applySpan = [&values, &out, count](std::size_t begin, std::size_t end,
|
||||
double beginShift, double endShift)
|
||||
{
|
||||
if (end <= begin)
|
||||
{
|
||||
if (begin < count && std::isfinite(values[begin]))
|
||||
{
|
||||
out[begin] = static_cast<float>(values[begin] + beginShift);
|
||||
}
|
||||
return;
|
||||
}
|
||||
const double span = static_cast<double>(end - begin);
|
||||
for (std::size_t index = begin; index <= end; ++index)
|
||||
{
|
||||
const double value = values[index];
|
||||
if (!std::isfinite(value))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const bool hasEnvelope = std::isfinite(envMin[index]) && std::isfinite(envMax[index]);
|
||||
const double base =
|
||||
std::isfinite(trend[index]) ? static_cast<double>(trend[index]) : value;
|
||||
const double t = static_cast<double>(index - begin) / span;
|
||||
const double weight = t * t * (3.0 - 2.0 * t);
|
||||
out[index] = static_cast<float>(value + beginShift * (1.0 - weight) +
|
||||
endShift * weight);
|
||||
}
|
||||
};
|
||||
|
||||
double pointGain = 1.0;
|
||||
const float local = strength[index];
|
||||
if (std::isfinite(local) && local > 0.0f)
|
||||
applySpan(0, extremes.front().index, 0.0, shift.front());
|
||||
for (std::size_t k = 0; k + 1 < extremeCount; ++k)
|
||||
{
|
||||
pointGain = target / static_cast<double>(local);
|
||||
}
|
||||
pointGain = std::max(1.0, std::min(pointGain, maxGain));
|
||||
|
||||
// 端点收敛:首尾各 2r 个采样点的**趋势窗口与极值窗口都被截断**
|
||||
//(趋势窗口半径 2r、极值窗口半径 r),局部振幅与中心线都被系统性估偏。
|
||||
// 因此这些采样点一律保持原样(增益恰为 1),再在接下来的 2r 个点内
|
||||
// 平滑过渡到完整增益,避免记录首尾被整片抬升。
|
||||
//
|
||||
// ⚠️ 不能靠“镜像延拓”解决:镜像后的窗口覆盖的真实下标集合与截断窗口相同
|
||||
//(例如 i=16、r=40 时两者都只覆盖真实下标 0..56),因此不会带来任何新信息。
|
||||
const double taperStart = static_cast<double>(trendRadius);
|
||||
const double taperSpan = std::max(1.0, static_cast<double>(trendRadius));
|
||||
const double distanceToEdge =
|
||||
static_cast<double>(std::min(index, count - 1 - index));
|
||||
const double taper =
|
||||
std::max(0.0, std::min(1.0, (distanceToEdge - taperStart) / taperSpan));
|
||||
pointGain = 1.0 + (pointGain - 1.0) * taper;
|
||||
|
||||
double amplified = base + pointGain * (value - base);
|
||||
if (hasEnvelope)
|
||||
{
|
||||
amplified = std::max(static_cast<double>(envMin[index]),
|
||||
std::min(amplified, static_cast<double>(envMax[index])));
|
||||
}
|
||||
out[index] = static_cast<float>(amplified);
|
||||
applySpan(extremes[k].index, extremes[k + 1].index, shift[k], shift[k + 1]);
|
||||
}
|
||||
applySpan(extremes.back().index, count - 1, shift.back(), 0.0);
|
||||
}
|
||||
|
||||
const Lane::MeanFilterItem* Lane::FindMeanFilterItem(const SignalRef& ref) const
|
||||
@@ -1430,9 +1320,9 @@ namespace jdi
|
||||
edgeCenterItems.end());
|
||||
}
|
||||
|
||||
const Lane::DiffAmpItem* Lane::FindDiffAmpItem(const SignalRef& ref) const
|
||||
const Lane::WeakBoostItem* Lane::FindWeakBoostItem(const SignalRef& ref) const
|
||||
{
|
||||
for (const DiffAmpItem& item : diffAmpItems)
|
||||
for (const WeakBoostItem& item : weakBoostItems)
|
||||
{
|
||||
if (item.ref == ref)
|
||||
{
|
||||
@@ -1442,32 +1332,27 @@ namespace jdi
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Lane::DiffAmpItem& Lane::EnsureDiffAmpItem(const SignalRef& ref)
|
||||
Lane::WeakBoostItem& Lane::EnsureWeakBoostItem(const SignalRef& ref)
|
||||
{
|
||||
for (DiffAmpItem& item : diffAmpItems)
|
||||
for (WeakBoostItem& item : weakBoostItems)
|
||||
{
|
||||
if (item.ref == ref)
|
||||
{
|
||||
return item;
|
||||
}
|
||||
}
|
||||
DiffAmpItem item;
|
||||
WeakBoostItem item;
|
||||
item.ref = ref;
|
||||
// 默认参数:窗口点数 X = 80(长度)、放大倍数 N = 2。
|
||||
// 窗口长度需略大于纹波周期的一半(见 ComputeDiffAmplify 的说明),
|
||||
// 80 点可覆盖周期约 160 采样以内的纹波。
|
||||
item.points = 80;
|
||||
item.order = 2;
|
||||
diffAmpItems.push_back(item);
|
||||
return diffAmpItems.back();
|
||||
weakBoostItems.push_back(item);
|
||||
return weakBoostItems.back();
|
||||
}
|
||||
|
||||
void Lane::RemoveDiffAmpItem(const SignalRef& ref)
|
||||
void Lane::RemoveWeakBoostItem(const SignalRef& ref)
|
||||
{
|
||||
diffAmpItems.erase(
|
||||
std::remove_if(diffAmpItems.begin(), diffAmpItems.end(),
|
||||
[&ref](const DiffAmpItem& item) { return item.ref == ref; }),
|
||||
diffAmpItems.end());
|
||||
weakBoostItems.erase(
|
||||
std::remove_if(weakBoostItems.begin(), weakBoostItems.end(),
|
||||
[&ref](const WeakBoostItem& item) { return item.ref == ref; }),
|
||||
weakBoostItems.end());
|
||||
}
|
||||
|
||||
int FindNearestSample(const DataFile& file, double time)
|
||||
@@ -1714,7 +1599,7 @@ namespace jdi
|
||||
};
|
||||
purgeRefs(lane.meanFilterItems);
|
||||
purgeRefs(lane.edgeCenterItems);
|
||||
purgeRefs(lane.diffAmpItems);
|
||||
purgeRefs(lane.weakBoostItems);
|
||||
}
|
||||
// 被移除信号若独占了某个网格,该网格随之消失(与取消勾选的行为一致)。
|
||||
m_lanes.erase(std::remove_if(m_lanes.begin(), m_lanes.end(),
|
||||
@@ -2006,18 +1891,19 @@ namespace jdi
|
||||
lane.edgeCenterPanelOpen = source.edgeCenterPanelOpen;
|
||||
lane.edgeCenterPanelX = source.edgeCenterPanelX;
|
||||
lane.edgeCenterPanelY = source.edgeCenterPanelY;
|
||||
lane.diffAmp = source.diffAmp;
|
||||
lane.diffAmpPanelOpen = source.diffAmpPanelOpen;
|
||||
lane.diffAmpPanelX = source.diffAmpPanelX;
|
||||
lane.diffAmpPanelY = source.diffAmpPanelY;
|
||||
lane.weakBoost = source.weakBoost;
|
||||
lane.weakBoostLevel = source.weakBoostLevel;
|
||||
lane.weakBoostPanelOpen = source.weakBoostPanelOpen;
|
||||
lane.weakBoostPanelX = source.weakBoostPanelX;
|
||||
lane.weakBoostPanelY = source.weakBoostPanelY;
|
||||
lane.meanFilter = source.meanFilter;
|
||||
lane.meanFilterPanelOpen = source.meanFilterPanelOpen;
|
||||
lane.meanFilterPanelX = source.meanFilterPanelX;
|
||||
lane.meanFilterPanelY = source.meanFilterPanelY;
|
||||
// 拆分后每个子网格只保留自己那个信号的均值滤波 / 中心轴 / 差异放大条目。
|
||||
// 拆分后每个子网格只保留自己那个信号的均值滤波 / 中心轴 / 弱波增强条目。
|
||||
lane.meanFilterItems.clear();
|
||||
lane.edgeCenterItems.clear();
|
||||
lane.diffAmpItems.clear();
|
||||
lane.weakBoostItems.clear();
|
||||
for (const Lane::MeanFilterItem& item : source.meanFilterItems)
|
||||
{
|
||||
if (item.ref == ref)
|
||||
@@ -2032,11 +1918,11 @@ namespace jdi
|
||||
lane.edgeCenterItems.push_back(item);
|
||||
}
|
||||
}
|
||||
for (const Lane::DiffAmpItem& item : source.diffAmpItems)
|
||||
for (const Lane::WeakBoostItem& item : source.weakBoostItems)
|
||||
{
|
||||
if (item.ref == ref)
|
||||
{
|
||||
lane.diffAmpItems.push_back(item);
|
||||
lane.weakBoostItems.push_back(item);
|
||||
}
|
||||
}
|
||||
lane.signals.push_back(ref);
|
||||
@@ -2093,7 +1979,7 @@ namespace jdi
|
||||
++item.ref.signalIndex;
|
||||
}
|
||||
}
|
||||
for (Lane::DiffAmpItem& item : lane.diffAmpItems)
|
||||
for (Lane::WeakBoostItem& item : lane.weakBoostItems)
|
||||
{
|
||||
if (item.ref.fileIndex == fileIndex && item.ref.signalIndex >= insertPos)
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user