修改峰拟合分析

This commit is contained in:
anxinglong 2026-05-13 18:19:04 +08:00
parent 0e7981024a
commit 9b383103be
2 changed files with 161 additions and 166 deletions

View File

@ -181,6 +181,7 @@ void EnergyCountPeakFitView::setupPlot()
_plot->setAxisAutoScale(QwtPlot::xBottom, true);
_plot->setAxisAutoScale(QwtPlot::yLeft, true);
_plot->enableAxis(QwtPlot::xBottom);
_plot->enableAxis(QwtPlot::yLeft);
@ -483,45 +484,51 @@ void EnergyCountPeakFitView::finishSelection()
}
}
if (roiX.size() >= 3) {
if (roiX.size() >= 3)
{
QStringList algorithms;
algorithms << QStringLiteral(u"y=A*exp(-pow(x-P,2)/(2*pow(delt,2))) + H/(1+exp((x-P)/W)) + C");
bool ok = false;
QString selected = QInputDialog::getItem(this,
QStringLiteral(u"选择拟合算法"),
QStringLiteral(u"请选择用于当前框选区域的峰拟合算法:"),
algorithms,
0,
false,
&ok);
if (ok && selected == QStringLiteral(u"y=A*exp(-pow(x-P,2)/(2*pow(delt,2))) + H/(1+exp((x-P)/W)) + C"))
{
arma::vec armaRoiX(roiX.size()), armaRoiY(roiY.size());
// 关键修改实例化QInputDialog并设置窗口标志去除问号按钮
QInputDialog* algorithmDlg = new QInputDialog(this, Qt::Dialog | Qt::WindowCloseButtonHint);
algorithmDlg->setWindowTitle(QStringLiteral(u"选择拟合算法"));
algorithmDlg->setLabelText(QStringLiteral(u"请选择用于当前框选区域的峰拟合算法:"));
algorithmDlg->setComboBoxItems(algorithms);
algorithmDlg->setComboBoxEditable(false);
algorithmDlg->setModal(false); // 保持非模态
algorithmDlg->setAttribute(Qt::WA_DeleteOnClose); // 关闭自动释放内存
// 异步处理用户选择原if(ok)逻辑移到这里)
connect(algorithmDlg, &QInputDialog::accepted, this, [=]() {
QString selected = algorithmDlg->textValue();
if (selected == algorithms.first())
{
arma::vec armaRoiX(roiX.size()), armaRoiY(roiY.size());
for (int i = 0; i < roiX.size(); ++i) {
armaRoiX(i) = roiX[i];
armaRoiY(i) = roiY[i];
}
// 3. 执行拟合(曲线自动添加到主图)
QList<PeakFitResult> results = performPeakFitting(roiX, roiY/*, &userP0*/);
if (results.isEmpty()) {
qDebug()<< QStringLiteral(u"拟合结果:") << QStringLiteral(u"未检测到有效峰或拟合失败。");
} else {
addSelectionRect(plotRect);
fadeSelectionRectBorders(); // 框选边框变为虚线(与原逻辑一致)
if (!_selectionRectItems.isEmpty() && !results.isEmpty())
{
PlotRectItem* lastRect = _selectionRectItems.last();
const PeakFitResult& r = results.first();
// 传入峰位、FWHM、面积、本底
lastRect->setPeakData(r.center, r.fwhm, r.area, r.baseline);
_plot->replot(); // 刷新显示
}
saveCurrentFitToHistory();
// 执行拟合(曲线自动添加到主图)
QList<PeakFitResult> results = performPeakFitting(roiX, roiY);
if (results.isEmpty()) {
qDebug()<< QStringLiteral(u"拟合结果:") << QStringLiteral(u"未检测到有效峰或拟合失败。");
} else {
addSelectionRect(plotRect);
fadeSelectionRectBorders(); // 框选边框变为虚线(与原逻辑一致)
if (!_selectionRectItems.isEmpty() && !results.isEmpty())
{
PlotRectItem* lastRect = _selectionRectItems.last();
const PeakFitResult& r = results.first();
// 传入峰位、FWHM、面积、本底
lastRect->setPeakData(r.center, r.fwhm, r.area, r.baseline);
_plot->replot(); // 刷新显示
}
saveCurrentFitToHistory();
}
}
}
} else {
qDebug() << QStringLiteral(u"框选区域内数据点不足3个无法拟合。");
});
algorithmDlg->show(); // 非模态显示
}
}
}
@ -872,34 +879,41 @@ void EnergyCountPeakFitView::onActionShowFitHistory()
}
}
if (!hasCurveData) {
QMessageBox::information(this,
QStringLiteral(u"峰拟合结果"),
QStringLiteral(u"暂无历史拟合数据,请先进行拟合并保存。"));
QMessageBox* msgBox = new QMessageBox(this);
msgBox->setWindowTitle(QStringLiteral(u"峰拟合结果"));
msgBox->setText(QStringLiteral(u"暂无历史拟合数据,请先进行拟合并保存。"));
msgBox->setStandardButtons(QMessageBox::Ok);
msgBox->setModal(false);
msgBox->setAttribute(Qt::WA_DeleteOnClose);
msgBox->setWindowFlags(Qt::Dialog | Qt::WindowCloseButtonHint);
msgBox->show();
return;
}
QDialog dlg(this);
dlg.setWindowTitle(QStringLiteral(u"峰拟合结果历史"));
dlg.setMinimumSize(1000, 500);
QVBoxLayout* layout = new QVBoxLayout(&dlg);
// 替换栈变量为堆上非模态对话框
QDialog* historyDlg = new QDialog(this , Qt::Dialog | Qt::WindowCloseButtonHint);
historyDlg->setWindowTitle(QStringLiteral(u"峰拟合结果"));
historyDlg->setMinimumSize(1000, 500);
historyDlg->setModal(false);
historyDlg->setAttribute(Qt::WA_DeleteOnClose);
QStandardItemModel* model = new QStandardItemModel(&dlg);
QVBoxLayout* layout = new QVBoxLayout(historyDlg);
QStandardItemModel* model = new QStandardItemModel(historyDlg);
QStringList headers;
headers << "" << "振幅(A)" << "高斯宽度(delt)" << "Sigmoid幅度(H)" << "Sigmoid宽度(W)" << "基线(C)" << "峰中心(P)";
model->setHorizontalHeaderLabels(headers);
QVector<QPair<int, int>> rowToDataIndices;
model->blockSignals(true);
for (int historyIdx = 0; historyIdx < _fitHistoryList.size(); ++historyIdx) {
const auto& historyItem = _fitHistoryList[historyIdx];
for (int curveIdx = 0; curveIdx < historyItem.curveList.size(); ++curveIdx) {
const auto& curve = historyItem.curveList[curveIdx];
QList<QStandardItem*> rowItems;
QStandardItem* checkItem = new QStandardItem();
checkItem->setCheckable(true);
checkItem->setEditable(false);
bool isCurrentlyDisplayed = false;
for (const auto& ref : _displayedHistoryCurves) {
if (ref.historyIndex == historyIdx && ref.curveIndex == curveIdx) {
@ -910,35 +924,12 @@ void EnergyCountPeakFitView::onActionShowFitHistory()
checkItem->setCheckState(isCurrentlyDisplayed ? Qt::Checked : Qt::Unchecked);
rowItems.append(checkItem);
// 2. 振幅 (A)
QStandardItem* itemA = new QStandardItem(QString::number(curve.amplitude, 'f', 4));
itemA->setEditable(false);
rowItems.append(itemA);
// 3. Sigma (delt)
QStandardItem* itemDelt = new QStandardItem(QString::number(curve.sigma, 'f', 4));
itemDelt->setEditable(false);
rowItems.append(itemDelt);
// 4. SigmoidH (H)
QStandardItem* itemH = new QStandardItem(QString::number(curve.sigmoidH, 'f', 4));
itemH->setEditable(false);
rowItems.append(itemH);
// 5. SigmoidW (W)
QStandardItem* itemW = new QStandardItem(QString::number(curve.sigmoidW, 'f', 4));
itemW->setEditable(false);
rowItems.append(itemW);
// 6. 本底 (C)
QStandardItem* itemC = new QStandardItem(QString::number(curve.baseline, 'f', 4));
itemC->setEditable(false);
rowItems.append(itemC);
// 7. 峰位 (P)
QStandardItem* itemP = new QStandardItem(QString::number(curve.center, 'f', 4));
itemP->setEditable(false);
rowItems.append(itemP);
rowItems.append(new QStandardItem(QString::number(curve.amplitude, 'f', 4)));
rowItems.append(new QStandardItem(QString::number(curve.sigma, 'f', 4)));
rowItems.append(new QStandardItem(QString::number(curve.sigmoidH, 'f', 4)));
rowItems.append(new QStandardItem(QString::number(curve.sigmoidW, 'f', 4)));
rowItems.append(new QStandardItem(QString::number(curve.baseline, 'f', 4)));
rowItems.append(new QStandardItem(QString::number(curve.center, 'f', 4)));
model->appendRow(rowItems);
rowToDataIndices.append(qMakePair(historyIdx, curveIdx));
@ -947,14 +938,14 @@ void EnergyCountPeakFitView::onActionShowFitHistory()
model->blockSignals(false);
QHBoxLayout* topBtnLayout = new QHBoxLayout();
QPushButton* btnSelectAll = new QPushButton(QStringLiteral(u"全选"), &dlg);
QPushButton* btnInvertSelection = new QPushButton(QStringLiteral(u"反选"), &dlg);
QPushButton* btnSelectAll = new QPushButton(QStringLiteral(u"全选"), historyDlg);
QPushButton* btnInvertSelection = new QPushButton(QStringLiteral(u"反选"), historyDlg);
topBtnLayout->addWidget(btnSelectAll);
topBtnLayout->addWidget(btnInvertSelection);
topBtnLayout->addStretch();
layout->addLayout(topBtnLayout);
QTableView* tableView = new QTableView(&dlg);
QTableView* tableView = new QTableView(historyDlg);
tableView->setModel(model);
tableView->setSelectionBehavior(QAbstractItemView::SelectRows);
tableView->setSelectionMode(QAbstractItemView::SingleSelection);
@ -964,16 +955,16 @@ void EnergyCountPeakFitView::onActionShowFitHistory()
tableView->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
layout->addWidget(tableView);
QPushButton* btnDelete = new QPushButton(QStringLiteral(u"删除选中记录"), &dlg);
QPushButton* btnClose = new QPushButton(QStringLiteral(u"关闭"), &dlg);
QPushButton* btnDelete = new QPushButton(QStringLiteral(u"删除选中记录"), historyDlg);
QPushButton* btnClose = new QPushButton(QStringLiteral(u"关闭"), historyDlg);
QHBoxLayout* bottomBtnLayout = new QHBoxLayout();
bottomBtnLayout->addStretch();
bottomBtnLayout->addWidget(btnDelete);
bottomBtnLayout->addWidget(btnClose);
layout->addLayout(bottomBtnLayout);
// --- 全选按钮 ---
connect(btnSelectAll, &QPushButton::clicked, [&]() {
// 全选按钮逻辑保持不变
connect(btnSelectAll, &QPushButton::clicked, [=]() {
model->blockSignals(true);
for (int row = 0; row < model->rowCount(); ++row) {
model->item(row, 0)->setCheckState(Qt::Checked);
@ -996,8 +987,8 @@ void EnergyCountPeakFitView::onActionShowFitHistory()
displaySelectedCurves(selectedCurves, selectedRefs);
});
// --- 反选按钮 ---
connect(btnInvertSelection, &QPushButton::clicked, [&]() {
// 反选按钮逻辑保持不变
connect(btnInvertSelection, &QPushButton::clicked, [=]() {
model->blockSignals(true);
for (int row = 0; row < model->rowCount(); ++row) {
QStandardItem* item = model->item(row, 0);
@ -1021,8 +1012,8 @@ void EnergyCountPeakFitView::onActionShowFitHistory()
displaySelectedCurves(selectedCurves, selectedRefs);
});
// --- 复选框状态改变 ---
connect(model, &QStandardItemModel::itemChanged, [&](QStandardItem* /*item*/) {
// 复选框状态改变逻辑保持不变
connect(model, &QStandardItemModel::itemChanged, [=](QStandardItem* /*item*/) {
QList<FitCurveData> selectedCurves;
QList<DisplayedCurveRef> selectedRefs;
for (int row = 0; row < model->rowCount(); ++row) {
@ -1038,9 +1029,8 @@ void EnergyCountPeakFitView::onActionShowFitHistory()
displaySelectedCurves(selectedCurves, selectedRefs);
});
// --- 删除选中记录按钮(核心精准删除逻辑) ---
connect(btnDelete, &QPushButton::clicked, [&]() {
// 1. 收集要删除的历史索引
// 删除按钮逻辑(改为非模态确认框)
connect(btnDelete, &QPushButton::clicked, [=]() {
QSet<int> historyIndicesToDelete;
for (int row = 0; row < model->rowCount(); ++row) {
if (model->item(row, 0)->checkState() == Qt::Checked) {
@ -1048,95 +1038,101 @@ void EnergyCountPeakFitView::onActionShowFitHistory()
historyIndicesToDelete.insert(indices.first);
}
}
if (historyIndicesToDelete.isEmpty()) {
QMessageBox::warning(&dlg, QStringLiteral(u"提示"), QStringLiteral(u"请先勾选要删除的记录。"));
QMessageBox* msgBox = new QMessageBox(historyDlg);
msgBox->setWindowTitle(QStringLiteral(u"提示"));
msgBox->setText(QStringLiteral(u"请先勾选要删除的记录。"));
msgBox->setStandardButtons(QMessageBox::Ok);
msgBox->setModal(false);
msgBox->setAttribute(Qt::WA_DeleteOnClose);
msgBox->show();
return;
}
// 2. 确认删除
QString msg = QStringLiteral(u"确定要删除选中的 %1 条历史记录吗?").arg(historyIndicesToDelete.size());
QMessageBox::StandardButton reply = QMessageBox::question(&dlg, QStringLiteral(u"确认删除"), msg, QMessageBox::Yes | QMessageBox::No);
if (reply != QMessageBox::Yes) return;
QMessageBox* confirmBox = new QMessageBox(historyDlg);
confirmBox->setWindowTitle(QStringLiteral(u"确认删除"));
confirmBox->setText(msg);
confirmBox->setStandardButtons(QMessageBox::Yes | QMessageBox::No);
confirmBox->setModal(false);
confirmBox->setAttribute(Qt::WA_DeleteOnClose);
// 3. 先从界面上精准删除显示(从后往前删)
QList<int> sortedDisplayIndices;
for (int i = 0; i < _displayedHistoryCurves.size(); ++i) {
if (historyIndicesToDelete.contains(_displayedHistoryCurves[i].historyIndex)) {
sortedDisplayIndices.append(i);
// 保存临时数据
confirmBox->setProperty("historyIndicesToDelete", QVariant::fromValue(historyIndicesToDelete));
confirmBox->setProperty("model", QVariant::fromValue(model));
confirmBox->setProperty("rowToDataIndices", QVariant::fromValue(rowToDataIndices));
connect(confirmBox, &QMessageBox::buttonClicked, this, [=](QAbstractButton* button) {
if (confirmBox->standardButton(button) == QMessageBox::Yes) {
QSet<int> historyIndicesToDelete = confirmBox->property("historyIndicesToDelete").value<QSet<int>>();
// 原有删除逻辑保持不变
QList<int> sortedDisplayIndices;
for (int i = 0; i < _displayedHistoryCurves.size(); ++i) {
if (historyIndicesToDelete.contains(_displayedHistoryCurves[i].historyIndex)) {
sortedDisplayIndices.append(i);
}
}
std::sort(sortedDisplayIndices.begin(), sortedDisplayIndices.end(), std::greater<int>());
for (int dispIdx : sortedDisplayIndices) {
const auto& ref = _displayedHistoryCurves[dispIdx];
if (ref.curveStartIndex + 1 < _fitCurves.size()) {
_fitCurves[ref.curveStartIndex + 1]->detach();
delete _fitCurves[ref.curveStartIndex + 1];
_fitCurves.removeAt(ref.curveStartIndex + 1);
_fitCurves[ref.curveStartIndex]->detach();
delete _fitCurves[ref.curveStartIndex];
_fitCurves.removeAt(ref.curveStartIndex);
}
if (ref.rectIndex < _selectionRectItems.size()) {
_selectionRectItems[ref.rectIndex]->detach();
delete _selectionRectItems[ref.rectIndex];
_selectionRectItems.removeAt(ref.rectIndex);
}
_displayedHistoryCurves.removeAt(dispIdx);
}
QList<FitCurveData> remainingCurves;
QList<DisplayedCurveRef> remainingRefs;
for (const auto& ref : _displayedHistoryCurves) {
remainingCurves.append(_fitHistoryList[ref.historyIndex].curveList[ref.curveIndex]);
remainingRefs.append(ref);
}
QList<int> sortedHistoryIndices = historyIndicesToDelete.values();
std::sort(sortedHistoryIndices.begin(), sortedHistoryIndices.end(), std::greater<int>());
for (auto& ref : remainingRefs) {
int offset = 0;
for (int delIdx : sortedHistoryIndices) {
if (delIdx < ref.historyIndex) offset++;
}
ref.historyIndex -= offset;
}
for (int historyIdx : sortedHistoryIndices) {
_fitHistoryList.removeAt(historyIdx);
}
clearFitCurves();
clearAllSelectionRects();
_displayedHistoryCurves.clear();
if (!remainingCurves.isEmpty()) {
displaySelectedCurves(remainingCurves, remainingRefs);
} else {
_plot->replot();
}
saveHistoryToFile();
historyDlg->accept();
onActionShowFitHistory(); // 重新打开更新后的历史窗口
}
}
std::sort(sortedDisplayIndices.begin(), sortedDisplayIndices.end(), std::greater<int>());
});
for (int dispIdx : sortedDisplayIndices) {
const auto& ref = _displayedHistoryCurves[dispIdx];
// 删除曲线(先删本底,再删拟合)
if (ref.curveStartIndex + 1 < _fitCurves.size()) {
_fitCurves[ref.curveStartIndex + 1]->detach();
delete _fitCurves[ref.curveStartIndex + 1];
_fitCurves.removeAt(ref.curveStartIndex + 1);
_fitCurves[ref.curveStartIndex]->detach();
delete _fitCurves[ref.curveStartIndex];
_fitCurves.removeAt(ref.curveStartIndex);
}
// 删除框选
if (ref.rectIndex < _selectionRectItems.size()) {
_selectionRectItems[ref.rectIndex]->detach();
delete _selectionRectItems[ref.rectIndex];
_selectionRectItems.removeAt(ref.rectIndex);
}
// 移除引用
_displayedHistoryCurves.removeAt(dispIdx);
}
// 4. 收集剩余显示的数据(用于重建)
QList<FitCurveData> remainingCurves;
QList<DisplayedCurveRef> remainingRefs;
for (const auto& ref : _displayedHistoryCurves) {
remainingCurves.append(_fitHistoryList[ref.historyIndex].curveList[ref.curveIndex]);
remainingRefs.append(ref);
}
// 5. 更新剩余引用的 historyIndex处理索引偏移
QList<int> sortedHistoryIndices = historyIndicesToDelete.values();
std::sort(sortedHistoryIndices.begin(), sortedHistoryIndices.end(), std::greater<int>());
for (auto& ref : remainingRefs) {
int offset = 0;
for (int delIdx : sortedHistoryIndices) {
if (delIdx < ref.historyIndex) offset++;
}
ref.historyIndex -= offset;
}
// 6. 删除历史数据
for (int historyIdx : sortedHistoryIndices) {
_fitHistoryList.removeAt(historyIdx);
}
// 7. 重建界面显示(确保索引正确)
clearFitCurves();
clearAllSelectionRects();
_displayedHistoryCurves.clear();
if (!remainingCurves.isEmpty()) {
displaySelectedCurves(remainingCurves, remainingRefs);
} else {
_plot->replot();
}
// 8. 保存并刷新
saveHistoryToFile();
dlg.accept();
onActionShowFitHistory();
confirmBox->show();
});
connect(btnClose, &QPushButton::clicked, &dlg, &QDialog::accept);
dlg.exec();
connect(btnClose, &QPushButton::clicked, historyDlg, &QDialog::close);
historyDlg->show(); // 非模态显示
}
void EnergyCountPeakFitView::onActionDeleteHoveredRect()

View File

@ -1,11 +1,10 @@
#include "PeakFitParamsDialog.h"
PeakFitParamsDialog::PeakFitParamsDialog(const FitParams& defaultParams, QWidget* parent)
: QDialog(parent)
: QDialog(parent, Qt::Dialog | Qt::WindowCloseButtonHint)
{
setWindowTitle(QStringLiteral(u"光子峰拟合参数设置"));
setModal(true);
QVBoxLayout* mainLayout = new QVBoxLayout(this);
QFormLayout* form = new QFormLayout();