139 lines
4.5 KiB
C++
139 lines
4.5 KiB
C++
|
|
#include "Poly2FindPeaks.h"
|
|||
|
|
|
|||
|
|
using namespace std;
|
|||
|
|
|
|||
|
|
vector<double> Poly2FindPeaks::SmoothData(const vector<double>& data, int window_size)
|
|||
|
|
{
|
|||
|
|
if (window_size % 2 == 0)
|
|||
|
|
// 确保窗口大小为奇数
|
|||
|
|
window_size++;
|
|||
|
|
|
|||
|
|
int n = data.size();
|
|||
|
|
int halfWin = window_size / 2;
|
|||
|
|
vector<double> smoothed(n, 0.0);
|
|||
|
|
|
|||
|
|
for (int i = 0; i < n; ++i) {
|
|||
|
|
int start = max(0, i - halfWin);
|
|||
|
|
int end = min(n - 1, i + halfWin);
|
|||
|
|
int count = end - start + 1;
|
|||
|
|
double sum = 0.0;
|
|||
|
|
for (int j = start; j <= end; ++j) {
|
|||
|
|
sum += data[j];
|
|||
|
|
}
|
|||
|
|
smoothed[i] = sum / count;
|
|||
|
|
}
|
|||
|
|
return smoothed;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 计算基线(使用百分位法,假设大部分数据为基线)
|
|||
|
|
double Poly2FindPeaks::calculateBaseline(const vector<double>& data, double percentile)
|
|||
|
|
{
|
|||
|
|
vector<double> sorted_data = data;
|
|||
|
|
sort(sorted_data.begin(), sorted_data.end());
|
|||
|
|
int idx = static_cast<int>(sorted_data.size() * percentile);
|
|||
|
|
return sorted_data[idx];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
vector<Poly2FindPeaks::Peak> Poly2FindPeaks::findPeaks(const vector<double>& data, double height_threshold, int min_distance, int smooth_window)
|
|||
|
|
{
|
|||
|
|
vector<Peak> peaks;
|
|||
|
|
int n = data.size();
|
|||
|
|
if (n < 3)
|
|||
|
|
// 数据点太少,无法检测峰值
|
|||
|
|
return peaks;
|
|||
|
|
|
|||
|
|
// 1. 预处理:平滑数据
|
|||
|
|
vector<double> smoothed = SmoothData(data, smooth_window);
|
|||
|
|
// 2. 计算基线
|
|||
|
|
double baseline = calculateBaseline(smoothed);
|
|||
|
|
// 3. 初步检测局部最大值(峰值候选)
|
|||
|
|
vector<int> candidates;
|
|||
|
|
for (int i = 1; i < n - 1; ++i) {
|
|||
|
|
// 判断是否为局部最大值
|
|||
|
|
if (smoothed[i] > smoothed[i - 1] && smoothed[i] > smoothed[i + 1]) {
|
|||
|
|
// 高度超过阈值(相对于基线)
|
|||
|
|
double height = smoothed[i] - baseline;
|
|||
|
|
if (height > height_threshold) {
|
|||
|
|
candidates.push_back(i);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
// 4. 过滤近距离峰(保留较高的峰)
|
|||
|
|
vector<int> filtered;
|
|||
|
|
for (int cand : candidates) {
|
|||
|
|
bool keep = true;
|
|||
|
|
// 检查与已保留峰的距离
|
|||
|
|
for (int peakIdx : filtered) {
|
|||
|
|
if (abs(cand - peakIdx) < min_distance) {
|
|||
|
|
// 保留高度更高的峰
|
|||
|
|
if (smoothed[cand] <= smoothed[peakIdx]) {
|
|||
|
|
keep = false;
|
|||
|
|
break;
|
|||
|
|
} else {
|
|||
|
|
// 移除较低的峰
|
|||
|
|
auto it = find(filtered.begin(), filtered.end(), peakIdx);
|
|||
|
|
if (it != filtered.end()) {
|
|||
|
|
filtered.erase(it);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if (keep) {
|
|||
|
|
filtered.push_back(cand);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
// 5. 提取峰值特征
|
|||
|
|
for (int idx : filtered) {
|
|||
|
|
Peak peak;
|
|||
|
|
peak.index = idx;
|
|||
|
|
peak.amplitude = smoothed[idx];
|
|||
|
|
peak.height = peak.amplitude - baseline;
|
|||
|
|
// 计算精确位置(抛物线插值)
|
|||
|
|
if (idx > 0 && idx < n - 1) {
|
|||
|
|
double y0 = smoothed[idx - 1];
|
|||
|
|
double y1 = smoothed[idx];
|
|||
|
|
double y2 = smoothed[idx + 1];
|
|||
|
|
// 抛物线拟合:y = ax² + bx + c,顶点在 x = -b/(2a)
|
|||
|
|
double a = (y0 + y2 - 2 * y1) / 2;
|
|||
|
|
double b = (y2 - y0) / 2;
|
|||
|
|
peak.position = idx - b / (2 * a); // 精确位置(可能非整数)
|
|||
|
|
} else {
|
|||
|
|
peak.position = idx; // 边缘点无法插值,直接用索引
|
|||
|
|
}
|
|||
|
|
// 计算半高宽FWHM
|
|||
|
|
double halfHeight = baseline + peak.height / 2;
|
|||
|
|
int left = idx, right = idx;
|
|||
|
|
// 向左找半高处
|
|||
|
|
while (left > 0 && smoothed[left] > halfHeight) {
|
|||
|
|
left--;
|
|||
|
|
}
|
|||
|
|
// 向右找半高处
|
|||
|
|
while (right < n - 1 && smoothed[right] > halfHeight) {
|
|||
|
|
right++;
|
|||
|
|
}
|
|||
|
|
// 线性插值计算半高处的精确位置
|
|||
|
|
double leftPos = left;
|
|||
|
|
if (left < idx) {
|
|||
|
|
double dy = smoothed[left + 1] - smoothed[left];
|
|||
|
|
if (dy != 0) {
|
|||
|
|
leftPos += (halfHeight - smoothed[left]) / dy;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
double rightPos = right;
|
|||
|
|
if (right > idx) {
|
|||
|
|
double dy = smoothed[right] - smoothed[right - 1];
|
|||
|
|
if (dy != 0) {
|
|||
|
|
rightPos = right - 1 + (halfHeight - smoothed[right - 1]) / dy;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
peak.fwhm = rightPos - leftPos;
|
|||
|
|
peaks.push_back(peak);
|
|||
|
|
}
|
|||
|
|
// 按位置排序
|
|||
|
|
sort(peaks.begin(), peaks.end(), [](const Peak& a, const Peak& b) {
|
|||
|
|
return a.position < b.position;
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
return peaks;
|
|||
|
|
}
|