1. 添加计算时间戳宏
2. 重写切割两片玻璃函数
3. 添加分组过滤干扰点的函数
已修改2个文件
266 ■■■■ 文件已修改
SourceCode/Bond/SGMeasurement/SGMeasurementDlg.cpp 248 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/SGMeasurement/SGMeasurementDlg.h 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/SGMeasurement/SGMeasurementDlg.cpp
@@ -35,6 +35,19 @@
#define TIMER_INTERVAL_MS        500
#define TIMER_ID_OUTPUT_UPDATE    1
// 计时宏定义
#define MEASURE_FUNC_START() \
    clock_t __startClock = clock();
#define MEASURE_FUNC_END() \
    do { \
        clock_t __endClock = clock(); \
        double __elapsedMs = 1000.0 * (__endClock - __startClock) / CLOCKS_PER_SEC; \
        CString __strElapsed; \
        __strElapsed.Format(_T("%s 执行耗时:%.1f ms"), _T(__FUNCTION__), __elapsedMs); \
        AppendLogLineRichStyled(__strElapsed, LOG_COLOR_SUCCESS); \
    } while (0)
class CAboutDlg : public CDialogEx
{
public:
@@ -72,7 +85,7 @@
    , m_dOutValues{ 0.0, 0.0, 0.0, 0.0 }
    , m_nUseTrigger(0)
    , m_nSavePointCount(100000)
    , m_fJumpThreshold(1.0f)
    , m_fJumpThreshold(0.2f)
    , m_nJumpWindow(3)
    , m_nValleyMargin(0)
    , m_nMinGlass1Count(10)
@@ -232,7 +245,7 @@
    strFullLogLine.Format(_T("%s %s"), strLevel, strContent);
    // 写入日志文件
    LOG_LINE(strFullLogLine);
    // LOG_LINE(strFullLogLine);
}
void CSGMeasurementDlg::HighlightAllMatches(const CString& strSearch, COLORREF clrHighlight/* = RGB(255, 165, 0)*/)
@@ -337,32 +350,38 @@
        return false;
    }
    RC nRet = SGIF_CloseDevice(DeviceID);
    if (nRet == RC_OK) {
        if (m_bSaving) {
            m_bSaving = FALSE;
            nRet = SGIF_DataStorageStop(DeviceID);
            if (nRet == RC_OK) {
                AppendLogLineRichStyled(_T("数据存储已停止。"), LOG_COLOR_SUCCESS);
            }
            else {
                CString strError;
                strError.Format(_T("停止数据存储失败,错误码:%#X"), nRet);
                AppendLogLineRichStyled(strError, LOG_COLOR_ERROR);
            }
        }
    // 停止定时器,避免在断开后仍尝试更新输出数据
    KillTimer(TIMER_ID_OUTPUT_UPDATE);
    // 先停止数据存储(如果正在进行)
    RC nRet;
    if (m_bConnected && m_bSaving) {
        nRet = SGIF_DataStorageStop(DeviceID);
        if (nRet == RC_OK) {
            m_bSaving = FALSE;
            AppendLogLineRichStyled(_T("数据存储已停止。"), LOG_COLOR_SUCCESS);
        }
        else {
            CString strError;
            strError.Format(_T("停止数据存储失败,错误码:%#X"), nRet);
            AppendLogLineRichStyled(strError, LOG_COLOR_ERROR);
        }
    }
    // 断开设备连接
    nRet = SGIF_CloseDevice(DeviceID);
    if (nRet == RC_OK) {
        AppendLogLineRichStyled(_T("断开连接成功!"), LOG_COLOR_SUCCESS);
        m_bConnected = FALSE;
        UpdateControlStatus(FALSE, FALSE);
        KillTimer(TIMER_ID_OUTPUT_UPDATE);
        return true;
    }
    else {
        CString strError;
        strError.Format(_T("断开连接失败,错误码:%#X"), nRet);
        AppendLogLineRichStyled(strError, LOG_COLOR_ERROR);
        AppendLogLineRichStyled(_T("保持当前状态,断开失败。"), LOG_COLOR_ERROR);
        UpdateControlStatus(m_bConnected, m_bSaving);
        return false;
@@ -371,6 +390,8 @@
void CSGMeasurementDlg::CleanInvalidValuesInPlace(int nOutNo, std::vector<float>& vecData, float fInvalid/* = -999.0f*/)
{
    MEASURE_FUNC_START();
    // 找到第一个有效值
    auto itStart = std::find_if(vecData.begin(), vecData.end(), [=](float v) { return v > fInvalid; });
@@ -406,62 +427,87 @@
    else {
        vecData.clear();
    }
    MEASURE_FUNC_END();
}
bool CSGMeasurementDlg::SplitGlassSegments(int nOutNo, const std::vector<float>& vecData, std::vector<float>& vecGlass1, std::vector<float>& vecGlass2, float fJumpThreshold /*= 0.2f*/, int nWindow /*= 3*/, int nValleyMargin /*= 0*/, int nMinGlass1Count /*= 10*/)
bool CSGMeasurementDlg::SplitGlassSegments(int nOutNo, const std::vector<float>& vecData, std::vector<float>& vecGlass1, std::vector<float>& vecGlass2, float fJumpThreshold /*= 0.2f*/, int nWindow /*= 3*/, int nValleyMargin /*= 0*/, int nMinGlassCount /*= 10*/)
{
    const int n = static_cast<int>(vecData.size());
    if (n < 2 * nWindow + 1 + nMinGlass1Count) {
        CString strError;
        strError.Format(_T("OUT%d: 数据量不足,至少需要 %d 个点才能进行切割。"), nOutNo, 2 * nWindow + 1 + nMinGlass1Count);
        AppendLogLineRichStyled(strError, LOG_COLOR_WARNING);
    MEASURE_FUNC_START();
    CString strLog;
    const int nTotal = static_cast<int>(vecData.size());
    if (nTotal < 2 * nWindow + 1 + nMinGlassCount) {
        strLog.Format(_T("OUT%d: 数据量不足,至少需要 %d 个点才能切割。"), nOutNo, 2 * nWindow + 1 + nMinGlassCount);
        AppendLogLineRichStyled(strLog, LOG_COLOR_WARNING);
        return false;
    }
    int nValleyIdx = -1;
    for (int i = nWindow; i < n - nWindow - nMinGlass1Count; ++i) {
        float fDelta = std::fabs(vecData[i + nWindow] - vecData[i - nWindow]);
        if (fDelta > fJumpThreshold) {
            // 找 valley 点(稍微往后跳几步以避开边缘抖动)
            int nSearchStart = i + nWindow + 2;
            int nSearchEnd = min(nSearchStart + 10, n);
    // 从中间向两边查找 valley 候选点
    int nMid = nTotal / 2;
    int nValleyLeft = -1, nValleyRight = -1;
            auto itValley = std::min_element(vecData.begin() + nSearchStart, vecData.begin() + nSearchEnd,
                [](float a, float b) {
                    return (a > -900.0f && b > -900.0f) ? a < b : false;
                });
            if (itValley != vecData.begin() + nSearchEnd) {
                nValleyIdx = static_cast<int>(std::distance(vecData.begin(), itValley));
                break;
            }
    // 向左查找 valley
    for (int i = nMid; i >= nWindow; --i) {
        float fDelta = std::fabs(vecData[i] - vecData[i - nWindow]);
        if (fDelta > fJumpThreshold &&
            vecData[i] < vecData[i - 1] &&
            vecData[i] < vecData[i + 1]) {
            nValleyLeft = i;
            break;
        }
    }
    if (nValleyIdx < 0 || nValleyIdx < nMinGlass1Count) {
        AppendLogLineRichStyled(_T("未找到合适 valley 点,或 valley 太靠前。"), LOG_COLOR_WARNING);
        return false;
    // 向右查找 valley
    for (int i = nMid; i <= nTotal - nWindow - 2; ++i) {
        float fDelta = std::fabs(vecData[i + nWindow] - vecData[i]);
        if (fDelta > fJumpThreshold &&
            vecData[i] < vecData[i - 1] &&
            vecData[i] < vecData[i + 1]) {
            nValleyRight = i;
            break;
        }
    }
    // 从 valley 处开始切割,或者往后偏移
    int nCutStart = min(nValleyIdx + nValleyMargin, n);
    vecGlass1.assign(vecData.begin(), vecData.begin() + nCutStart);
    vecGlass2.assign(vecData.begin() + nCutStart, vecData.end());
    // 选定 valley 点
    int nValleyIdx = -1;
    if (nValleyLeft > 0 && nValleyRight > 0) {
        nValleyIdx = (vecData[nValleyLeft] < vecData[nValleyRight]) ? nValleyLeft : nValleyRight;
    }
    else if (nValleyLeft > 0) {
        nValleyIdx = nValleyLeft;
    }
    else if (nValleyRight > 0) {
        nValleyIdx = nValleyRight;
    }
    // fallback: valley 未找到,使用中间切割法
    if (nValleyIdx < 0) {
        AppendLogLineRichStyled(_T("未找到 valley 跳变点,使用中间位置切割。"), LOG_COLOR_WARNING);
        nValleyIdx = nMid;
    }
    // 应用切割位置,限制边界
    int nCutPos = max(1, min(nTotal - 1, nValleyIdx + nValleyMargin));
    vecGlass1.assign(vecData.begin(), vecData.begin() + nCutPos);
    vecGlass2.assign(vecData.begin() + nCutPos, vecData.end());
    int nGlass1Count = static_cast<int>(vecGlass1.size());
    int nGlass2Count = static_cast<int>(vecGlass2.size());
    // 日志输出
    CString strLog;
    strLog.Format(_T("OUT%d: 切割成功,第一片玻璃数据量 %d,第二片玻璃数据量 %d。"), nOutNo, nGlass1Count, nGlass2Count);
    strLog.Format(_T("OUT%d: 切割成功,第一片玻璃 %d 点,第二片玻璃 %d 点。"), nOutNo, nGlass1Count, nGlass2Count);
    AppendLogLineRichStyled(strLog, LOG_COLOR_SUCCESS);
    if (nGlass1Count < nMinGlass1Count) {
        strLog.Format(_T("OUT%d: 第一片玻璃数据量过少,可能影响后续处理。"), nOutNo);
    if (nGlass1Count < nMinGlassCount) {
        strLog.Format(_T("OUT%d: 第一片玻璃数据量少于 %d 点,可能影响计算。"), nOutNo, nMinGlassCount);
        AppendLogLineRichStyled(strLog, LOG_COLOR_WARNING);
    }
    if (nGlass2Count < nMinGlass1Count) {
        strLog.Format(_T("OUT%d: 第二片玻璃数据量过少,可能影响后续处理。"), nOutNo);
    if (nGlass2Count < nMinGlassCount) {
        strLog.Format(_T("OUT%d: 第二片玻璃数据量少于 %d 点,可能影响计算。"), nOutNo, nMinGlassCount);
        AppendLogLineRichStyled(strLog, LOG_COLOR_WARNING);
    }
@@ -471,11 +517,61 @@
    AppendLogLineRichStyled(_T("第二片玻璃数据:"), LOG_COLOR_NORMAL);
    PrintSampleData(nOutNo, vecGlass2);
    return !vecGlass1.empty() && !vecGlass2.empty();
    MEASURE_FUNC_END();
    return true;
}
bool CSGMeasurementDlg::FilterDominantGroup(int nOutNo, const std::vector<float>& vecInput, std::vector<float>& vecOutput)
{
    MEASURE_FUNC_START();
    if (vecInput.empty()) {
        AppendLogLineRichStyled(_T("输入数据为空,无法进行分组筛选。"), LOG_COLOR_WARNING);
        return false;
    }
    // 分组:map<int整数部分, vector<float>>
    std::map<int, std::vector<float>> mapGroup;
    for (float fVal : vecInput) {
        int nKey = static_cast<int>(fVal);
        mapGroup[nKey].push_back(fVal);
    }
    // 找出数量最多的那组
    size_t nMaxCount = 0;
    auto itMaxGroup = mapGroup.begin();
    for (auto it = mapGroup.begin(); it != mapGroup.end(); ++it) {
        if (it->second.size() > nMaxCount) {
            nMaxCount = it->second.size();
            itMaxGroup = it;
        }
    }
    if (nMaxCount == 0) {
        AppendLogLineRichStyled(_T("所有数据都被过滤或为空,无法筛选出主要分组。"), LOG_COLOR_WARNING);
        return false;
    }
    vecOutput = itMaxGroup->second;
    CString strLog;
    strLog.Format(_T("成功从 %d 个组中筛选出主要分组:值约为 %d,共 %zu 个点。"), static_cast<int>(mapGroup.size()), itMaxGroup->first, nMaxCount);
    AppendLogLineRichStyled(strLog, LOG_COLOR_SUCCESS);
    // 打印筛选后的数据
    AppendLogLineRichStyled(_T("筛选后的主要分组数据:"), LOG_COLOR_NORMAL);
    PrintSampleData(nOutNo, vecOutput);
    MEASURE_FUNC_END();
    return true;
}
bool CSGMeasurementDlg::ExtractStableRegionFixed(int nOutNo, const std::vector<float>& vecIn, std::vector<float>& vecOut, int nFixedCount/* = 5*/, float fMaxDelta/* = 0.04f*/)
{
    MEASURE_FUNC_START();
    const int n = static_cast<int>(vecIn.size());
    if (n < nFixedCount) {
        CString strError;
@@ -520,11 +616,15 @@
    AppendLogLineRichStyled(_T("提取的稳定区数据:"), LOG_COLOR_NORMAL);
    PrintSampleData(nOutNo, vecOut);
    MEASURE_FUNC_END();
    return true;
}
bool CSGMeasurementDlg::CalcGlassOffset(const std::vector<float>& vecGlass1, const std::vector<float>& vecGlass2, float& fAvg1, float& fAvg2, float& fOffset)
{
    MEASURE_FUNC_START();
    if (vecGlass1.empty() || vecGlass2.empty()) {
        AppendLogLineRichStyled(_T("稳定区数据为空,无法计算平均值和偏移。"), LOG_COLOR_WARNING);
        return false;
@@ -542,6 +642,8 @@
    CString strLog;
    strLog.Format(_T("第一片玻璃平均值: %.3f,第二片玻璃平均值: %.3f,偏移量: %.3f"), fAvg1, fAvg2, fOffset);
    AppendLogLineRichStyled(strLog, LOG_COLOR_SUCCESS);
    MEASURE_FUNC_END();
    return true;
}
@@ -612,6 +714,8 @@
float CSGMeasurementDlg::AnalyzeStoredData(int nOutNo)
{
    MEASURE_FUNC_START();
    UpdateData(TRUE);
    if (m_nUseTrigger) {
@@ -640,8 +744,6 @@
        return -1.0f;
    }
    clock_t startClock = clock();  // 记录开始时间
    std::vector<float> vecBuffer(m_nSavePointCount, 0.0f);
    int nReceived = 0;
@@ -662,9 +764,29 @@
        return -1.0f;
    }
    std::vector<float> vecGlass1Filtered, vecGlass2Filtered;
    bool bGlass1Filtered = FilterDominantGroup(nOutNo, vecGlass1, vecGlass1Filtered);
    bool bGlass2Filtered = FilterDominantGroup(nOutNo, vecGlass2, vecGlass2Filtered);
    if (!bGlass1Filtered) {
        AppendLogLineRichStyled(_T("Glass1 分段中未能识别出主数据组,使用原始数据。"), LOG_COLOR_WARNING);
        vecGlass1Filtered = vecGlass1;
    }
    else {
        AppendLogLineRichStyled(_T("Glass1 主数据组已提取。"), LOG_COLOR_SUCCESS);
    }
    if (!bGlass2Filtered) {
        AppendLogLineRichStyled(_T("Glass2 分段中未能识别出主数据组,使用原始数据。"), LOG_COLOR_WARNING);
        vecGlass2Filtered = vecGlass2;
    }
    else {
        AppendLogLineRichStyled(_T("Glass2 主数据组已提取。"), LOG_COLOR_SUCCESS);
    }
    std::vector<float> vecStable1, vecStable2;
    bool bStable1 = ExtractStableRegionFixed(nOutNo, vecGlass1, vecStable1, m_nFixedCount, m_fMaxDelta);
    bool bStable2 = ExtractStableRegionFixed(nOutNo, vecGlass2, vecStable2, m_nFixedCount, m_fMaxDelta);
    bool bStable1 = ExtractStableRegionFixed(nOutNo, vecGlass1Filtered, vecStable1, m_nFixedCount, m_fMaxDelta);
    bool bStable2 = ExtractStableRegionFixed(nOutNo, vecGlass2Filtered, vecStable2, m_nFixedCount, m_fMaxDelta);
    float fAvg1 = 0.0f, fAvg2 = 0.0f, fOffset = 0.0f;
    if (bStable1 && bStable2) {
@@ -676,12 +798,7 @@
        CalcGlassOffset(vecGlass1, vecGlass2, fAvg1, fAvg2, fOffset);
    }
    clock_t endClock = clock();  // 记录结束时间
    double dElapsedMs = 1000.0 * (endClock - startClock) / CLOCKS_PER_SEC;
    CString strElapsed;
    strElapsed.Format(_T("AnalyzeStoredData 执行耗时:%.1f ms"), dElapsedMs);
    AppendLogLineRichStyled(strElapsed, LOG_COLOR_SUCCESS);
    MEASURE_FUNC_END();
    return fOffset;
}
@@ -829,6 +946,11 @@
            CString strError;
            strError.Format(_T("获取测量值失败,错误码:%#X"), nRet);
            AppendLogLineRichStyled(strError, LOG_COLOR_ERROR);
            // 断开连接
            if (m_bConnected) {
                DisconnectFromDevice();
            }
        }
    }
SourceCode/Bond/SGMeasurement/SGMeasurementDlg.h
@@ -3,6 +3,7 @@
//
#pragma once
#include <map>
#include <vector>
#include <numeric>
#include <algorithm>
@@ -145,14 +146,25 @@
     * @param fJumpThreshold 跳变阈值,用于检测数据的明显断点。
     * @param nWindow 跳变判断的前后窗口宽度。
     * @param nValleyMargin valley 点之后多少个点作为实际切割点。
     * @param nMinGlass1Count 第一段最少有效点数限制。
     * @param nMinGlass1Count 最少有效点数限制。
     *
     * @return true 表示切割成功,false 表示失败(例如数据不足或无明显跳变)。
     */
    bool SplitGlassSegments(int nOutNo, const std::vector<float>& validData,
        std::vector<float>& vecGlass1, std::vector<float>& vecGlass2,
        float fJumpThreshold = 1.0f, int nWindow = 3, int nValleyMargin = 0,
        int nMinGlass1Count = 10);
        float fJumpThreshold = 0.2f, int nWindow = 3, int nValleyMargin = 0,
        int nMinGlassCount = 10);
    /**
     * @brief 对数据按整数部分进行分组,保留数据量最多的一组(排除异常/干扰)。
     *
     * @param nOutNo 输出通道编号,用于日志记录。
     * @param vecInput 原始浮点数据(已裁剪无效值)。
     * @param vecOutput 输出被保留的主要分组数据(最多的那组)。
     *
     * @return true 表示成功过滤出主分组,false 表示所有数据都被过滤或为空。
     */
    bool FilterDominantGroup(int nOutNo, const std::vector<float>& vecInput, std::vector<float>& vecOutput);
    /**
     * @brief 从输入数据中提取一个固定长度的稳定区间。