EnergySpectrumAnalyer/src/DataCalcProcess/FindPeaksBySvd.cpp
2026-04-20 18:02:41 +08:00

141 lines
5.7 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "FindPeaksBySvd.h"
using namespace arma;
std::vector<FindPeaksBySvd::PeakInfo> FindPeaksBySvd::FindPeaks(const arma::mat &spec_data, int step_w) throw(std::string)
{
std::vector<FindPeaksBySvd::PeakInfo> peak_info_vec;
try {
const arma::vec& spec_data_col0 = spec_data.col(0);
const arma::vec& spec_data_col1 = spec_data.col(1);
int m = spec_data_col1.n_elem; // 能谱长度
int n = (step_w == -1) ? 7 : step_w; // 窗口大小默认7
int ns = ceil((n - 1) / 2.0); // 窗口半宽
// 初始化变量
vec kosin(m, fill::zeros);
vec Smax(m, fill::zeros);
vec Smin(m, fill::zeros);
int k = 0; // 索引计数器对应Matlab的k
// 滑动窗口遍历+SVD分析
for (int i = 0; i < m; i++) { // Armadillo默认0索引Matlab是1索引需对应转换
k++;
vec vec2; // 局部窗口数据
// 边界处理:左边界
if (k <= ns) {
vec2 = spec_data_col1.subvec(0, n - 1);
}
// 右边界
else if (k >= m - ns) { // Matlab: k>=m-ns-1因索引差异调整
vec2 = spec_data_col1.subvec(m - n, m - 1);
}
// 中间区域以i为中心的n点窗口
else {
int start = i - floor((n - 1) / 2.0);
int end = i + ceil((n - 1) / 2.0);
vec2 = spec_data_col1.subvec(start, end);
}
// 构造矩阵M2列基线参考+局部数据)
vec vec1(n, fill::ones);
double min_val = vec2.min();
mat M(n, 2);
M.col(0) = min_val * vec1;
M.col(1) = vec2;
// SVD分解Armadillo svd返回U,S,V^T与Matlab一致
mat U, V;
vec S;
svd(U, S, V, M); // S为奇异值向量降序排列
// 存储最大/最小奇异值
Smax(i) = S(0);
Smin(i) = S(1);
// 计算余弦相似度kosin
double mean_vec2 = mean(vec2);
vec mean_vec1 = mean_vec2 * vec1;
double dot_prod = dot(vec2, mean_vec1);
double norm_vec2 = norm(vec2);
double norm_mean1 = norm(mean_vec1);
kosin(i) = dot_prod / (norm_vec2 * norm_mean1 + 1e-8); // 加小值避免除零
}
// 计算峰识别指标DistV
vec sin_kosin = sqrt(1 - pow(kosin, 2));
vec DistV1 = sin_kosin % Smax;
vec DistV2 = sin_kosin % Smin;
double mean_DistV2 = mean(DistV2);
vec DistV = DistV2 - 3 * sqrt(mean_DistV2);
// 峰边界识别与参数提取
int Lc = 0, Rc = 1;
int nos = DistV.n_elem;
for (int ch = 0; ch < nos - 1; ch++) { // 遍历
if (DistV(ch) < 0 && DistV(ch + 1) >= 0) {
// 左边界DistV从负→正
Lc = ch;
} else if (DistV(ch) >= 0 && DistV(ch + 1) <= 0) {
// 右边界DistV从正→负
Rc = ch;
} else {
continue;
}
// 计算峰参数
if (Rc > Lc) {
int wop = abs(Rc - Lc);
if (wop <= 0)
continue; // 无效宽度跳过
// 提取峰区域数据
vec peak_region = spec_data_col1.subvec(Lc, Rc);
double height = peak_region.max();
uword HWP_idx = peak_region.index_max(); // 峰值位置
// 计算半高宽(FWHM)
double half_height = height / 2.0;
int left_half = 0, right_half = peak_region.n_elem - 1;
// 找到左侧半高位置
for (int i = HWP_idx; i >= 0; i--) {
if (peak_region(i) <= half_height) {
left_half = i;
break;
}
}
// 找到右侧半高位置
for (int i = HWP_idx; i < peak_region.n_elem; i++) {
if (peak_region(i) <= half_height) {
right_half = i;
break;
}
}
// 计算半高宽(转换为原始坐标)
double fwhm = (right_half - left_half + 1);
// 计算峰面积(使用梯形法则)
double area = 0.0;
for (int i = 0; i < peak_region.n_elem - 1; i++) {
// 梯形面积 = (上底 + 下底) * 高 / 2
area += (peak_region(i) + peak_region(i + 1)) / 2.0;
}
// 峰中心
int pl = round((Lc + Rc) / 2.0);
// 筛选有效峰:峰宽>2且中心DistV>0
// 修改20251126: 去除峰宽>2及“wop > 2”的判断条件
if (DistV(pl - 1) > 0) { // pl-1转换为0索引
int left = Lc;
int width = wop + 1;
int pos = Lc + HWP_idx;
PeakInfo peak_info;
peak_info.left = spec_data_col0.at(left);
peak_info.width = spec_data_col0.at(width);
peak_info.height = height;
peak_info.pos = spec_data_col0.at(pos);
peak_info.fwhm = fwhm;
peak_info.area = area;
peak_info_vec.push_back(peak_info);
}
}
}
}
catch(const std::exception& e) {
throw std::string("Find Peaks Exception, ") + std::string(e.what());
}
catch (...) {
throw std::string("Find peaks unkonow exception");
}
return peak_info_vec;
}