EnergySpectrumAnalyer/src/DataCalcProcess/FindPeaksBySvd.cpp

141 lines
5.7 KiB
C++
Raw Normal View History

2026-03-17 10:50:33 +08:00
#include "FindPeaksBySvd.h"
using namespace arma;
std::vector<FindPeaksBySvd::PeakInfo> FindPeaksBySvd::FindPeaks(const arma::mat &spec_data, int step_w) throw(std::string)
2026-03-17 10:50:33 +08:00
{
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;
}
2026-03-17 10:50:33 +08:00
// 峰中心
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;
2026-03-17 10:50:33 +08:00
peak_info_vec.push_back(peak_info);
}
}
}
}
catch(const std::exception& e) {
throw std::string("Find Peaks Exception, ") + std::string(e.what());
2026-03-17 10:50:33 +08:00
}
catch (...) {
throw std::string("Find peaks unkonow exception");
}
return peak_info_vec;
}