弱波增强:去掉前置滤波并新增「阶数 N」滑块;修复选区放大越界绘制与浮动面板 ✕ 交互
- 弱波增强不再做任何前置滤波(删除内部的 5 点滑动平均 BoostShapeAt): 极值点直接在输入采样上识别。需要平滑时用户可以先用「均值滤波」构建一条 滤波后的信号,再对它做弱波增强。 - 新增「阶数 N」参数(1~10,滑块输入),语义与均值滤波的阶数 N 一致 —— 把同一处理重复应用 N 次,每阶以上一阶为输入: 一阶时改定量严格等于「等级」对应的目标值;多阶会让相邻弱波互相抬高彼此的 基准,从而把较宽的弱波区逐步恢复(实测三连弱波的中间峰 550→575→598.4)。 某一阶若不再产生改动会提前结束迭代。 `ComputeWeakWaveBoost(values, level, order, out)`; `Lane::weakBoostOrder`(默认 1)随 LaneCarry / SplitLane / 引用重映射同步, 并进入 SceneContentHash;曲线缓存键变为「信号 + 等级 + 阶数」。 - 浮动面板同步新增一行「阶数 N」滑块(新拖拽模式 BOOST_ORDER、 EdgePanelLayout::sliderOrder);「构建为新信号」命名带上阶数 (形如 `<原信号> (弱波增强 60%×2阶)`)。 - 修复:选区放大时鼠标被捕获到绘图区之外(工具栏 / 信号面板 / 时间轴)会把 选区块画到绘图区之外,而拖拽期间只失效绘图区 → 留下无法擦除的墨迹。 现在拖拽时把选区夹在绘图区内,PaintRubberBand 再对 m_plotRect 求交一次。 - 修复:浮动面板的 ✕ 位于标题栏矩形内部,按下后会继续落到「标题栏 → 拖动面板」 分支,表现为「按住 ✕ 却把面板拖走了」;现在按下 ✕ 直接消费该次点击(抬起时 仍按原语义确认关闭),并把 ✕ 的光标改为手型,与其它关闭按钮一致。 - 修复:✕ / 滑块 / 底部按钮的悬停高亮依赖鼠标坐标,但从同一网格的波形移到面板上 时悬停状态毫无变化 → OnMouseMove 返回 false → 宿主不重绘 → 高亮不出现。 现在「进入 / 离开 / 在面板内移动」都会请求重绘。 - 离屏验证(窗口隐藏,不占用鼠标/键盘/屏幕):345 项检查全部通过,含 阶数扫描(N=1 只改外侧弱峰、N=2 传导到中间弱峰、高 N 逼近健康幅值且不越界、 健康峰在任何阶数下逐位不变)、两个滑块的按压/拖拽/钳制、选区越界零墨迹 (绘图区外像素差 0)、面板 ✕ 悬停高亮与按下不拖动、光标拖拽始终在视界内, 以及真实 20K 电机纹波录波(信号范围保持不变、20%+ 采样被调整)。
This commit is contained in:
1 parent
67f659ad23
commit
8c8508e5f8
5 files changed
+356
-139
No files matched your search
@@ -1480,8 +1480,13 @@ namespace jdi
|
|||||||
if (request.weakBoost)
|
if (request.weakBoost)
|
||||||
{
|
{
|
||||||
ComputeWeakWaveBoost(source.values, static_cast<double>(request.levelPercent) / 100.0,
|
ComputeWeakWaveBoost(source.values, static_cast<double>(request.levelPercent) / 100.0,
|
||||||
derived_values);
|
request.order, derived_values);
|
||||||
derived.name = baseName + L" (弱波增强 " + std::to_wstring(request.levelPercent) + L"%)";
|
derived.name = baseName + L" (弱波增强 " + std::to_wstring(request.levelPercent) + L"%";
|
||||||
|
if (request.order > 1)
|
||||||
|
{
|
||||||
|
derived.name += L"×" + std::to_wstring(request.order) + L"阶";
|
||||||
|
}
|
||||||
|
derived.name += L")";
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -1563,6 +1568,7 @@ namespace jdi
|
|||||||
request.fileIndex = item.fileIndex;
|
request.fileIndex = item.fileIndex;
|
||||||
request.signalIndex = item.signalIndex;
|
request.signalIndex = item.signalIndex;
|
||||||
request.levelPercent = item.levelPercent;
|
request.levelPercent = item.levelPercent;
|
||||||
|
request.order = item.order;
|
||||||
request.weakBoost = true;
|
request.weakBoost = true;
|
||||||
requests.push_back(request);
|
requests.push_back(request);
|
||||||
}
|
}
|
||||||
@@ -1605,7 +1611,8 @@ namespace jdi
|
|||||||
std::wstring text = L"已构建 " + std::to_wstring(built) + L" 条 ";
|
std::wstring text = L"已构建 " + std::to_wstring(built) + L" 条 ";
|
||||||
if (last.weakBoost)
|
if (last.weakBoost)
|
||||||
{
|
{
|
||||||
text += L"弱波增强 " + std::to_wstring(last.levelPercent) + L"% 信号。";
|
text += L"弱波增强 " + std::to_wstring(last.levelPercent) + L"%" +
|
||||||
|
std::to_wstring(last.order) + L"阶信号。";
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ namespace jdi
|
|||||||
EDGE_MOVE, /**< 拖动「边沿计数」的横向光标(上下移动幅值)。 */
|
EDGE_MOVE, /**< 拖动「边沿计数」的横向光标(上下移动幅值)。 */
|
||||||
EDGE_PANEL_MOVE, /**< 拖动边沿计数浮动面板。 */
|
EDGE_PANEL_MOVE, /**< 拖动边沿计数浮动面板。 */
|
||||||
BOOST_LEVEL, /**< 拖动「弱波增强」面板中的等级滑块。 */
|
BOOST_LEVEL, /**< 拖动「弱波增强」面板中的等级滑块。 */
|
||||||
|
BOOST_ORDER, /**< 拖动「弱波增强」面板中的阶数 N 滑块。 */
|
||||||
LANE_REORDER /**< 拖动网格图例中的 GX 编号徽标:上下调整网格次序。 */
|
LANE_REORDER /**< 拖动网格图例中的 GX 编号徽标:上下调整网格次序。 */
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -530,6 +531,7 @@ namespace jdi
|
|||||||
int fileIndex = -1; /**< 信号所属文件下标。 */
|
int fileIndex = -1; /**< 信号所属文件下标。 */
|
||||||
int signalIndex = -1; /**< 文件内信号下标。 */
|
int signalIndex = -1; /**< 文件内信号下标。 */
|
||||||
int levelPercent = 50; /**< 弱波增强等级(0–100)。 */
|
int levelPercent = 50; /**< 弱波增强等级(0–100)。 */
|
||||||
|
int order = 1; /**< 阶数 N(1–10)。 */
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -569,6 +571,21 @@ namespace jdi
|
|||||||
*/
|
*/
|
||||||
double LaneWeakBoostLevel(int laneIndex) const;
|
double LaneWeakBoostLevel(int laneIndex) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 读取某网格的弱波增强阶数 N(1–10)。
|
||||||
|
* @param [IN] laneIndex 网格下标。
|
||||||
|
* @return 阶数;网格不存在时返回 1。
|
||||||
|
*/
|
||||||
|
int LaneWeakBoostOrder(int laneIndex) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置某网格的弱波增强阶数 N(1–10,超出范围会被夹取)。
|
||||||
|
* @param [IN] laneIndex 网格下标。
|
||||||
|
* @param [IN] order 阶数。
|
||||||
|
* @return 阶数发生变化返回 true。
|
||||||
|
*/
|
||||||
|
bool SetLaneWeakBoostOrder(int laneIndex, int order);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 设置某网格的弱波增强等级(0–1,超出范围会被夹取)。
|
* @brief 设置某网格的弱波增强等级(0–1,超出范围会被夹取)。
|
||||||
* @param [IN] laneIndex 网格下标。
|
* @param [IN] laneIndex 网格下标。
|
||||||
@@ -578,23 +595,26 @@ namespace jdi
|
|||||||
bool SetLaneWeakBoostLevel(int laneIndex, double level);
|
bool SetLaneWeakBoostLevel(int laneIndex, double level);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 获取弱波增强曲线(按「信号 + 增强等级」缓存)。
|
* @brief 获取弱波增强曲线(按「信号 + 增强等级 + 阶数」缓存)。
|
||||||
* @param [IN] signal 目标信号。
|
* @param [IN] signal 目标信号。
|
||||||
* @param [IN] levelPercent 弱波增强等级(0–100,整数百分比)。
|
* @param [IN] levelPercent 弱波增强等级(0–100,整数百分比)。
|
||||||
|
* @param [IN] order 阶数 N(1–10)。
|
||||||
* @return 曲线数值数组(长度与 `signal.values` 相同;缺失处为 NaN)。
|
* @return 曲线数值数组(长度与 `signal.values` 相同;缺失处为 NaN)。
|
||||||
*/
|
*/
|
||||||
const std::vector<float>& WeakBoostCurveFor(const SignalData& signal, int levelPercent) const;
|
const std::vector<float>& WeakBoostCurveFor(const SignalData& signal, int levelPercent,
|
||||||
|
int order) const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 取某个时刻的弱波增强曲线取值(用于纵向光标的取值标注)。
|
* @brief 取某个时刻的弱波增强曲线取值(用于纵向光标的取值标注)。
|
||||||
* @param [IN] file 信号所属文件。
|
* @param [IN] file 信号所属文件。
|
||||||
* @param [IN] signal 目标信号。
|
* @param [IN] signal 目标信号。
|
||||||
* @param [IN] levelPercent 弱波增强等级(0–100)。
|
* @param [IN] levelPercent 弱波增强等级(0–100)。
|
||||||
|
* @param [IN] order 阶数 N(1–10)。
|
||||||
* @param [IN] time 采样时刻。
|
* @param [IN] time 采样时刻。
|
||||||
* @param [OUT] value 输出取值。
|
* @param [OUT] value 输出取值。
|
||||||
* @return 取到有效值返回 true。
|
* @return 取到有效值返回 true。
|
||||||
*/
|
*/
|
||||||
bool WeakBoostValueAt(const DataFile& file, const SignalData& signal, int levelPercent,
|
bool WeakBoostValueAt(const DataFile& file, const SignalData& signal, int levelPercent, int order,
|
||||||
double time, double& value) const;
|
double time, double& value) const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1064,6 +1084,7 @@ namespace jdi
|
|||||||
gd::RectF edit; /**< 面板底部编辑行(无编辑内容时宽度为 0)。 */
|
gd::RectF edit; /**< 面板底部编辑行(无编辑内容时宽度为 0)。 */
|
||||||
gd::RectF button; /**< 面板底部按钮(无按钮时宽度为 0)。 */
|
gd::RectF button; /**< 面板底部按钮(无按钮时宽度为 0)。 */
|
||||||
gd::RectF slider; /**< 「弱波增强」等级滑块的轨道矩形(无滑块时宽度为 0)。 */
|
gd::RectF slider; /**< 「弱波增强」等级滑块的轨道矩形(无滑块时宽度为 0)。 */
|
||||||
|
gd::RectF sliderOrder; /**< 「弱波增强」阶数 N 滑块的轨道矩形(无滑块时宽度为 0)。 */
|
||||||
int rows = 0; /**< 统计行数(每条可见信号一行)。 */
|
int rows = 0; /**< 统计行数(每条可见信号一行)。 */
|
||||||
/**
|
/**
|
||||||
* @brief 「逐条目」面板的矩形(MEAN 与 CENTER 面板共用同一套排布)。
|
* @brief 「逐条目」面板的矩形(MEAN 与 CENTER 面板共用同一套排布)。
|
||||||
@@ -1193,9 +1214,10 @@ namespace jdi
|
|||||||
* @param [IN] resolved 信号视图。
|
* @param [IN] resolved 信号视图。
|
||||||
* @param [IN] lane 网格绘制信息。
|
* @param [IN] lane 网格绘制信息。
|
||||||
* @param [IN] levelPercent 弱波增强等级(0–100)。
|
* @param [IN] levelPercent 弱波增强等级(0–100)。
|
||||||
|
* @param [IN] order 阶数 N(1–10)。
|
||||||
*/
|
*/
|
||||||
void PaintWeakBoostCurve(gd::Graphics& g, const ResolvedSignal& resolved, const LaneVisual& lane,
|
void PaintWeakBoostCurve(gd::Graphics& g, const ResolvedSignal& resolved, const LaneVisual& lane,
|
||||||
int levelPercent) const;
|
int levelPercent, int order) const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 数据点标记的位置与"越界方向"(用于决定画圆点还是贴边三角)。
|
* @brief 数据点标记的位置与"越界方向"(用于决定画圆点还是贴边三角)。
|
||||||
@@ -1414,7 +1436,8 @@ namespace jdi
|
|||||||
int m_pendingWeakBoostLane = -1; /**< 待构建的弱波增强信号所属网格下标(-1 表示无请求)。 */
|
int m_pendingWeakBoostLane = -1; /**< 待构建的弱波增强信号所属网格下标(-1 表示无请求)。 */
|
||||||
std::vector<MeanFilterBuild> m_pendingMeanFilterItems; /**< 待构建的信号及其参数。 */
|
std::vector<MeanFilterBuild> m_pendingMeanFilterItems; /**< 待构建的信号及其参数。 */
|
||||||
std::vector<WeakBoostBuild> m_pendingWeakBoostItems; /**< 待构建的弱波增强信号及其参数。 */
|
std::vector<WeakBoostBuild> m_pendingWeakBoostItems; /**< 待构建的弱波增强信号及其参数。 */
|
||||||
int m_boostDragLane = -1; /**< 正在拖动等级滑块的面板所属网格。 */
|
int m_boostDragLane = -1; /**< 正在拖动滑块的面板所属网格。 */
|
||||||
|
bool m_mouseOverPanel = false; /**< 上一帧鼠标是否落在某个实验性功能面板上。 */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 实验性功能浮动面板的 ✕ 处于"已按下、等待抬起确认"状态。
|
* @brief 实验性功能浮动面板的 ✕ 处于"已按下、等待抬起确认"状态。
|
||||||
@@ -1470,11 +1493,12 @@ namespace jdi
|
|||||||
};
|
};
|
||||||
mutable std::deque<MeanCurveEntry> m_meanCurveCache;
|
mutable std::deque<MeanCurveEntry> m_meanCurveCache;
|
||||||
|
|
||||||
/** 弱波增强曲线缓存项(按「信号指针 + 增强等级百分比」标识)。 */
|
/** 弱波增强曲线缓存项(按「信号指针 + 增强等级百分比 + 阶数」标识)。 */
|
||||||
struct WeakBoostCurveEntry
|
struct WeakBoostCurveEntry
|
||||||
{
|
{
|
||||||
const SignalData* signal = nullptr;
|
const SignalData* signal = nullptr;
|
||||||
int levelPercent = 50; /**< 弱波增强等级(0–100)。 */
|
int levelPercent = 50; /**< 弱波增强等级(0–100)。 */
|
||||||
|
int order = 1; /**< 阶数 N(1–10)。 */
|
||||||
std::vector<float> values; /**< 与 signal->values 等长;缺失处为 NaN。 */
|
std::vector<float> values; /**< 与 signal->values 等长;缺失处为 NaN。 */
|
||||||
};
|
};
|
||||||
mutable std::deque<WeakBoostCurveEntry> m_weakBoostCurveCache;
|
mutable std::deque<WeakBoostCurveEntry> m_weakBoostCurveCache;
|
||||||
|
|||||||
@@ -70,10 +70,13 @@ namespace jdi
|
|||||||
const float kEdgePanelMeanEditHeight = 24.0f; /**< 逐条目面板中每条信号的参数行高度。 */
|
const float kEdgePanelMeanEditHeight = 24.0f; /**< 逐条目面板中每条信号的参数行高度。 */
|
||||||
const float kEdgePanelMeanRowGap = 2.0f; /**< 名称行与参数行之间的间距。 */
|
const float kEdgePanelMeanRowGap = 2.0f; /**< 名称行与参数行之间的间距。 */
|
||||||
const float kEdgePanelMeanCheckSize = 15.0f; /**< 逐条目面板中条目复选框尺寸。 */
|
const float kEdgePanelMeanCheckSize = 15.0f; /**< 逐条目面板中条目复选框尺寸。 */
|
||||||
const float kWeakBoostSliderRowHeight = 26.0f; /**< 弱波增强面板中等级滑块行的高度。 */
|
const float kWeakBoostSliderRowHeight = 26.0f; /**< 弱波增强面板中每个滑块行的高度。 */
|
||||||
const float kWeakBoostTrackHeight = 6.0f; /**< 弱波增强等级滑块轨道高度。 */
|
const float kWeakBoostTrackHeight = 6.0f; /**< 弱波增强滑块的轨道高度。 */
|
||||||
const float kWeakBoostTrackMinWidth = 120.0f; /**< 弱波增强等级滑块轨道的最小宽度。 */
|
const float kWeakBoostTrackMinWidth = 120.0f; /**< 弱波增强滑块轨道的最小宽度。 */
|
||||||
|
const int kWeakBoostOrderMin = 1; /**< 阶数 N 的下限。 */
|
||||||
|
const int kWeakBoostOrderMax = 10; /**< 阶数 N 的上限。 */
|
||||||
const wchar_t* const kWeakBoostLevelLabel = L"弱波增强等级"; /**< 等级滑块行左侧的说明文字。 */
|
const wchar_t* const kWeakBoostLevelLabel = L"弱波增强等级"; /**< 等级滑块行左侧的说明文字。 */
|
||||||
|
const wchar_t* const kWeakBoostOrderLabel = L"阶数 N"; /**< 阶数滑块行左侧的说明文字。 */
|
||||||
const wchar_t* const kWeakBoostTitle = L"弱波增强"; /**< 弱波增强面板标题。 */
|
const wchar_t* const kWeakBoostTitle = L"弱波增强"; /**< 弱波增强面板标题。 */
|
||||||
const wchar_t* const kBuildSignalLabel = L"构建为新信号"; /**< 逐条目面板底部按钮文案(均值滤波 / 弱波增强共用)。 */
|
const wchar_t* const kBuildSignalLabel = L"构建为新信号"; /**< 逐条目面板底部按钮文案(均值滤波 / 弱波增强共用)。 */
|
||||||
const std::size_t kEdgeCountCacheLimit = 48; /**< 边沿统计缓存条目上限(超出时淘汰最早的一条)。 */
|
const std::size_t kEdgeCountCacheLimit = 48; /**< 边沿统计缓存条目上限(超出时淘汰最早的一条)。 */
|
||||||
@@ -1752,6 +1755,34 @@ namespace jdi
|
|||||||
return m_tab->Lanes()[static_cast<std::size_t>(laneIndex)].weakBoostLevel;
|
return m_tab->Lanes()[static_cast<std::size_t>(laneIndex)].weakBoostLevel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int PlotView::LaneWeakBoostOrder(int laneIndex) const
|
||||||
|
{
|
||||||
|
if (m_tab == nullptr || laneIndex < 0 || laneIndex >= static_cast<int>(m_tab->Lanes().size()))
|
||||||
|
{
|
||||||
|
return kWeakBoostOrderMin;
|
||||||
|
}
|
||||||
|
return m_tab->Lanes()[static_cast<std::size_t>(laneIndex)].weakBoostOrder;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PlotView::SetLaneWeakBoostOrder(int laneIndex, int order)
|
||||||
|
{
|
||||||
|
if (m_tab == nullptr || laneIndex < 0 || laneIndex >= static_cast<int>(m_tab->Lanes().size()))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Lane& lane = m_tab->Lanes()[static_cast<std::size_t>(laneIndex)];
|
||||||
|
const int clamped = std::max(kWeakBoostOrderMin, std::min(kWeakBoostOrderMax, order));
|
||||||
|
if (lane.weakBoostOrder == clamped)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
lane.weakBoostOrder = clamped;
|
||||||
|
// 阶数变化会改变预览曲线:清掉曲线缓存并让场景缓存失效。
|
||||||
|
m_weakBoostCurveCache.clear();
|
||||||
|
InvalidateSceneCache();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool PlotView::SetLaneWeakBoostLevel(int laneIndex, double level)
|
bool PlotView::SetLaneWeakBoostLevel(int laneIndex, double level)
|
||||||
{
|
{
|
||||||
if (m_tab == nullptr || laneIndex < 0 || laneIndex >= static_cast<int>(m_tab->Lanes().size()))
|
if (m_tab == nullptr || laneIndex < 0 || laneIndex >= static_cast<int>(m_tab->Lanes().size()))
|
||||||
@@ -1981,12 +2012,13 @@ namespace jdi
|
|||||||
}
|
}
|
||||||
|
|
||||||
const std::vector<float>& PlotView::WeakBoostCurveFor(const SignalData& signal,
|
const std::vector<float>& PlotView::WeakBoostCurveFor(const SignalData& signal,
|
||||||
int levelPercent) const
|
int levelPercent, int order) const
|
||||||
{
|
{
|
||||||
const int level = std::max(0, std::min(100, levelPercent));
|
const int level = std::max(0, std::min(100, levelPercent));
|
||||||
|
const int stages = std::max(kWeakBoostOrderMin, std::min(kWeakBoostOrderMax, order));
|
||||||
for (const WeakBoostCurveEntry& entry : m_weakBoostCurveCache)
|
for (const WeakBoostCurveEntry& entry : m_weakBoostCurveCache)
|
||||||
{
|
{
|
||||||
if (entry.signal == &signal && entry.levelPercent == level)
|
if (entry.signal == &signal && entry.levelPercent == level && entry.order == stages)
|
||||||
{
|
{
|
||||||
return entry.values;
|
return entry.values;
|
||||||
}
|
}
|
||||||
@@ -1995,23 +2027,24 @@ namespace jdi
|
|||||||
WeakBoostCurveEntry entry;
|
WeakBoostCurveEntry entry;
|
||||||
entry.signal = &signal;
|
entry.signal = &signal;
|
||||||
entry.levelPercent = level;
|
entry.levelPercent = level;
|
||||||
|
entry.order = stages;
|
||||||
// 与「构建为新信号」共用同一实现(Utilities 内的自由函数),
|
// 与「构建为新信号」共用同一实现(Utilities 内的自由函数),
|
||||||
// 因此预览曲线与构建出的采样值逐位一致。
|
// 因此预览曲线与构建出的采样值逐位一致。
|
||||||
ComputeWeakWaveBoost(signal.values, static_cast<double>(level) / 100.0, entry.values);
|
ComputeWeakWaveBoost(signal.values, static_cast<double>(level) / 100.0, stages, entry.values);
|
||||||
|
|
||||||
m_weakBoostCurveCache.push_back(std::move(entry));
|
m_weakBoostCurveCache.push_back(std::move(entry));
|
||||||
return m_weakBoostCurveCache.back().values;
|
return m_weakBoostCurveCache.back().values;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PlotView::WeakBoostValueAt(const DataFile& file, const SignalData& signal, int levelPercent,
|
bool PlotView::WeakBoostValueAt(const DataFile& file, const SignalData& signal, int levelPercent,
|
||||||
double time, double& value) const
|
int order, double time, double& value) const
|
||||||
{
|
{
|
||||||
const int index = FindNearestSample(file, time);
|
const int index = FindNearestSample(file, time);
|
||||||
if (index < 0)
|
if (index < 0)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const std::vector<float>& curve = WeakBoostCurveFor(signal, levelPercent);
|
const std::vector<float>& curve = WeakBoostCurveFor(signal, levelPercent, order);
|
||||||
if (static_cast<std::size_t>(index) >= curve.size())
|
if (static_cast<std::size_t>(index) >= curve.size())
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
@@ -2338,9 +2371,11 @@ namespace jdi
|
|||||||
|
|
||||||
if (weakBoost)
|
if (weakBoost)
|
||||||
{
|
{
|
||||||
// 等级滑块行 = 「弱波增强等级」说明文字 + 轨道 + 百分比数值。
|
// 两个滑块行(等级 / 阶数 N)= 「说明文字 + 轨道 + 数值」。
|
||||||
const float labelWidth = UiMeasureTextWidth(m_fonts->uiSmall, kWeakBoostLevelLabel);
|
const float labelWidth = std::max(UiMeasureTextWidth(m_fonts->uiSmall, kWeakBoostLevelLabel),
|
||||||
const float valueWidth = UiMeasureTextWidth(m_fonts->monoSmall, L"100%");
|
UiMeasureTextWidth(m_fonts->uiSmall, kWeakBoostOrderLabel));
|
||||||
|
const float valueWidth = std::max(UiMeasureTextWidth(m_fonts->monoSmall, L"100%"),
|
||||||
|
UiMeasureTextWidth(m_fonts->monoSmall, L"10"));
|
||||||
const float rowWidth = labelWidth + 6.0f * m_scale + kWeakBoostTrackMinWidth * m_scale +
|
const float rowWidth = labelWidth + 6.0f * m_scale + kWeakBoostTrackMinWidth * m_scale +
|
||||||
6.0f * m_scale + valueWidth;
|
6.0f * m_scale + valueWidth;
|
||||||
width = std::max(width, kEdgePanelPad * 2.0f * m_scale + rowWidth);
|
width = std::max(width, kEdgePanelPad * 2.0f * m_scale + rowWidth);
|
||||||
@@ -3277,6 +3312,9 @@ namespace jdi
|
|||||||
case PlotDragMode::BOOST_LEVEL:
|
case PlotDragMode::BOOST_LEVEL:
|
||||||
// 「弱波增强」等级滑块拖拽 → 左右箭头。
|
// 「弱波增强」等级滑块拖拽 → 左右箭头。
|
||||||
return LoadCursorW(nullptr, IDC_SIZEWE);
|
return LoadCursorW(nullptr, IDC_SIZEWE);
|
||||||
|
case PlotDragMode::BOOST_ORDER:
|
||||||
|
// 「弱波增强」阶数 N 滑块拖拽 → 左右箭头。
|
||||||
|
return LoadCursorW(nullptr, IDC_SIZEWE);
|
||||||
case PlotDragMode::MOVE_SIGNAL:
|
case PlotDragMode::MOVE_SIGNAL:
|
||||||
// 拖动信号(参考 Photoshop 平移元素的抓握光标)。
|
// 拖动信号(参考 Photoshop 平移元素的抓握光标)。
|
||||||
return LoadCursorW(nullptr, IDC_SIZEALL);
|
return LoadCursorW(nullptr, IDC_SIZEALL);
|
||||||
@@ -3315,18 +3353,20 @@ namespace jdi
|
|||||||
}
|
}
|
||||||
if (UiPointInRect(layout.close, static_cast<float>(x), static_cast<float>(y)))
|
if (UiPointInRect(layout.close, static_cast<float>(x), static_cast<float>(y)))
|
||||||
{
|
{
|
||||||
// ✕ 按钮:保持原始箭头(仅做悬停高亮提示)。
|
// ✕ 按钮:与面板/标签栏的其它 ✕ 一致,用手型光标作为可点击提示。
|
||||||
return nullptr;
|
return LoadCursorW(nullptr, IDC_HAND);
|
||||||
}
|
}
|
||||||
if (UiPointInRect(layout.title, static_cast<float>(x), static_cast<float>(y)))
|
if (UiPointInRect(layout.title, static_cast<float>(x), static_cast<float>(y)))
|
||||||
{
|
{
|
||||||
return LoadCursorW(nullptr, IDC_SIZEALL);
|
return LoadCursorW(nullptr, IDC_SIZEALL);
|
||||||
}
|
}
|
||||||
if (kind == EdgePanelKind::BOOST &&
|
if (kind == EdgePanelKind::BOOST &&
|
||||||
WeakBoostSliderHit(layout.slider, m_scale, static_cast<float>(x),
|
(WeakBoostSliderHit(layout.slider, m_scale, static_cast<float>(x),
|
||||||
static_cast<float>(y)))
|
static_cast<float>(y)) ||
|
||||||
|
WeakBoostSliderHit(layout.sliderOrder, m_scale, static_cast<float>(x),
|
||||||
|
static_cast<float>(y))))
|
||||||
{
|
{
|
||||||
// 等级滑块:左右箭头表示可左右拖动。
|
// 等级 / 阶数滑块:左右箭头表示可左右拖动。
|
||||||
return LoadCursorW(nullptr, IDC_SIZEWE);
|
return LoadCursorW(nullptr, IDC_SIZEWE);
|
||||||
}
|
}
|
||||||
if ((kind == EdgePanelKind::MEAN || kind == EdgePanelKind::BOOST) &&
|
if ((kind == EdgePanelKind::MEAN || kind == EdgePanelKind::BOOST) &&
|
||||||
@@ -3459,20 +3499,34 @@ namespace jdi
|
|||||||
if (m_rubberAxis == PlotRubberAxis::TIME)
|
if (m_rubberAxis == PlotRubberAxis::TIME)
|
||||||
{
|
{
|
||||||
// 竖向选区贯穿整个绘图区高度,只取横向区间用于时间轴缩放。
|
// 竖向选区贯穿整个绘图区高度,只取横向区间用于时间轴缩放。
|
||||||
const float left = static_cast<float>(std::min(x, m_dragStartX));
|
// **横向必须夹在绘图区内**:鼠标被捕获时可能移动到工具栏 / 信号面板等处,
|
||||||
const float width = static_cast<float>(std::abs(x - m_dragStartX));
|
// 若不夹取,选区会画到绘图区之外(那些区域只会被局部失效,于是残留成"绘制错误")。
|
||||||
|
const float plotLeft = m_plotRect.X;
|
||||||
|
const float plotRight = m_plotRect.X + m_plotRect.Width;
|
||||||
|
const float from = ClampDouble(static_cast<double>(m_dragStartX),
|
||||||
|
static_cast<double>(plotLeft),
|
||||||
|
static_cast<double>(plotRight));
|
||||||
|
const float to = ClampDouble(static_cast<double>(x), static_cast<double>(plotLeft),
|
||||||
|
static_cast<double>(plotRight));
|
||||||
|
const float left = std::min(from, to);
|
||||||
|
const float width = std::fabs(to - from);
|
||||||
m_rubberRect = gd::RectF(left, m_plotRect.Y, width, m_plotRect.Height);
|
m_rubberRect = gd::RectF(left, m_plotRect.Y, width, m_plotRect.Height);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// 横向选区限定在按下时的网格内,只取纵向区间用于幅值轴缩放。
|
// 横向选区限定在按下时的网格内(纵向同样夹在该网格内),只取纵向区间用于幅值轴缩放。
|
||||||
if (m_dragLane < 0 || m_dragLane >= static_cast<int>(m_lanes.size()))
|
if (m_dragLane < 0 || m_dragLane >= static_cast<int>(m_lanes.size()))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const gd::RectF& laneRect = m_lanes[static_cast<std::size_t>(m_dragLane)].rect;
|
const gd::RectF& laneRect = m_lanes[static_cast<std::size_t>(m_dragLane)].rect;
|
||||||
const float top = static_cast<float>(std::min(y, m_dragStartY));
|
const float from = ClampDouble(static_cast<double>(m_dragStartY),
|
||||||
const float bottom = static_cast<float>(std::max(y, m_dragStartY));
|
static_cast<double>(laneRect.Y),
|
||||||
|
static_cast<double>(laneRect.Y + laneRect.Height));
|
||||||
|
const float to = ClampDouble(static_cast<double>(y), static_cast<double>(laneRect.Y),
|
||||||
|
static_cast<double>(laneRect.Y + laneRect.Height));
|
||||||
|
const float top = std::min(from, to);
|
||||||
|
const float bottom = std::max(from, to);
|
||||||
m_rubberRect = gd::RectF(laneRect.X, top, laneRect.Width, bottom - top);
|
m_rubberRect = gd::RectF(laneRect.X, top, laneRect.Width, bottom - top);
|
||||||
}
|
}
|
||||||
m_dragMoved = true;
|
m_dragMoved = true;
|
||||||
@@ -3582,6 +3636,26 @@ namespace jdi
|
|||||||
m_dragMoved = true;
|
m_dragMoved = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
case PlotDragMode::BOOST_ORDER:
|
||||||
|
{
|
||||||
|
// 「弱波增强」阶数 N 滑块:按滑轨上的横向位置换算 1–10(整数)。
|
||||||
|
if (m_tab == nullptr || m_boostDragLane < 0 ||
|
||||||
|
m_boostDragLane >= static_cast<int>(m_lanes.size()))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const EdgePanelLayout layout = EdgePanelLayoutFor(m_boostDragLane, EdgePanelKind::BOOST);
|
||||||
|
if (layout.sliderOrder.Width <= 0.0f)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const double ratio = UiSliderRatioFromX(layout.sliderOrder, fx);
|
||||||
|
const int order = kWeakBoostOrderMin + static_cast<int>(std::lround(
|
||||||
|
ratio * (kWeakBoostOrderMax - kWeakBoostOrderMin)));
|
||||||
|
SetLaneWeakBoostOrder(m_boostDragLane, order);
|
||||||
|
m_dragMoved = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
case PlotDragMode::MOVE_SIGNAL:
|
case PlotDragMode::MOVE_SIGNAL:
|
||||||
{
|
{
|
||||||
const int target = LaneAtY(y);
|
const int target = LaneAtY(y);
|
||||||
@@ -3707,6 +3781,16 @@ namespace jdi
|
|||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ✕ 关闭按钮 / 等级与阶数滑块 / 底部按钮的悬停提示都直接依赖鼠标坐标,
|
||||||
|
// 但它们在面板内部移动时上面这些状态可能毫无变化(例如从同一网格的波形移到面板上),
|
||||||
|
// 于是返回值恒为 false → 宿主不重绘 → **悬停提示不会出现**。
|
||||||
|
// 因此在“鼠标进入 / 离开 / 在面板内移动”时强制标记需要重绘。
|
||||||
|
if ((overEdgePanel || m_mouseOverPanel) && mouseMoved)
|
||||||
|
{
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
m_mouseOverPanel = overEdgePanel;
|
||||||
|
|
||||||
// 时间光标不再跟随鼠标移动,仅在鼠标覆盖光标时才显示具体时间读数。
|
// 时间光标不再跟随鼠标移动,仅在鼠标覆盖光标时才显示具体时间读数。
|
||||||
int hoverWhich = 0;
|
int hoverWhich = 0;
|
||||||
const bool hover = Tab().CursorEnabled() && IsOnCursorLine(x, hoverWhich);
|
const bool hover = Tab().CursorEnabled() && IsOnCursorLine(x, hoverWhich);
|
||||||
@@ -3885,9 +3969,13 @@ namespace jdi
|
|||||||
{
|
{
|
||||||
// ✕:**抬起**时才关闭该网格对应的实验性功能,这里只记录目标;
|
// ✕:**抬起**时才关闭该网格对应的实验性功能,这里只记录目标;
|
||||||
// 按住后移开再松开视为放弃(与面板/标签栏的关闭 ✕ 语义一致)。
|
// 按住后移开再松开视为放弃(与面板/标签栏的关闭 ✕ 语义一致)。
|
||||||
|
// ⚠️ 必须在此**直接消费**该次按下:✕ 位于标题栏矩形内部,
|
||||||
|
// 若继续往下走会被“标题栏 → 拖动面板”分支命中,表现为“按下 ✕ 却拖走了面板”。
|
||||||
m_pendingPanelClose = true;
|
m_pendingPanelClose = true;
|
||||||
m_pendingPanelCloseLane = index;
|
m_pendingPanelCloseLane = index;
|
||||||
m_pendingPanelCloseKind = kind;
|
m_pendingPanelCloseKind = kind;
|
||||||
|
m_dragMode = PlotDragMode::NONE;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 「中心轴边沿计数」/「均值滤波」/「弱波增强」面板:逐信号条目
|
// 「中心轴边沿计数」/「均值滤波」/「弱波增强」面板:逐信号条目
|
||||||
@@ -3900,7 +3988,7 @@ namespace jdi
|
|||||||
const bool boostMode = (kind == EdgePanelKind::BOOST);
|
const bool boostMode = (kind == EdgePanelKind::BOOST);
|
||||||
Lane& tabLane = m_tab->Lanes()[static_cast<std::size_t>(index)];
|
Lane& tabLane = m_tab->Lanes()[static_cast<std::size_t>(index)];
|
||||||
|
|
||||||
// 弱波增强等级滑块:按下即开始拖动(拖动过程中实时更新等级)。
|
// 弱波增强的两个滑块:按下即开始拖动(拖动过程中实时更新)。
|
||||||
if (boostMode && WeakBoostSliderHit(layout.slider, m_scale, fx, fy))
|
if (boostMode && WeakBoostSliderHit(layout.slider, m_scale, fx, fy))
|
||||||
{
|
{
|
||||||
m_dragMode = PlotDragMode::BOOST_LEVEL;
|
m_dragMode = PlotDragMode::BOOST_LEVEL;
|
||||||
@@ -3908,6 +3996,17 @@ namespace jdi
|
|||||||
SetLaneWeakBoostLevel(index, UiSliderRatioFromX(layout.slider, fx));
|
SetLaneWeakBoostLevel(index, UiSliderRatioFromX(layout.slider, fx));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if (boostMode && WeakBoostSliderHit(layout.sliderOrder, m_scale, fx, fy))
|
||||||
|
{
|
||||||
|
m_dragMode = PlotDragMode::BOOST_ORDER;
|
||||||
|
m_boostDragLane = index;
|
||||||
|
const double ratio = UiSliderRatioFromX(layout.sliderOrder, fx);
|
||||||
|
SetLaneWeakBoostOrder(index,
|
||||||
|
kWeakBoostOrderMin +
|
||||||
|
static_cast<int>(std::lround(
|
||||||
|
ratio * (kWeakBoostOrderMax - kWeakBoostOrderMin))));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
for (std::size_t rowIndex = 0; rowIndex < layout.rowRefs.size(); ++rowIndex)
|
for (std::size_t rowIndex = 0; rowIndex < layout.rowRefs.size(); ++rowIndex)
|
||||||
{
|
{
|
||||||
@@ -3970,10 +4069,12 @@ namespace jdi
|
|||||||
if (boostMode && layout.button.Width > 0.0f && UiPointInRect(layout.button, fx, fy))
|
if (boostMode && layout.button.Width > 0.0f && UiPointInRect(layout.button, fx, fy))
|
||||||
{
|
{
|
||||||
// 「弱波增强 → 构建为新信号」:与均值滤波同一套请求机制
|
// 「弱波增强 → 构建为新信号」:与均值滤波同一套请求机制
|
||||||
//(等级属于整个网格,网格内各被勾选信号共用同一个值)。
|
//(等级与阶数属于整个网格,网格内各被勾选信号共用同一组参数)。
|
||||||
const Lane& lane = m_tab->Lanes()[static_cast<std::size_t>(index)];
|
const Lane& lane = m_tab->Lanes()[static_cast<std::size_t>(index)];
|
||||||
const int levelPercent = static_cast<int>(
|
const int levelPercent = static_cast<int>(
|
||||||
std::lround(std::max(0.0, std::min(1.0, lane.weakBoostLevel)) * 100.0));
|
std::lround(std::max(0.0, std::min(1.0, lane.weakBoostLevel)) * 100.0));
|
||||||
|
const int order = std::max(kWeakBoostOrderMin,
|
||||||
|
std::min(kWeakBoostOrderMax, lane.weakBoostOrder));
|
||||||
m_pendingWeakBoostItems.clear();
|
m_pendingWeakBoostItems.clear();
|
||||||
for (const SignalRef& ref : lane.signals)
|
for (const SignalRef& ref : lane.signals)
|
||||||
{
|
{
|
||||||
@@ -3990,13 +4091,14 @@ namespace jdi
|
|||||||
build.fileIndex = ref.fileIndex;
|
build.fileIndex = ref.fileIndex;
|
||||||
build.signalIndex = ref.signalIndex;
|
build.signalIndex = ref.signalIndex;
|
||||||
build.levelPercent = levelPercent;
|
build.levelPercent = levelPercent;
|
||||||
|
build.order = order;
|
||||||
m_pendingWeakBoostItems.push_back(build);
|
m_pendingWeakBoostItems.push_back(build);
|
||||||
}
|
}
|
||||||
if (!m_pendingWeakBoostItems.empty())
|
if (!m_pendingWeakBoostItems.empty())
|
||||||
{
|
{
|
||||||
SetHint(L"正在构建弱波增强信号(等级 " + std::to_wstring(levelPercent) +
|
SetHint(L"正在构建弱波增强信号(等级 " + std::to_wstring(levelPercent) +
|
||||||
L"%,共 " + std::to_wstring(m_pendingWeakBoostItems.size()) +
|
L"% / " + std::to_wstring(order) + L" 阶,共 " +
|
||||||
L" 条)…");
|
std::to_wstring(m_pendingWeakBoostItems.size()) + L" 条)…");
|
||||||
m_pendingWeakBoostLane = index;
|
m_pendingWeakBoostLane = index;
|
||||||
}
|
}
|
||||||
m_dragMode = PlotDragMode::NONE;
|
m_dragMode = PlotDragMode::NONE;
|
||||||
@@ -5156,9 +5258,10 @@ namespace jdi
|
|||||||
mix(static_cast<std::uint64_t>(item.points));
|
mix(static_cast<std::uint64_t>(item.points));
|
||||||
mix(static_cast<std::uint64_t>(item.order));
|
mix(static_cast<std::uint64_t>(item.order));
|
||||||
}
|
}
|
||||||
// 「弱波增强」的开关、增强等级与每个被勾选信号的条目同理。
|
// 「弱波增强」的开关、增强等级、阶数 N 与每个被勾选信号的条目同理。
|
||||||
mix(Tab().Lanes()[laneIndex].weakBoost ? 1ull : 0ull);
|
mix(Tab().Lanes()[laneIndex].weakBoost ? 1ull : 0ull);
|
||||||
mixFloat(static_cast<float>(Tab().Lanes()[laneIndex].weakBoostLevel));
|
mixFloat(static_cast<float>(Tab().Lanes()[laneIndex].weakBoostLevel));
|
||||||
|
mix(static_cast<std::uint64_t>(Tab().Lanes()[laneIndex].weakBoostOrder));
|
||||||
mix(static_cast<std::uint64_t>(Tab().Lanes()[laneIndex].weakBoostItems.size()));
|
mix(static_cast<std::uint64_t>(Tab().Lanes()[laneIndex].weakBoostItems.size()));
|
||||||
for (const Lane::WeakBoostItem& item : Tab().Lanes()[laneIndex].weakBoostItems)
|
for (const Lane::WeakBoostItem& item : Tab().Lanes()[laneIndex].weakBoostItems)
|
||||||
{
|
{
|
||||||
@@ -5756,10 +5859,24 @@ namespace jdi
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 选区只应出现在绘图区内:即使内部矩形意外越界(例如鼠标被捕获到工具栏 /
|
||||||
|
// 信号面板上),也在这里再夹一次,绝不把选区块画到绘图区之外。
|
||||||
|
const float clipLeft = std::max(m_rubberRect.X, m_plotRect.X);
|
||||||
|
const float clipTop = std::max(m_rubberRect.Y, m_plotRect.Y);
|
||||||
|
const float clipRight =
|
||||||
|
std::min(m_rubberRect.X + m_rubberRect.Width, m_plotRect.X + m_plotRect.Width);
|
||||||
|
const float clipBottom =
|
||||||
|
std::min(m_rubberRect.Y + m_rubberRect.Height, m_plotRect.Y + m_plotRect.Height);
|
||||||
|
if (clipRight <= clipLeft || clipBottom <= clipTop)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const gd::RectF bandRect(clipLeft, clipTop, clipRight - clipLeft, clipBottom - clipTop);
|
||||||
|
|
||||||
// 两种方向的选区使用**同一种强调色**,保持视觉一致。
|
// 两种方向的选区使用**同一种强调色**,保持视觉一致。
|
||||||
const std::uint32_t band = m_theme->accent;
|
const std::uint32_t band = m_theme->accent;
|
||||||
UiFillRect(g, m_rubberRect, WithAlpha(band, 0x28));
|
UiFillRect(g, bandRect, WithAlpha(band, 0x28));
|
||||||
UiDrawRoundRect(g, m_rubberRect, 2.0f * m_scale, band, 1.4f);
|
UiDrawRoundRect(g, bandRect, 2.0f * m_scale, band, 1.4f);
|
||||||
|
|
||||||
// 选区跨度提示(时间用秒/数值用幅度量纲),放在选区顶部。
|
// 选区跨度提示(时间用秒/数值用幅度量纲),放在选区顶部。
|
||||||
std::wstring text;
|
std::wstring text;
|
||||||
@@ -5781,9 +5898,9 @@ namespace jdi
|
|||||||
|
|
||||||
const float textWidth = UiMeasureTextWidth(m_fonts->monoSmall, text) + 12.0f * m_scale;
|
const float textWidth = UiMeasureTextWidth(m_fonts->monoSmall, text) + 12.0f * m_scale;
|
||||||
const float textHeight = 18.0f * m_scale;
|
const float textHeight = 18.0f * m_scale;
|
||||||
float labelX = m_rubberRect.X + m_rubberRect.Width * 0.5f - textWidth * 0.5f;
|
float labelX = bandRect.X + bandRect.Width * 0.5f - textWidth * 0.5f;
|
||||||
labelX = std::max(m_plotRect.X, std::min(labelX, m_plotRect.X + m_plotRect.Width - textWidth));
|
labelX = std::max(m_plotRect.X, std::min(labelX, m_plotRect.X + m_plotRect.Width - textWidth));
|
||||||
const gd::RectF labelRect(labelX, m_rubberRect.Y + 3.0f * m_scale, textWidth, textHeight);
|
const gd::RectF labelRect(labelX, bandRect.Y + 3.0f * m_scale, textWidth, textHeight);
|
||||||
|
|
||||||
UiFillRoundRect(g, labelRect, 3.0f * m_scale, band);
|
UiFillRoundRect(g, labelRect, 3.0f * m_scale, band);
|
||||||
UiText(g, text, m_fonts->monoSmall, labelRect, m_theme->textOnAccent, TextAlign::CENTER);
|
UiText(g, text, m_fonts->monoSmall, labelRect, m_theme->textOnAccent, TextAlign::CENTER);
|
||||||
@@ -6129,7 +6246,11 @@ namespace jdi
|
|||||||
Tab().Lanes()[static_cast<std::size_t>(laneIndex)]
|
Tab().Lanes()[static_cast<std::size_t>(laneIndex)]
|
||||||
.weakBoostLevel)) *
|
.weakBoostLevel)) *
|
||||||
100.0));
|
100.0));
|
||||||
PaintWeakBoostCurve(g, resolved, lane, levelPercent);
|
const int order = std::max(
|
||||||
|
kWeakBoostOrderMin,
|
||||||
|
std::min(kWeakBoostOrderMax,
|
||||||
|
Tab().Lanes()[static_cast<std::size_t>(laneIndex)].weakBoostOrder));
|
||||||
|
PaintWeakBoostCurve(g, resolved, lane, levelPercent, order);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6960,7 +7081,10 @@ namespace jdi
|
|||||||
const int levelPercent = static_cast<int>(std::lround(
|
const int levelPercent = static_cast<int>(std::lround(
|
||||||
std::max(0.0, std::min(1.0, m_tab->Lanes()[laneIndex].weakBoostLevel)) *
|
std::max(0.0, std::min(1.0, m_tab->Lanes()[laneIndex].weakBoostLevel)) *
|
||||||
100.0));
|
100.0));
|
||||||
paintDerivedCapsule(WeakBoostCurveFor(*resolved.data, levelPercent),
|
const int order = std::max(
|
||||||
|
kWeakBoostOrderMin,
|
||||||
|
std::min(kWeakBoostOrderMax, m_tab->Lanes()[laneIndex].weakBoostOrder));
|
||||||
|
paintDerivedCapsule(WeakBoostCurveFor(*resolved.data, levelPercent, order),
|
||||||
L"弱波增强");
|
L"弱波增强");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7082,8 +7206,9 @@ namespace jdi
|
|||||||
// 因此不再有独立的底部编辑行;只有「边沿计数」保留两行文本式排版。
|
// 因此不再有独立的底部编辑行;只有「边沿计数」保留两行文本式排版。
|
||||||
const float buttonHeight = hasButton ? (kEdgePanelButtonHeight * m_scale) : 0.0f;
|
const float buttonHeight = hasButton ? (kEdgePanelButtonHeight * m_scale) : 0.0f;
|
||||||
const float rowsPerSignal = meanFilter ? 1.0f : 2.0f;
|
const float rowsPerSignal = meanFilter ? 1.0f : 2.0f;
|
||||||
// 「弱波增强」在条目与底部按钮之间额外有一行等级滑块,其余逐条目面板没有。
|
// 「弱波增强」在条目与底部按钮之间额外有两行滑块(等级 / 阶数 N),其余逐条目面板没有。
|
||||||
const float sliderRowHeight = weakBoost ? (kWeakBoostSliderRowHeight * m_scale) : 0.0f;
|
const float sliderRowHeight = weakBoost ? (kWeakBoostSliderRowHeight * m_scale) : 0.0f;
|
||||||
|
const float sliderRowsHeight = weakBoost ? (sliderRowHeight * 2.0f) : 0.0f;
|
||||||
|
|
||||||
// 逐条目面板:每个条目 = 第一排(信号名 + 复选框)
|
// 逐条目面板:每个条目 = 第一排(信号名 + 复选框)
|
||||||
// + 第二排(均值点数 X / 阶数 N)+(中心轴)第三排(统计结果);
|
// + 第二排(均值点数 X / 阶数 N)+(中心轴)第三排(统计结果);
|
||||||
@@ -7116,7 +7241,7 @@ namespace jdi
|
|||||||
}
|
}
|
||||||
// 底部按钮行:上方与条目之间、下方与面板下边界之间各留一个 `pad`,
|
// 底部按钮行:上方与条目之间、下方与面板下边界之间各留一个 `pad`,
|
||||||
// 与面板的左右内边距完全一致(原先按钮直接贴着下边框,看起来很不协调)。
|
// 与面板的左右内边距完全一致(原先按钮直接贴着下边框,看起来很不协调)。
|
||||||
height = titleHeight + pad + itemsHeight + sliderRowHeight + pad + buttonHeight +
|
height = titleHeight + pad + itemsHeight + sliderRowsHeight + pad + buttonHeight +
|
||||||
(hasButton ? pad : 0.0f);
|
(hasButton ? pad : 0.0f);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -7162,18 +7287,25 @@ namespace jdi
|
|||||||
std::max(0.0f, width - pad * 2.0f), buttonRowHeight);
|
std::max(0.0f, width - pad * 2.0f), buttonRowHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 弱波增强等级滑块行(位于信号条目与底部按钮之间)。
|
// 弱波增强的两个滑块行(等级 / 阶数 N,位于信号条目与底部按钮之间)。
|
||||||
if (weakBoost)
|
if (weakBoost)
|
||||||
{
|
{
|
||||||
const float labelWidth = UiMeasureTextWidth(m_fonts->uiSmall, kWeakBoostLevelLabel);
|
const float labelWidth =
|
||||||
const float valueWidth = UiMeasureTextWidth(m_fonts->monoSmall, L"100%");
|
std::max(UiMeasureTextWidth(m_fonts->uiSmall, kWeakBoostLevelLabel),
|
||||||
|
UiMeasureTextWidth(m_fonts->uiSmall, kWeakBoostOrderLabel));
|
||||||
|
const float valueWidth = std::max(UiMeasureTextWidth(m_fonts->monoSmall, L"100%"),
|
||||||
|
UiMeasureTextWidth(m_fonts->monoSmall, L"10"));
|
||||||
const float trackHeight = kWeakBoostTrackHeight * m_scale;
|
const float trackHeight = kWeakBoostTrackHeight * m_scale;
|
||||||
const float rowTop = top + titleHeight + pad + itemsHeight;
|
const float firstRowTop = top + titleHeight + pad + itemsHeight;
|
||||||
const float trackLeft = left + pad + labelWidth + 6.0f * m_scale;
|
const float trackLeft = left + pad + labelWidth + 6.0f * m_scale;
|
||||||
const float trackRight = left + width - pad - valueWidth - 6.0f * m_scale;
|
const float trackRight = left + width - pad - valueWidth - 6.0f * m_scale;
|
||||||
|
const float trackWidth = std::max(10.0f * m_scale, trackRight - trackLeft);
|
||||||
layout.slider =
|
layout.slider =
|
||||||
gd::RectF(trackLeft, rowTop + (sliderRowHeight - trackHeight) * 0.5f,
|
gd::RectF(trackLeft, firstRowTop + (sliderRowHeight - trackHeight) * 0.5f,
|
||||||
std::max(10.0f * m_scale, trackRight - trackLeft), trackHeight);
|
trackWidth, trackHeight);
|
||||||
|
layout.sliderOrder =
|
||||||
|
gd::RectF(trackLeft, firstRowTop + sliderRowHeight + (sliderRowHeight - trackHeight) * 0.5f,
|
||||||
|
trackWidth, trackHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 逐信号条目:第一排 = 名称 + 复选框(复选框靠右);
|
// 逐信号条目:第一排 = 名称 + 复选框(复选框靠右);
|
||||||
@@ -7568,14 +7700,14 @@ namespace jdi
|
|||||||
}
|
}
|
||||||
|
|
||||||
void PlotView::PaintWeakBoostCurve(gd::Graphics& g, const ResolvedSignal& resolved,
|
void PlotView::PaintWeakBoostCurve(gd::Graphics& g, const ResolvedSignal& resolved,
|
||||||
const LaneVisual& lane, int levelPercent) const
|
const LaneVisual& lane, int levelPercent, int order) const
|
||||||
{
|
{
|
||||||
if (resolved.data == nullptr || resolved.file == nullptr || resolved.style == nullptr)
|
if (resolved.data == nullptr || resolved.file == nullptr || resolved.style == nullptr)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// 派生曲线一律使用同一段绘制逻辑与配色规则(弱波增强曲线同样如此)。
|
// 派生曲线一律使用同一段绘制逻辑与配色规则(弱波增强曲线同样如此)。
|
||||||
PaintDerivedCurve(g, resolved, lane, WeakBoostCurveFor(*resolved.data, levelPercent));
|
PaintDerivedCurve(g, resolved, lane, WeakBoostCurveFor(*resolved.data, levelPercent, order));
|
||||||
}
|
}
|
||||||
|
|
||||||
void PlotView::PaintEdgeCursor(gd::Graphics& g, const std::vector<LaneVisual>& lanes)
|
void PlotView::PaintEdgeCursor(gd::Graphics& g, const std::vector<LaneVisual>& lanes)
|
||||||
@@ -7858,33 +7990,55 @@ namespace jdi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 「弱波增强」等级滑块:左侧说明文字 + 轨道 + 右侧百分比数值。
|
// 「弱波增强」的两个滑块(等级 0–100% / 阶数 N 1–10):
|
||||||
|
// 左侧说明文字 + 轨道 + 右侧数值。
|
||||||
if (weakBoost && layout.slider.Width > 0.0f)
|
if (weakBoost && layout.slider.Width > 0.0f)
|
||||||
{
|
{
|
||||||
const gd::RectF& track = layout.slider;
|
const float rowHeight =
|
||||||
const float labelWidth = UiMeasureTextWidth(m_fonts->uiSmall, kWeakBoostLevelLabel);
|
std::max(kWeakBoostSliderRowHeight * m_scale, layout.slider.Height);
|
||||||
const float rowHeight = std::max(kWeakBoostSliderRowHeight * m_scale, track.Height);
|
const auto paintSliderRow = [&](const gd::RectF& track, const wchar_t* label,
|
||||||
UiText(g, kWeakBoostLevelLabel, m_fonts->uiSmall,
|
double ratio, const std::wstring& valueText,
|
||||||
|
bool active)
|
||||||
|
{
|
||||||
|
if (track.Width <= 0.0f)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const float labelWidth = UiMeasureTextWidth(m_fonts->uiSmall, label);
|
||||||
|
UiText(g, label, m_fonts->uiSmall,
|
||||||
gd::RectF(track.X - 6.0f * m_scale - labelWidth,
|
gd::RectF(track.X - 6.0f * m_scale - labelWidth,
|
||||||
track.Y + track.Height * 0.5f - rowHeight * 0.5f, labelWidth,
|
track.Y + track.Height * 0.5f - rowHeight * 0.5f,
|
||||||
rowHeight),
|
labelWidth, rowHeight),
|
||||||
m_theme->textMuted, TextAlign::LEFT);
|
m_theme->textMuted, TextAlign::LEFT);
|
||||||
|
|
||||||
const double level =
|
|
||||||
std::max(0.0, std::min(1.0, tabLane.weakBoostLevel));
|
|
||||||
const bool active = (m_dragMode == PlotDragMode::BOOST_LEVEL) &&
|
|
||||||
(m_boostDragLane == static_cast<int>(index));
|
|
||||||
// 与样式区/拾色器共用同一套滑块绘制(轨道 + 已填充部分 + 圆钮)。
|
// 与样式区/拾色器共用同一套滑块绘制(轨道 + 已填充部分 + 圆钮)。
|
||||||
UiDrawSlider(g, track, level, m_theme->accent, active, m_scale);
|
UiDrawSlider(g, track, ratio, m_theme->accent, active, m_scale);
|
||||||
|
|
||||||
const std::wstring levelText =
|
const float valueWidth = UiMeasureTextWidth(m_fonts->monoSmall, valueText);
|
||||||
std::to_wstring(static_cast<int>(std::lround(level * 100.0))) + L"%";
|
UiText(g, valueText, m_fonts->monoSmall,
|
||||||
const float valueWidth = UiMeasureTextWidth(m_fonts->monoSmall, levelText);
|
|
||||||
UiText(g, levelText, m_fonts->monoSmall,
|
|
||||||
gd::RectF(track.X + track.Width + 6.0f * m_scale,
|
gd::RectF(track.X + track.Width + 6.0f * m_scale,
|
||||||
track.Y + track.Height * 0.5f - rowHeight * 0.5f, valueWidth,
|
track.Y + track.Height * 0.5f - rowHeight * 0.5f,
|
||||||
rowHeight),
|
valueWidth, rowHeight),
|
||||||
m_theme->textStrong, TextAlign::LEFT);
|
m_theme->textStrong, TextAlign::LEFT);
|
||||||
|
};
|
||||||
|
|
||||||
|
const double level = std::max(0.0, std::min(1.0, tabLane.weakBoostLevel));
|
||||||
|
const bool levelActive = (m_dragMode == PlotDragMode::BOOST_LEVEL) &&
|
||||||
|
(m_boostDragLane == static_cast<int>(index));
|
||||||
|
paintSliderRow(layout.slider, kWeakBoostLevelLabel, level,
|
||||||
|
std::to_wstring(static_cast<int>(std::lround(level * 100.0))) +
|
||||||
|
L"%",
|
||||||
|
levelActive);
|
||||||
|
|
||||||
|
const int order = std::max(kWeakBoostOrderMin,
|
||||||
|
std::min(kWeakBoostOrderMax, tabLane.weakBoostOrder));
|
||||||
|
const double orderRatio = static_cast<double>(order - kWeakBoostOrderMin) /
|
||||||
|
static_cast<double>(kWeakBoostOrderMax -
|
||||||
|
kWeakBoostOrderMin);
|
||||||
|
const bool orderActive = (m_dragMode == PlotDragMode::BOOST_ORDER) &&
|
||||||
|
(m_boostDragLane == static_cast<int>(index));
|
||||||
|
paintSliderRow(layout.sliderOrder, kWeakBoostOrderLabel, orderRatio,
|
||||||
|
std::to_wstring(order), orderActive);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -513,6 +513,15 @@ namespace jdi
|
|||||||
/** 弱波增强等级(0–1),0% = 目标取左右极值中较小/较大的那一个,100% = 取另一个。 */
|
/** 弱波增强等级(0–1),0% = 目标取左右极值中较小/较大的那一个,100% = 取另一个。 */
|
||||||
double weakBoostLevel = 0.5;
|
double weakBoostLevel = 0.5;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 弱波增强的阶数 N(1–10)。
|
||||||
|
*
|
||||||
|
* 与「均值滤波」的阶数 N 一样表示“把同一处理重复应用 N 次”(每一阶以上一阶为输入):
|
||||||
|
* 一阶时改定量就已严格等于「等级」对应的目标值;多阶会把相邻弱波互相抬高
|
||||||
|
* 彼此的基准,从而把较宽的弱波区逐步恢复。
|
||||||
|
*/
|
||||||
|
int weakBoostOrder = 1;
|
||||||
|
|
||||||
/** 参与弱波增强预览的信号条目(只包含被勾选的信号)。 */
|
/** 参与弱波增强预览的信号条目(只包含被勾选的信号)。 */
|
||||||
std::vector<WeakBoostItem> weakBoostItems;
|
std::vector<WeakBoostItem> weakBoostItems;
|
||||||
|
|
||||||
@@ -585,8 +594,11 @@ namespace jdi
|
|||||||
/**
|
/**
|
||||||
* @brief 「弱波增强」:把比周边更弱的纹波波峰/波谷恢复(自适应弱波增强)。
|
* @brief 「弱波增强」:把比周边更弱的纹波波峰/波谷恢复(自适应弱波增强)。
|
||||||
*
|
*
|
||||||
* 思路(全部 O(n)):
|
* 本算法**不做任何前置滤波**:需要平滑时用户可以先用「均值滤波」构建一条
|
||||||
* 1. **识别趋势与极值**:对轻度平滑后的信号扫描单调段,方向发生翻转处即为
|
* 滤波后的信号,再对它做弱波增强。极值点直接在输入采样上识别。
|
||||||
|
*
|
||||||
|
* 单阶处理(全 O(n)):
|
||||||
|
* 1. **识别趋势与极值**:扫描单调段,方向发生翻转处的上一个采样点即为
|
||||||
* 波峰(先升后降)或波谷(先降后升);波峰与波谷交替出现。
|
* 波峰(先升后降)或波谷(先降后升);波峰与波谷交替出现。
|
||||||
* 2. **判定“弱”**:对第 k 个波峰,取同类的左右相邻波峰(k−2、k+2)。
|
* 2. **判定“弱”**:对第 k 个波峰,取同类的左右相邻波峰(k−2、k+2)。
|
||||||
* 若本波峰低于两者的均值,说明它是被削弱的那个;波谷反之(高于两者均值)。
|
* 若本波峰低于两者的均值,说明它是被削弱的那个;波谷反之(高于两者均值)。
|
||||||
@@ -598,14 +610,14 @@ namespace jdi
|
|||||||
* 4. **平滑偏移**:极值点本身取其目标值,两侧的数据点按相邻两个极值点的偏移量
|
* 4. **平滑偏移**:极值点本身取其目标值,两侧的数据点按相邻两个极值点的偏移量
|
||||||
* 平滑过渡(平滑步进,两端导数为 0),使波形整体趋势不变、只在弱波附近被推回。
|
* 平滑过渡(平滑步进,两端导数为 0),使波形整体趋势不变、只在弱波附近被推回。
|
||||||
*
|
*
|
||||||
* 与“全局缩放/局部放大”不同,这里**只改动被判为弱波的极值及其两侧**,
|
|
||||||
* 正常周期的极值不满足条件、偏移量恒为 0,因此输出与输入逐位相同。
|
* 正常周期的极值不满足条件、偏移量恒为 0,因此输出与输入逐位相同。
|
||||||
*
|
*
|
||||||
* @param [IN] values 原始采样值。
|
* @param [IN] values 原始采样值。
|
||||||
* @param [IN] level 弱波增强等级(0–1,超出范围会被夹取)。
|
* @param [IN] level 弱波增强等级(0–1,超出范围会被夹取)。
|
||||||
|
* @param [IN] order 阶数 N(重复应用次数,至少为 1;与均值滤波的阶数同一语义)。
|
||||||
* @param [OUT] out 输出数组(长度与 values 相同;原值为 NaN 处保持 NaN)。
|
* @param [OUT] out 输出数组(长度与 values 相同;原值为 NaN 处保持 NaN)。
|
||||||
*/
|
*/
|
||||||
void ComputeWeakWaveBoost(const std::vector<double>& values, double level,
|
void ComputeWeakWaveBoost(const std::vector<double>& values, double level, int order,
|
||||||
std::vector<float>& out);
|
std::vector<float>& out);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+86
-66
@@ -137,6 +137,7 @@ namespace jdi
|
|||||||
bool weakBoost = false;
|
bool weakBoost = false;
|
||||||
std::vector<Lane::WeakBoostItem> weakBoostItems;
|
std::vector<Lane::WeakBoostItem> weakBoostItems;
|
||||||
double weakBoostLevel = 0.5;
|
double weakBoostLevel = 0.5;
|
||||||
|
int weakBoostOrder = 1;
|
||||||
bool weakBoostPanelOpen = true;
|
bool weakBoostPanelOpen = true;
|
||||||
double weakBoostPanelX = -1.0;
|
double weakBoostPanelX = -1.0;
|
||||||
double weakBoostPanelY = -1.0;
|
double weakBoostPanelY = -1.0;
|
||||||
@@ -168,6 +169,7 @@ namespace jdi
|
|||||||
carry.weakBoost = lane.weakBoost;
|
carry.weakBoost = lane.weakBoost;
|
||||||
carry.weakBoostItems = lane.weakBoostItems;
|
carry.weakBoostItems = lane.weakBoostItems;
|
||||||
carry.weakBoostLevel = lane.weakBoostLevel;
|
carry.weakBoostLevel = lane.weakBoostLevel;
|
||||||
|
carry.weakBoostOrder = lane.weakBoostOrder;
|
||||||
carry.weakBoostPanelOpen = lane.weakBoostPanelOpen;
|
carry.weakBoostPanelOpen = lane.weakBoostPanelOpen;
|
||||||
carry.weakBoostPanelX = lane.weakBoostPanelX;
|
carry.weakBoostPanelX = lane.weakBoostPanelX;
|
||||||
carry.weakBoostPanelY = lane.weakBoostPanelY;
|
carry.weakBoostPanelY = lane.weakBoostPanelY;
|
||||||
@@ -210,6 +212,7 @@ namespace jdi
|
|||||||
lane.weakBoost = carry.weakBoost;
|
lane.weakBoost = carry.weakBoost;
|
||||||
lane.weakBoostItems = carry.weakBoostItems;
|
lane.weakBoostItems = carry.weakBoostItems;
|
||||||
lane.weakBoostLevel = carry.weakBoostLevel;
|
lane.weakBoostLevel = carry.weakBoostLevel;
|
||||||
|
lane.weakBoostOrder = carry.weakBoostOrder;
|
||||||
lane.weakBoostPanelOpen = carry.weakBoostPanelOpen;
|
lane.weakBoostPanelOpen = carry.weakBoostPanelOpen;
|
||||||
lane.weakBoostPanelX = carry.weakBoostPanelX;
|
lane.weakBoostPanelX = carry.weakBoostPanelX;
|
||||||
lane.weakBoostPanelY = carry.weakBoostPanelY;
|
lane.weakBoostPanelY = carry.weakBoostPanelY;
|
||||||
@@ -1056,93 +1059,62 @@ namespace jdi
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 取用于“识别波峰/波谷”的轻度平滑值。
|
* @brief 「弱波增强」的单阶处理:把偏弱的波峰上拉、偏弱的波谷下拉。
|
||||||
*
|
*
|
||||||
* 直接对原始采样做方向判定时,采样噪声会被当成大量假波峰/波谷,使增强曲线抖动;
|
* **不做任何前置滤波**(需要平滑时用户可以先用「均值滤波」构建一条滤波后的信号):
|
||||||
* 因此先看一下 5 点滑动平均(半径 2)之后的“形状”。这里按需就地计算,
|
* 极值点直接在输入采样上通过单调段的方向翻转识别。
|
||||||
* 不额外分配与采样数同量级的缓冲。
|
|
||||||
*
|
*
|
||||||
* @param [IN] values 原始采样值。
|
* @param [IN] input 本阶的输入采样值。
|
||||||
* @param [IN] index 采样下标。
|
* @param [IN] strength 弱波增强等级(0–1,已夹取)。
|
||||||
* @return 轻度平滑后的取值;邻域内没有有效值时返回 NaN。
|
* @param [OUT] output 本阶的输出(长度与 input 相同;原值为 NaN 处保持 NaN)。
|
||||||
|
* @return 确实有采样点被改动返回 true;完全无弱波时返回 false。
|
||||||
*/
|
*/
|
||||||
double BoostShapeAt(const std::vector<double>& values, std::size_t index)
|
bool ApplyWeakBoostStage(const std::vector<float>& input, double strength,
|
||||||
|
std::vector<float>& output)
|
||||||
{
|
{
|
||||||
const std::size_t count = values.size();
|
const std::size_t count = input.size();
|
||||||
const std::size_t low = (index > 2) ? (index - 2) : 0;
|
output.assign(count, std::numeric_limits<float>::quiet_NaN());
|
||||||
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());
|
|
||||||
if (count == 0)
|
if (count == 0)
|
||||||
{
|
{
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 先把有效采样原样拷入输出,之后按“极值点之间的平滑偏移”逐点叠加。
|
// 先把有效采样原样拷入输出,之后按“极值点之间的平滑偏移”逐点叠加。
|
||||||
for (std::size_t index = 0; index < count; ++index)
|
for (std::size_t index = 0; index < count; ++index)
|
||||||
{
|
{
|
||||||
const double value = values[index];
|
const float value = input[index];
|
||||||
if (std::isfinite(value))
|
if (std::isfinite(value))
|
||||||
{
|
{
|
||||||
out[index] = static_cast<float>(value);
|
output[index] = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 增强等级:波峰取左右两个波峰中较小者为 0%、较大者为 100%,
|
|
||||||
// 目标值 = 较小 + 等级 × (较大 − 较小)(波谷相反)。
|
|
||||||
const double strength = std::max(0.0, std::min(1.0, level));
|
|
||||||
|
|
||||||
// 1) 识别波峰 / 波谷:扫描单调段,方向翻转处的上一个采样点即为极值点。
|
// 1) 识别波峰 / 波谷:扫描单调段,方向翻转处的上一个采样点即为极值点。
|
||||||
// 波峰(先升后降)与波谷(先降后升)必然交替出现,因此同类极值就是下标 ±2 的两个。
|
// 波峰(先升后降)与波谷(先降后升)必然交替出现,
|
||||||
|
// 因此同类极值就是极值序列里下标 ±2 的两个。
|
||||||
std::vector<BoostExtremum> extremes;
|
std::vector<BoostExtremum> extremes;
|
||||||
{
|
{
|
||||||
int direction = 0; // 当前单调段方向(+1 上升、-1 下降)。
|
int direction = 0; // 当前单调段方向(+1 上升、-1 下降)。
|
||||||
bool hasPrevious = false;
|
bool hasPrevious = false;
|
||||||
std::size_t previousIndex = 0;
|
std::size_t previousIndex = 0;
|
||||||
double previousShape = 0.0;
|
double previousValue = 0.0;
|
||||||
for (std::size_t index = 0; index < count; ++index)
|
for (std::size_t index = 0; index < count; ++index)
|
||||||
{
|
{
|
||||||
if (!std::isfinite(values[index]))
|
const double value = static_cast<double>(input[index]);
|
||||||
|
if (!std::isfinite(value))
|
||||||
{
|
{
|
||||||
// 无效采样作为断点:不跨越它判定极值。
|
// 无效采样作为断点:不跨越它判定极值。
|
||||||
hasPrevious = false;
|
hasPrevious = false;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const double shape = BoostShapeAt(values, index);
|
|
||||||
if (!std::isfinite(shape))
|
|
||||||
{
|
|
||||||
hasPrevious = false;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!hasPrevious)
|
if (!hasPrevious)
|
||||||
{
|
{
|
||||||
hasPrevious = true;
|
hasPrevious = true;
|
||||||
previousIndex = index;
|
previousIndex = index;
|
||||||
previousShape = shape;
|
previousValue = value;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const int step = (shape > previousShape) ? 1 : ((shape < previousShape) ? -1 : 0);
|
const int step = (value > previousValue) ? 1 : ((value < previousValue) ? -1 : 0);
|
||||||
if (step != 0)
|
if (step != 0)
|
||||||
{
|
{
|
||||||
if (direction == 0)
|
if (direction == 0)
|
||||||
@@ -1154,21 +1126,21 @@ namespace jdi
|
|||||||
// 方向在此翻转:上一个采样点就是极值(原方向向上则为波峰)。
|
// 方向在此翻转:上一个采样点就是极值(原方向向上则为波峰)。
|
||||||
BoostExtremum extreme;
|
BoostExtremum extreme;
|
||||||
extreme.index = previousIndex;
|
extreme.index = previousIndex;
|
||||||
extreme.value = values[previousIndex];
|
extreme.value = previousValue;
|
||||||
extreme.type = direction;
|
extreme.type = direction;
|
||||||
extremes.push_back(extreme);
|
extremes.push_back(extreme);
|
||||||
direction = step;
|
direction = step;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
previousIndex = index;
|
previousIndex = index;
|
||||||
previousShape = shape;
|
previousValue = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (extremes.size() < 3)
|
// 识别不到足够多的极值(常量 / 单调 / 采样点太少):本阶无需任何改动。
|
||||||
|
if (extremes.size() < 5)
|
||||||
{
|
{
|
||||||
// 找不到足够的极值(例如常量或单调信号):无可增强之处。
|
return false;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2) 逐个极值点判定“是否偏弱”,并算出它自己的目标偏移量。
|
// 2) 逐个极值点判定“是否偏弱”,并算出它自己的目标偏移量。
|
||||||
@@ -1215,39 +1187,86 @@ namespace jdi
|
|||||||
|
|
||||||
// 3) 把偏移量平滑地摊到极值点之间的数据点上:
|
// 3) 把偏移量平滑地摊到极值点之间的数据点上:
|
||||||
// 相邻两个极值点之间用平滑步进过渡(两端导数为 0,波形不会出现折角),
|
// 相邻两个极值点之间用平滑步进过渡(两端导数为 0,波形不会出现折角),
|
||||||
// 数据首尾则从 0 渐变到最近的极值点偏移,保证端点原样不动。
|
// 数据首尾则从 0 渐变到最近的极值点偏移。
|
||||||
const auto applySpan = [&values, &out, count](std::size_t begin, std::size_t end,
|
// 注意:极值序列的首尾各两个元素的偏移量恒为 0(缺少同类的左右邻居),
|
||||||
|
// 因此这里只需要填充 shift 非零的区间。
|
||||||
|
bool changed = false;
|
||||||
|
const auto applySpan = [&input, &output, count](std::size_t begin, std::size_t end,
|
||||||
double beginShift, double endShift)
|
double beginShift, double endShift)
|
||||||
{
|
{
|
||||||
if (end <= begin)
|
if (end <= begin)
|
||||||
{
|
{
|
||||||
if (begin < count && std::isfinite(values[begin]))
|
if (begin < count && std::isfinite(input[begin]))
|
||||||
{
|
{
|
||||||
out[begin] = static_cast<float>(values[begin] + beginShift);
|
output[begin] = static_cast<float>(input[begin] + beginShift);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const double span = static_cast<double>(end - begin);
|
const double span = static_cast<double>(end - begin);
|
||||||
for (std::size_t index = begin; index <= end; ++index)
|
for (std::size_t index = begin; index <= end; ++index)
|
||||||
{
|
{
|
||||||
const double value = values[index];
|
const float value = input[index];
|
||||||
if (!std::isfinite(value))
|
if (!std::isfinite(value))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const double t = static_cast<double>(index - begin) / span;
|
const double t = static_cast<double>(index - begin) / span;
|
||||||
const double weight = t * t * (3.0 - 2.0 * t);
|
const double weight = t * t * (3.0 - 2.0 * t);
|
||||||
out[index] = static_cast<float>(value + beginShift * (1.0 - weight) +
|
output[index] = static_cast<float>(value + beginShift * (1.0 - weight) +
|
||||||
endShift * weight);
|
endShift * weight);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
applySpan(0, extremes.front().index, 0.0, shift.front());
|
|
||||||
for (std::size_t k = 0; k + 1 < extremeCount; ++k)
|
for (std::size_t k = 0; k + 1 < extremeCount; ++k)
|
||||||
{
|
{
|
||||||
applySpan(extremes[k].index, extremes[k + 1].index, shift[k], shift[k + 1]);
|
if (shift[k] == 0.0 && shift[k + 1] == 0.0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
applySpan(extremes.back().index, count - 1, shift.back(), 0.0);
|
applySpan(extremes[k].index, extremes[k + 1].index, shift[k], shift[k + 1]);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void ComputeWeakWaveBoost(const std::vector<double>& values, double level, int order,
|
||||||
|
std::vector<float>& out)
|
||||||
|
{
|
||||||
|
const std::size_t count = values.size();
|
||||||
|
out.assign(count, std::numeric_limits<float>::quiet_NaN());
|
||||||
|
if (count == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 第 1 阶:从原始采样值出发(非有限值保持 NaN)。
|
||||||
|
std::vector<float> current(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))
|
||||||
|
{
|
||||||
|
current[index] = static_cast<float>(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 「阶数 N」与「均值滤波」的阶数语义一致:把同一处理**重复应用 N 次**,
|
||||||
|
// 每一阶以上一阶的结果为输入。一阶时改定量已严格等于「等级」对应的目标值;
|
||||||
|
// 多阶时相邻的弱波会互相抬高彼此的基准,从而把较宽的弱波区逐步恢复。
|
||||||
|
const int stages = std::max(1, std::min(order, 1000));
|
||||||
|
const double strength = std::max(0.0, std::min(1.0, level));
|
||||||
|
std::vector<float> next;
|
||||||
|
for (int stage = 0; stage < stages; ++stage)
|
||||||
|
{
|
||||||
|
if (!ApplyWeakBoostStage(current, strength, next))
|
||||||
|
{
|
||||||
|
// 这一阶没有任何采样点需要调整:后续阶数也不会再有变化,提前结束。
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
current.swap(next);
|
||||||
|
}
|
||||||
|
out.swap(current);
|
||||||
}
|
}
|
||||||
|
|
||||||
const Lane::MeanFilterItem* Lane::FindMeanFilterItem(const SignalRef& ref) const
|
const Lane::MeanFilterItem* Lane::FindMeanFilterItem(const SignalRef& ref) const
|
||||||
@@ -1893,6 +1912,7 @@ namespace jdi
|
|||||||
lane.edgeCenterPanelY = source.edgeCenterPanelY;
|
lane.edgeCenterPanelY = source.edgeCenterPanelY;
|
||||||
lane.weakBoost = source.weakBoost;
|
lane.weakBoost = source.weakBoost;
|
||||||
lane.weakBoostLevel = source.weakBoostLevel;
|
lane.weakBoostLevel = source.weakBoostLevel;
|
||||||
|
lane.weakBoostOrder = source.weakBoostOrder;
|
||||||
lane.weakBoostPanelOpen = source.weakBoostPanelOpen;
|
lane.weakBoostPanelOpen = source.weakBoostPanelOpen;
|
||||||
lane.weakBoostPanelX = source.weakBoostPanelX;
|
lane.weakBoostPanelX = source.weakBoostPanelX;
|
||||||
lane.weakBoostPanelY = source.weakBoostPanelY;
|
lane.weakBoostPanelY = source.weakBoostPanelY;
|
||||||
|
|||||||
Reference in new issue
Block a user