diff --git a/src/EnergyCountPeakFitView/EnergyCountPeakFitView.cpp b/src/EnergyCountPeakFitView/EnergyCountPeakFitView.cpp index 6b0b7b8..73a9133 100644 --- a/src/EnergyCountPeakFitView/EnergyCountPeakFitView.cpp +++ b/src/EnergyCountPeakFitView/EnergyCountPeakFitView.cpp @@ -57,11 +57,9 @@ QJsonObject PeakFitHistoryItem::toJson() const curveObj["center"] = curve.center; curveObj["xMin"] = curve.xMin; curveObj["xMax"] = curve.xMax; - curveObj["fwhm"] = curve.fwhm; curveObj["area"] = curve.area; - - //保存框选区域 + curveObj["selectionIndex"] = curve.selectionIndex; if (!curve.selectionRect.isNull()) { QJsonObject rectObj; rectObj["left"] = curve.selectionRect.left(); @@ -92,11 +90,9 @@ PeakFitHistoryItem PeakFitHistoryItem::fromJson(const QJsonObject& obj) curve.center = curveObj["center"].toDouble(); curve.xMin = curveObj["xMin"].toDouble(); curve.xMax = curveObj["xMax"].toDouble(); - curve.fwhm = curveObj["fwhm"].toDouble(0.0); curve.area = curveObj["area"].toDouble(0.0); - - //加载框选区域 + curve.selectionIndex = curveObj["selectionIndex"].toInt(-1); if (curveObj.contains("selectionRect")) { QJsonObject rectObj = curveObj["selectionRect"].toObject(); curve.selectionRect = QRectF( @@ -104,7 +100,7 @@ PeakFitHistoryItem PeakFitHistoryItem::fromJson(const QJsonObject& obj) rectObj["top"].toDouble(), rectObj["right"].toDouble() - rectObj["left"].toDouble(), rectObj["bottom"].toDouble() - rectObj["top"].toDouble() - ); + ); } item.curveList.append(curve); } @@ -122,6 +118,8 @@ EnergyCountPeakFitView::EnergyCountPeakFitView(QWidget* parent) setupPlot(); this->_menu = new QMenu(this); setupMenu(); + connect(this, &EnergyCountPeakFitView::newFitResultAdded, this, &EnergyCountPeakFitView::onNewFitResultAdded); + } EnergyCountPeakFitView::~EnergyCountPeakFitView() @@ -137,6 +135,7 @@ void EnergyCountPeakFitView::InitViewWorkspace(const QString& project_name) Q_UNUSED(project_name); if (project_name.isEmpty()) return; + auto project_model = ProjectList::Instance()->GetProjectModel(project_name); if (!project_model) return; @@ -147,7 +146,33 @@ void EnergyCountPeakFitView::InitViewWorkspace(const QString& project_name) project_dir.mkpath(_workspace); _historyFilePath = QDir(_workspace).filePath("peak_fit_history.json"); + + clearFitCurves(); + clearAllSelectionRects(); + _currentFitRecords.clear(); + _displayedHistoryCurves.clear(); + _fitHistoryList.clear(); + loadHistoryFromFile(); + + QList allHistoryCurves; + QList allHistoryRefs; + + for (int historyIdx = 0; historyIdx < _fitHistoryList.size(); ++historyIdx) { + const PeakFitHistoryItem& historyItem = _fitHistoryList[historyIdx]; + for (int curveIdx = 0; curveIdx < historyItem.curveList.size(); ++curveIdx) { + allHistoryCurves.append(historyItem.curveList[curveIdx]); + DisplayedCurveRef ref; + ref.historyIndex = historyIdx; + ref.curveIndex = curveIdx; + allHistoryRefs.append(ref); + } + } + if (!allHistoryCurves.isEmpty()) { + displaySelectedCurves(allHistoryCurves, allHistoryRefs); + } + + _plot->replot(); } void EnergyCountPeakFitView::SetAnalyzeDataFilename(const QMap& data_files_set) @@ -177,7 +202,6 @@ void EnergyCountPeakFitView::setupPlot() _plot->setAxisTitle(QwtPlot::xBottom, energy_label); _plot->setAxisTitle(QwtPlot::yLeft, count_label); - // set axis auto scale _plot->setAxisAutoScale(QwtPlot::xBottom, true); _plot->setAxisAutoScale(QwtPlot::yLeft, true); @@ -185,7 +209,6 @@ void EnergyCountPeakFitView::setupPlot() _plot->enableAxis(QwtPlot::xBottom); _plot->enableAxis(QwtPlot::yLeft); - // 设置QWT图例 QwtLegend* legend = new QwtLegend(); legend->setDefaultItemMode(QwtLegendData::ReadOnly); _plot->insertLegend(legend, QwtPlot::RightLegend); @@ -194,12 +217,11 @@ void EnergyCountPeakFitView::setupPlot() QwtPlotCurve *dummyFit = new QwtPlotCurve(QStringLiteral(u"拟合数据")); dummyFit->setPen(QPen(Qt::blue, 2)); - dummyFit->setSamples(QVector(), QVector()); // 无数据 - dummyFit->setVisible(false); // 不在画布上绘制 - dummyFit->setItemAttribute(QwtPlotItem::Legend, true); // 保证图例显示(默认 true) + dummyFit->setSamples(QVector(), QVector()); + dummyFit->setVisible(false); + dummyFit->setItemAttribute(QwtPlotItem::Legend, true); dummyFit->attach(_plot); - // 本底数据(虚拟,图例用) QwtPlotCurve *dummyBg = new QwtPlotCurve(QStringLiteral(u"本底数据")); dummyBg->setPen(QPen(Qt::yellow, 2)); dummyBg->setSamples(QVector(), QVector()); @@ -209,7 +231,6 @@ void EnergyCountPeakFitView::setupPlot() _data_selector = new CustomQwtPlotXaxisSelector(_plot->canvas()); _data_selector->setEnabled(false); - // 启用鼠标追踪,并安装事件过滤器 _plot->canvas()->setMouseTracking(true); _plot->canvas()->installEventFilter(this); } @@ -226,9 +247,6 @@ void EnergyCountPeakFitView::setupMenu() this->_plot->ResetPlot(); }); this->_menu->addSeparator(); - //QAction* action_set_curve_show = this->_menu->addAction(QStringLiteral(u"选择曲线")); - //action_set_curve_show->setObjectName("curve_show_setting"); - //connect(action_set_curve_show, &QAction::triggered, this, &EnergyCountPeakFitView::onActionCurveShowSetting); QAction* action_plot_config = this->_menu->addAction(QStringLiteral(u"图表配置")); action_plot_config->setObjectName("plot_config"); connect(action_plot_config, &QAction::triggered, this, &EnergyCountPeakFitView::onActionPlotConfigure); @@ -236,8 +254,7 @@ void EnergyCountPeakFitView::setupMenu() this->_menu->addSeparator(); QAction* action_refit_rect = this->_menu->addAction(QStringLiteral(u"重新拟合当前区域")); connect(action_refit_rect, &QAction::triggered, this, &EnergyCountPeakFitView::onActionRefitCurrentRect); - //QAction* action_save_fit = this->_menu->addAction(QStringLiteral(u"保存当前拟合结果")); - //connect(action_save_fit, &QAction::triggered, this, &EnergyCountPeakFitView::onActionSaveCurrentFit); + QAction* action_show_history = this->_menu->addAction(QStringLiteral(u"峰拟合结果")); connect(action_show_history, &QAction::triggered, this, &EnergyCountPeakFitView::onActionShowFitHistory); @@ -245,14 +262,17 @@ void EnergyCountPeakFitView::setupMenu() QAction* action_delete_hovered_rect = this->_menu->addAction(QStringLiteral(u"删除当前框选区域")); connect(action_delete_hovered_rect, &QAction::triggered, this, &EnergyCountPeakFitView::onActionDeleteHoveredRect); - //QAction* action_hint = this->_menu->addAction(QStringLiteral(u"框选提示:按住 Ctrl 键并拖动左键")); - //action_hint->setEnabled(false); } void EnergyCountPeakFitView::loadDataFromFile(const QString& data_name, const QString& filename) { - //加载新数据前清空旧数据 _currentFitRecords.clear(); + QList currentHistoryCurves; + QList currentHistoryRefs; + for (const auto& ref : _displayedHistoryCurves) { + currentHistoryCurves.append(_fitHistoryList[ref.historyIndex].curveList[ref.curveIndex]); + currentHistoryRefs.append(ref); + } clearFitCurves(); clearAllSelectionRects(); _plot->replot(); @@ -283,13 +303,14 @@ void EnergyCountPeakFitView::loadDataFromFile(const QString& data_name, const QS _plot->SetAxisInitRange(QwtPlot::xBottom, 0.0f, x_max); _plot->SetAxisInitRange(QwtPlot::yLeft, 0.0f, y_max); - // 绘制曲线 QwtPlotCurve* curve = new QwtPlotCurve(QStringLiteral(u"原始数据")); - curve->setPen(QPen(Qt::gray, 2)); // 原始数据统一灰色 + curve->setPen(QPen(Qt::gray, 2)); curve->setSamples(x, y); _plot->AddCurve(curve,false); - + if (!currentHistoryCurves.isEmpty()) { + displaySelectedCurves(currentHistoryCurves, currentHistoryRefs); + } } void EnergyCountPeakFitView::onActionCurveShowSetting() @@ -307,7 +328,6 @@ void EnergyCountPeakFitView::onActionCurveShowSetting() for (QwtPlotCurve* curve : this->_plot->GetCurveList()) { curve_name_list.append(curve->title().text()); } - // 自动计算多列排布 int num_columns = std::sqrt(this->_plot->GetCurveList().size()); if (num_columns == 0) num_columns = 1; @@ -336,7 +356,6 @@ void EnergyCountPeakFitView::onActionCurveShowSetting() checkbox_column_layout->addStretch(); } checkbox_layout->addLayout(checkbox_column_layout); - // 全选和反选 auto curveCheckboxUpdate = [this, curve_checkbox_map]() { for (QwtPlotCurve* curve : this->_plot->GetCurveList()) { curve_checkbox_map[curve]->setChecked(curve->isVisible()); @@ -389,27 +408,22 @@ bool EnergyCountPeakFitView::eventFilter(QObject* watched, QEvent* event) if (watched == _plot->canvas()) { QMouseEvent* me = static_cast(event); - // 按下 Ctrl+左键 开始框选 if (event->type() == QEvent::MouseButtonPress && - me->button() == Qt::LeftButton /*&&*/ - /*(me->modifiers() & Qt::ControlModifier)*/) { + me->button() == Qt::LeftButton ) { startSelection(me->pos()); return true; } - // 移动时更新橡皮筋 else if (event->type() == QEvent::MouseMove && _isSelecting) { updateSelection(me->pos()); return true; } - // 释放左键完成框选 else if (event->type() == QEvent::MouseButtonRelease && me->button() == Qt::LeftButton && _isSelecting) { finishSelection(); fadeSelectionRectBorders(); return true; } - //鼠标移动时检测悬停 else if (event->type() == QEvent::MouseMove) { updateHoverState(me->pos()); return true; @@ -427,7 +441,6 @@ void EnergyCountPeakFitView::startSelection(const QPoint& pos) _rubberBand = new QRubberBand(QRubberBand::Rectangle, _plot->canvas()); } int canvasHeight = _plot->canvas()->height(); - // 初始矩形:宽度为0,高度为画布全高 _rubberBand->setGeometry(QRect(_selectionStart.x(), 0, 0, canvasHeight)); _rubberBand->show(); } @@ -447,26 +460,17 @@ void EnergyCountPeakFitView::finishSelection() { if (_rubberBand) { - QRect finalRect = _rubberBand->geometry(); _rubberBand->hide(); - if (finalRect.width() > 2) { const QwtScaleMap xMap = _plot->canvasMap(QwtPlot::xBottom); double xMin = xMap.invTransform(finalRect.left()); double xMax = xMap.invTransform(finalRect.right()); - if (xMin > xMax) std::swap(xMin, xMax); - - double yMin = _plot->axisScaleDiv(QwtPlot::yLeft).lowerBound(); double yMax = _plot->axisScaleDiv(QwtPlot::yLeft).upperBound(); QRectF plotRect(xMin, yMin, xMax - xMin, yMax - yMin); -// addSelectionRect(plotRect); - - - // 获取曲线数据 QList curves = _plot->GetCurveList(); if (!curves.isEmpty()) { QwtPlotCurve* originalCurve = curves.first(); @@ -475,7 +479,6 @@ void EnergyCountPeakFitView::finishSelection() origX.push_back(originalCurve->sample(i).x()); origY.push_back(originalCurve->sample(i).y()); } - QVector roiX, roiY; for (int i = 0; i < origX.size(); ++i) { if (origX[i] >= xMin && origX[i] <= xMax) { @@ -483,22 +486,17 @@ void EnergyCountPeakFitView::finishSelection() roiY.push_back(origY[i]); } } - 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"); - - // 关键修改:实例化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)逻辑移到这里) + algorithmDlg->setModal(false); + algorithmDlg->setAttribute(Qt::WA_DeleteOnClose); connect(algorithmDlg, &QInputDialog::accepted, this, [=]() { QString selected = algorithmDlg->textValue(); if (selected == algorithms.first()) @@ -508,27 +506,25 @@ void EnergyCountPeakFitView::finishSelection() armaRoiX(i) = roiX[i]; armaRoiY(i) = roiY[i]; } - // 执行拟合(曲线自动添加到主图) QList results = performPeakFitting(roiX, roiY); if (results.isEmpty()) { qDebug()<< QStringLiteral(u"拟合结果:") << QStringLiteral(u"未检测到有效峰或拟合失败。"); } else { - addSelectionRect(plotRect); - fadeSelectionRectBorders(); // 框选边框变为虚线(与原逻辑一致) + int newHistoryIndex = saveCurrentFitToHistory(plotRect); + addSelectionRect(plotRect, newHistoryIndex); + 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(); // 刷新显示 + _plot->replot(); } - saveCurrentFitToHistory(); + emit newFitResultAdded(); } } }); - - algorithmDlg->show(); // 非模态显示 + algorithmDlg->show(); } } } @@ -536,20 +532,18 @@ void EnergyCountPeakFitView::finishSelection() _isSelecting = false; } -void EnergyCountPeakFitView::addSelectionRect(const QRectF& plotRect) +void EnergyCountPeakFitView::addSelectionRect(const QRectF& plotRect ,int index) { PlotRectItem* rectItem = new PlotRectItem("Selection"); - rectItem->setAxes(QwtPlot::xBottom, QwtPlot::yLeft); - rectItem->setRect(plotRect); - // 设置填充为透明(无填充) - rectItem->setBrush(QBrush(Qt::transparent)); // 或 Qt::NoBrush - // 设置边框颜色为红色,线宽2 - rectItem->setPen(QPen(Qt::red, 2)); - rectItem->setSelectionType("current"); - rectItem->setSelectionIndex(_selectionRectItems.size()); - rectItem->attach(_plot); - _selectionRectItems.append(rectItem); - _plot->replot(); + rectItem->setAxes(QwtPlot::xBottom, QwtPlot::yLeft); + rectItem->setRect(plotRect); + rectItem->setBrush(QBrush(Qt::transparent)); + rectItem->setPen(QPen(Qt::red, 2)); + rectItem->setSelectionType("current"); + rectItem->setSelectionIndex(index); + rectItem->attach(_plot); + _selectionRectItems.append(rectItem); + _plot->replot(); } @@ -557,7 +551,7 @@ void EnergyCountPeakFitView::fadeSelectionRectBorders() { for (PlotRectItem* item : _selectionRectItems) { if (item) { - QPen fadedPen(Qt::red, 1, Qt::DashLine); // 可根据原样式调整 + QPen fadedPen(Qt::red, 1, Qt::DashLine); item->setPen(fadedPen); } } @@ -575,7 +569,6 @@ QList EnergyCountPeakFitView::performPeakFitting(const QVector EnergyCountPeakFitView::performPeakFitting(const QVector EnergyCountPeakFitView::performPeakFitting(const QVector xs, ys,ysTw; - double step = (xMax - xMin) / (numPoints - 1); - - arma::vec p(6); - p(0) = result.amplitude; - p(1) = result.sigma; - p(2) = result.sigmoidH; - p(3) = result.sigmoidW; - p(4) = result.baseline; - p(5) = result.center; - - for (int i = 0; i < numPoints; ++i) { - double x = xMin + i * step; - xs.append(x); - double y = PhotonPeakModel(x, p); - ys.append(y); - } - - - QwtPlotCurve* curve = new QwtPlotCurve(QStringLiteral(u"拟合数据")); - curve->setPen(QPen(Qt::blue, 2)); - curve->setSamples(xs, ys); - curve->attach(_plot); - return curve; -} QList EnergyCountPeakFitView::createFitCurve(const PeakFitResult &result, arma::vec xVec) { - QList curveList; // 用于返回 + QList curveList; QVector xs, ys ,ysTw; arma::vec p(6); @@ -702,14 +667,14 @@ QList EnergyCountPeakFitView::createFitCurve(const PeakFitResult curve->setSamples(xs, ys); curve->setItemAttribute(QwtPlotItem::Legend, false); curve->attach(_plot); - curveList.append(curve); // 加入列表 + curveList.append(curve); QwtPlotCurve* curveTw = new QwtPlotCurve(QStringLiteral(u"本底数据")); curveTw->setPen(QPen(Qt::yellow, 2)); curveTw->setSamples(xs, ysTw); curveTw->setItemAttribute(QwtPlotItem::Legend, false); curveTw->attach(_plot); - curveList.append(curveTw); // 加入列表 + curveList.append(curveTw); return curveList; } @@ -763,18 +728,13 @@ void EnergyCountPeakFitView::saveHistoryToFile() } } -void EnergyCountPeakFitView::saveCurrentFitToHistory() +int EnergyCountPeakFitView::saveCurrentFitToHistory(const QRectF& selectionRect) { - if (_currentFitRecords.isEmpty()) return; - - //只取最新的一次拟合结果(列表中的最后一条) + if (_currentFitRecords.isEmpty()) return -1; const auto& latestRecord = _currentFitRecords.last(); - int latestRectIndex = _currentFitRecords.size() - 1; // 对应的框选区域索引 PeakFitHistoryItem item; item.timestamp = QDateTime::currentDateTime(); - - // 仅构建当前这一次的曲线数据 FitCurveData curve; curve.amplitude = latestRecord.params(0); curve.sigma = latestRecord.params(1); @@ -786,88 +746,26 @@ void EnergyCountPeakFitView::saveCurrentFitToHistory() curve.xMax = latestRecord.xMax; curve.fwhm = latestRecord.result.fwhm; curve.area = latestRecord.result.area; - - // 关联对应的框选区域(最新的那个) - if (latestRectIndex < _selectionRectItems.size()) { - curve.selectionRect = _selectionRectItems[latestRectIndex]->rect(); - } - item.curveList.append(curve); // 仅添加这一条曲线 - + curve.selectionRect = selectionRect; + int newHistoryIndex = _fitHistoryList.size(); + curve.selectionIndex = newHistoryIndex; + item.curveList.append(curve); _fitHistoryList.append(item); + + _currentFitRecords.last().historyIndex = newHistoryIndex; + DisplayedCurveRef newRef; + newRef.historyIndex = newHistoryIndex; + newRef.curveIndex = 0; + newRef.rectIndex = newHistoryIndex; + newRef.curveStartIndex = newRef.rectIndex * 2; + _displayedHistoryCurves.append(newRef); + saveHistoryToFile(); + return newHistoryIndex; } -void EnergyCountPeakFitView::displayFitFromHistory(const PeakFitHistoryItem& item) -{ - // 遍历历史中的所有曲线,逐一重建 - for (const auto& curve : item.curveList) { - // 1. 生成X轴数据 - const int numPoints = 200; - QVector xs, ys, ysTw; - double step = (curve.xMax - curve.xMin) / (numPoints - 1); - // 2. 构建参数向量 - arma::vec p(6); - p(0) = curve.amplitude; - p(1) = curve.sigma; - p(2) = curve.sigmoidH; - p(3) = curve.sigmoidW; - p(4) = curve.baseline; - p(5) = curve.center; - // 3. 生成拟合曲线数据 - for (int i = 0; i < numPoints; ++i) { - double x = curve.xMin + i * step; - xs.append(x); - ys.append(PhotonPeakModel(x, p)); - } - - // 4. 创建并添加拟合曲线 - QwtPlotCurve* fitCurve = new QwtPlotCurve(QStringLiteral(u"历史拟合数据")); - fitCurve->setPen(QPen(Qt::blue, 2)); - fitCurve->setSamples(xs, ys); - fitCurve->setItemAttribute(QwtPlotItem::Legend, false); - fitCurve->attach(_plot); - _fitCurves.append(fitCurve); - - // 5. 生成本底曲线数据 - arma::vec xVec(numPoints); - for (int i = 0; i < numPoints; ++i) xVec(i) = xs[i]; - arma::vec yTw = PhotonPeakModelTuowei(xVec, p); - for (int i = 0; i < yTw.size(); ++i) ysTw.append(yTw(i)); - - // 6. 创建并添加本底曲线 - QwtPlotCurve* bgCurve = new QwtPlotCurve(QStringLiteral(u"历史本底数据")); - bgCurve->setPen(QPen(Qt::yellow, 2, Qt::DashLine)); - bgCurve->setSamples(xs, ysTw); - bgCurve->setItemAttribute(QwtPlotItem::Legend, false); - bgCurve->attach(_plot); - _fitCurves.append(bgCurve); - // 7. 恢复并绘制框选区域 - if (!curve.selectionRect.isNull()) { - PlotRectItem* histRect = new PlotRectItem("HistorySelection"); - - //显式绑定坐标轴,确保坐标映射正确 - histRect->setAxes(QwtPlot::xBottom, QwtPlot::yLeft); - - histRect->setRect(curve.selectionRect); - histRect->setBrush(QBrush(Qt::transparent)); - // 历史框选区域用红色虚线区分 - histRect->setPen(QPen(Qt::red, 2, Qt::DashLine)); - histRect->setPeakData(curve.center, curve.fwhm, curve.area, curve.baseline); - - histRect->attach(_plot); - _selectionRectItems.append(histRect); - } - } - - _plot->replot(); -} - -void EnergyCountPeakFitView::onActionSaveCurrentFit() -{ - saveCurrentFitToHistory(); -} void EnergyCountPeakFitView::onActionShowFitHistory() { @@ -890,46 +788,47 @@ void EnergyCountPeakFitView::onActionShowFitHistory() return; } - // 替换栈变量为堆上非模态对话框 - 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); + if (_historyDlg) { + _historyDlg->raise(); + _historyDlg->activateWindow(); + return; + } - QVBoxLayout* layout = new QVBoxLayout(historyDlg); - QStandardItemModel* model = new QStandardItemModel(historyDlg); + _historyDlg = new QDialog(this , Qt::Dialog | Qt::WindowCloseButtonHint); + _historyDlg->setWindowTitle(QStringLiteral(u"峰拟合结果")); + _historyDlg->setMinimumSize(1000, 500); + _historyDlg->setSizeGripEnabled(true); + _historyDlg->setModal(false); + _historyDlg->setAttribute(Qt::WA_DeleteOnClose); + + connect(_historyDlg, &QDialog::finished, this, &EnergyCountPeakFitView::onHistoryDlgClosed); + + QVBoxLayout* layout = new QVBoxLayout(_historyDlg); + QStandardItemModel* model = new QStandardItemModel(_historyDlg); QStringList headers; - headers << "" << "振幅(A)" << "高斯宽度(delt)" << "Sigmoid幅度(H)" << "Sigmoid宽度(W)" << "基线(C)" << "峰中心(P)"; + headers << "序号" << "振幅(A)" << "高斯宽度(delt)" << "Sigmoid幅度(H)" << "Sigmoid宽度(W)" << "基线(C)" ; model->setHorizontalHeaderLabels(headers); QVector> rowToDataIndices; model->blockSignals(true); + int rowNumber = 1; + 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 rowItems; - QStandardItem* checkItem = new QStandardItem(); + QStandardItem* checkItem = new QStandardItem(QString::number(rowNumber++)); checkItem->setCheckable(true); checkItem->setEditable(false); - - bool isCurrentlyDisplayed = false; - for (const auto& ref : _displayedHistoryCurves) { - if (ref.historyIndex == historyIdx && ref.curveIndex == curveIdx) { - isCurrentlyDisplayed = true; - break; - } - } - checkItem->setCheckState(isCurrentlyDisplayed ? Qt::Checked : Qt::Unchecked); + checkItem->setCheckState(isHistoryCurveDisplayed(historyIdx, curveIdx) ? Qt::Checked : Qt::Unchecked); rowItems.append(checkItem); - 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)); @@ -937,15 +836,18 @@ void EnergyCountPeakFitView::onActionShowFitHistory() } model->blockSignals(false); + _historyDlg->setProperty("rowToDataIndices", QVariant::fromValue(rowToDataIndices)); + QHBoxLayout* topBtnLayout = new QHBoxLayout(); - QPushButton* btnSelectAll = new QPushButton(QStringLiteral(u"全选"), historyDlg); - QPushButton* btnInvertSelection = new QPushButton(QStringLiteral(u"反选"), historyDlg); + 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(historyDlg); + QTableView* tableView = new QTableView(_historyDlg); + tableView->setObjectName("historyTableView"); tableView->setModel(model); tableView->setSelectionBehavior(QAbstractItemView::SelectRows); tableView->setSelectionMode(QAbstractItemView::SingleSelection); @@ -955,15 +857,14 @@ void EnergyCountPeakFitView::onActionShowFitHistory() tableView->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); layout->addWidget(tableView); - QPushButton* btnDelete = new QPushButton(QStringLiteral(u"删除选中记录"), historyDlg); - QPushButton* btnClose = new QPushButton(QStringLiteral(u"关闭"), historyDlg); + 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, [=]() { model->blockSignals(true); for (int row = 0; row < model->rowCount(); ++row) { @@ -974,20 +875,20 @@ void EnergyCountPeakFitView::onActionShowFitHistory() QList selectedCurves; QList selectedRefs; + QVector> indices = _historyDlg->property("rowToDataIndices").value>>(); for (int row = 0; row < model->rowCount(); ++row) { if (model->item(row, 0)->checkState() == Qt::Checked) { - QPair indices = rowToDataIndices[row]; - selectedCurves.append(_fitHistoryList[indices.first].curveList[indices.second]); + QPair idx = indices[row]; + selectedCurves.append(_fitHistoryList[idx.first].curveList[idx.second]); DisplayedCurveRef ref; - ref.historyIndex = indices.first; - ref.curveIndex = indices.second; + ref.historyIndex = idx.first; + ref.curveIndex = idx.second; selectedRefs.append(ref); } } displaySelectedCurves(selectedCurves, selectedRefs); }); - // 反选按钮逻辑保持不变 connect(btnInvertSelection, &QPushButton::clicked, [=]() { model->blockSignals(true); for (int row = 0; row < model->rowCount(); ++row) { @@ -999,47 +900,47 @@ void EnergyCountPeakFitView::onActionShowFitHistory() QList selectedCurves; QList selectedRefs; + QVector> indices = _historyDlg->property("rowToDataIndices").value>>(); for (int row = 0; row < model->rowCount(); ++row) { if (model->item(row, 0)->checkState() == Qt::Checked) { - QPair indices = rowToDataIndices[row]; - selectedCurves.append(_fitHistoryList[indices.first].curveList[indices.second]); + QPair idx = indices[row]; + selectedCurves.append(_fitHistoryList[idx.first].curveList[idx.second]); DisplayedCurveRef ref; - ref.historyIndex = indices.first; - ref.curveIndex = indices.second; + ref.historyIndex = idx.first; + ref.curveIndex = idx.second; selectedRefs.append(ref); } } displaySelectedCurves(selectedCurves, selectedRefs); }); - // 复选框状态改变逻辑保持不变 - connect(model, &QStandardItemModel::itemChanged, [=](QStandardItem* /*item*/) { + connect(model, &QStandardItemModel::itemChanged, this, [=](QStandardItem* /*item*/) { QList selectedCurves; QList selectedRefs; + QVector> indices = _historyDlg->property("rowToDataIndices").value>>(); for (int row = 0; row < model->rowCount(); ++row) { if (model->item(row, 0)->checkState() == Qt::Checked) { - QPair indices = rowToDataIndices[row]; - selectedCurves.append(_fitHistoryList[indices.first].curveList[indices.second]); + QPair idx = indices[row]; + selectedCurves.append(_fitHistoryList[idx.first].curveList[idx.second]); DisplayedCurveRef ref; - ref.historyIndex = indices.first; - ref.curveIndex = indices.second; + ref.historyIndex = idx.first; + ref.curveIndex = idx.second; selectedRefs.append(ref); } } displaySelectedCurves(selectedCurves, selectedRefs); }); - // 删除按钮逻辑(改为非模态确认框) connect(btnDelete, &QPushButton::clicked, [=]() { QSet historyIndicesToDelete; + QVector> indices = _historyDlg->property("rowToDataIndices").value>>(); for (int row = 0; row < model->rowCount(); ++row) { if (model->item(row, 0)->checkState() == Qt::Checked) { - QPair indices = rowToDataIndices[row]; - historyIndicesToDelete.insert(indices.first); + historyIndicesToDelete.insert(indices[row].first); } } if (historyIndicesToDelete.isEmpty()) { - QMessageBox* msgBox = new QMessageBox(historyDlg); + QMessageBox* msgBox = new QMessageBox(_historyDlg); msgBox->setWindowTitle(QStringLiteral(u"提示")); msgBox->setText(QStringLiteral(u"请先勾选要删除的记录。")); msgBox->setStandardButtons(QMessageBox::Ok); @@ -1050,23 +951,15 @@ void EnergyCountPeakFitView::onActionShowFitHistory() } QString msg = QStringLiteral(u"确定要删除选中的 %1 条历史记录吗?").arg(historyIndicesToDelete.size()); - QMessageBox* confirmBox = new QMessageBox(historyDlg); + 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); - // 保存临时数据 - 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 historyIndicesToDelete = confirmBox->property("historyIndicesToDelete").value>(); - - // 原有删除逻辑保持不变 QList sortedDisplayIndices; for (int i = 0; i < _displayedHistoryCurves.size(); ++i) { if (historyIndicesToDelete.contains(_displayedHistoryCurves[i].historyIndex)) { @@ -1108,7 +1001,6 @@ void EnergyCountPeakFitView::onActionShowFitHistory() } ref.historyIndex -= offset; } - for (int historyIdx : sortedHistoryIndices) { _fitHistoryList.removeAt(historyIdx); } @@ -1123,426 +1015,209 @@ void EnergyCountPeakFitView::onActionShowFitHistory() } saveHistoryToFile(); - historyDlg->accept(); - onActionShowFitHistory(); // 重新打开更新后的历史窗口 + + emit newFitResultAdded(); } }); - confirmBox->show(); }); - connect(btnClose, &QPushButton::clicked, historyDlg, &QDialog::close); - historyDlg->show(); // 非模态显示 + connect(btnClose, &QPushButton::clicked, _historyDlg, &QDialog::close); + _historyDlg->show(); } void EnergyCountPeakFitView::onActionDeleteHoveredRect() { if (!_hoveredRectItem) { - QMessageBox::information(this, QStringLiteral(u"提示"), - QStringLiteral(u"请先将鼠标移动到要删除的框选区域上(区域边框会变为实线),然后再点击此菜单。")); - return; + QMessageBox::information(this, QStringLiteral(u"提示"), + QStringLiteral(u"请先将鼠标移动到要删除的框选区域上(区域边框会变为实线),然后再点击此菜单。")); + return; + } + QString rectType = _hoveredRectItem->selectionType(); + int historyIndex = _hoveredRectItem->selectionIndex(); + + int curveStartIdx = historyIndex * 2; + if (curveStartIdx + 1 < _fitCurves.size()) { + _fitCurves[curveStartIdx + 1]->detach(); + delete _fitCurves[curveStartIdx + 1]; + _fitCurves.removeAt(curveStartIdx + 1); + _fitCurves[curveStartIdx]->detach(); + delete _fitCurves[curveStartIdx]; + _fitCurves.removeAt(curveStartIdx); + } + + _hoveredRectItem->detach(); + delete _hoveredRectItem; + for (auto it = _selectionRectItems.begin(); it != _selectionRectItems.end();) { + if (*it && (*it)->selectionIndex() == historyIndex) { + it = _selectionRectItems.erase(it); + } else { + ++it; } + } + _hoveredRectItem = nullptr; - //使用新接口获取类型和索引 - QString rectType = _hoveredRectItem->selectionType(); - int rectIndex = _hoveredRectItem->selectionIndex(); + if (historyIndex >= 0 && historyIndex < _fitHistoryList.size()) { + _fitHistoryList.removeAt(historyIndex); + saveHistoryToFile(); + } - if (rectType == "current") { - if (rectIndex < 0 || rectIndex >= _currentFitRecords.size()) { - QMessageBox::warning(this, QStringLiteral(u"错误"), QStringLiteral(u"框选区域索引无效。")); - return; - } - - int curveStartIdx = rectIndex * 2; - int historyIndex = _currentFitRecords[rectIndex].historyIndex; - - // 1. 删除拟合曲线和本底曲线 - if (curveStartIdx + 1 < _fitCurves.size()) { - _fitCurves[curveStartIdx + 1]->detach(); - delete _fitCurves[curveStartIdx + 1]; - _fitCurves.removeAt(curveStartIdx + 1); - - _fitCurves[curveStartIdx]->detach(); - delete _fitCurves[curveStartIdx]; - _fitCurves.removeAt(curveStartIdx); - } - - // 2. 删除当前拟合记录 - _currentFitRecords.removeAt(rectIndex); - - // 3. 删除框选区域 - _hoveredRectItem->detach(); - delete _hoveredRectItem; - _selectionRectItems.removeAt(rectIndex); - _hoveredRectItem = nullptr; - - // 4. 删除对应的历史记录 - if (historyIndex >= 0 && historyIndex < _fitHistoryList.size()) { - _fitHistoryList.removeAt(historyIndex); - saveHistoryToFile(); - - // 5. 更新剩余当前记录的历史索引 - for (auto& record : _currentFitRecords) { - if (record.historyIndex > historyIndex) { - record.historyIndex--; - } - } - - // 6. 更新历史显示记录的索引 - for (auto& ref : _displayedHistoryCurves) { - if (ref.historyIndex > historyIndex) { - ref.historyIndex--; - } - if (ref.curveStartIndex > curveStartIdx) { - ref.curveStartIndex -= 2; - } - if (ref.rectIndex > rectIndex) { - ref.rectIndex--; - } - } - } - - // 7. 更新剩余当前框选的索引 - for (int i = rectIndex; i < _selectionRectItems.size(); ++i) { - PlotRectItem* item = _selectionRectItems[i]; - if (item->selectionType() == "current") { - item->setSelectionIndex(i); - } else if (item->selectionType() == "history") { - // 更新历史框选的索引 - for (int j = 0; j < _displayedHistoryCurves.size(); ++j) { - if (_displayedHistoryCurves[j].rectIndex == i) { - item->setSelectionIndex(j); - break; - } - } - } - } - - } else if (rectType == "history") { - if (rectIndex < 0 || rectIndex >= _displayedHistoryCurves.size()) { - QMessageBox::warning(this, QStringLiteral(u"错误"), QStringLiteral(u"历史框选区域索引无效。")); - return; - } - - const DisplayedCurveRef& ref = _displayedHistoryCurves[rectIndex]; - int historyIndex = ref.historyIndex; - int curveStartIdx = ref.curveStartIndex; - int rectIdx = ref.rectIndex; - - // 1. 删除拟合曲线和本底曲线 - if (curveStartIdx + 1 < _fitCurves.size()) { - _fitCurves[curveStartIdx + 1]->detach(); - delete _fitCurves[curveStartIdx + 1]; - _fitCurves.removeAt(curveStartIdx + 1); - - _fitCurves[curveStartIdx]->detach(); - delete _fitCurves[curveStartIdx]; - _fitCurves.removeAt(curveStartIdx); - } - - // 2. 删除框选区域 - _hoveredRectItem->detach(); - delete _hoveredRectItem; - _selectionRectItems.removeAt(rectIdx); - _hoveredRectItem = nullptr; - - // 3. 从显示引用列表中移除 - _displayedHistoryCurves.removeAt(rectIndex); - - // 4. 删除对应的历史记录 - if (historyIndex >= 0 && historyIndex < _fitHistoryList.size()) { - _fitHistoryList.removeAt(historyIndex); - saveHistoryToFile(); - - // 5. 更新剩余历史显示记录的索引 - for (auto& r : _displayedHistoryCurves) { - if (r.historyIndex > historyIndex) { - r.historyIndex--; - } - if (r.curveStartIndex > curveStartIdx) { - r.curveStartIndex -= 2; - } - if (r.rectIndex > rectIdx) { - r.rectIndex--; - } - } - - // 6. 更新当前记录的历史索引 - for (auto& record : _currentFitRecords) { - if (record.historyIndex > historyIndex) { - record.historyIndex--; - } - } - } - - // 7. 更新剩余历史框选的索引 - for (int i = rectIndex; i < _displayedHistoryCurves.size(); ++i) { - const DisplayedCurveRef& r = _displayedHistoryCurves[i]; - PlotRectItem* item = _selectionRectItems[r.rectIndex]; - item->setSelectionIndex(i); - } - - // 8. 更新当前框选的索引 - for (int i = 0; i < _currentFitRecords.size(); ++i) { - PlotRectItem* item = _selectionRectItems[i]; - if (item->selectionType() == "current" && item->selectionIndex() > rectIdx) { - item->setSelectionIndex(item->selectionIndex() - 1); - } - } + for (auto& ref : _displayedHistoryCurves) { + if (ref.historyIndex > historyIndex) { + ref.historyIndex--; + ref.rectIndex = ref.historyIndex; + ref.curveStartIndex = ref.rectIndex * 2; } + } + for (auto it = _displayedHistoryCurves.begin(); it != _displayedHistoryCurves.end();) { + if (it->historyIndex == historyIndex) { + it = _displayedHistoryCurves.erase(it); + } else { + ++it; + } + } - _plot->replot(); + for (PlotRectItem* item : _selectionRectItems) { + if (item && item->selectionIndex() > historyIndex) { + item->setSelectionIndex(item->selectionIndex() - 1); + } + } + + for (auto& record : _currentFitRecords) { + if (record.historyIndex > historyIndex) { + record.historyIndex--; + } + } + + _plot->replot(); + emit newFitResultAdded(); } void EnergyCountPeakFitView::onActionRefitCurrentRect() { - // 检查是否有悬停的框选区域 if (!_hoveredRectItem) { QMessageBox::information(this, QStringLiteral(u"提示"), - QStringLiteral(u"请先将鼠标移动到要重新拟合的框选区域上(区域边框会变为实线),然后再点击此菜单。")); + QStringLiteral(u"请先将鼠标移动到要重新拟合的框选区域上(区域边框会变为实线),然后再点击此菜单。")); + return; + } + int historyIndex = _hoveredRectItem->selectionIndex(); + + const FitCurveData* originalCurveData = nullptr; + if (historyIndex >= 0 && historyIndex < _fitHistoryList.size()) { + originalCurveData = &_fitHistoryList[historyIndex].curveList[0]; + } else { + QMessageBox::warning(this, QStringLiteral(u"错误"), + QStringLiteral(u"未找到对应区域的拟合记录,无法重新拟合。")); return; } - // 获取框选类型 - QString rectType = _hoveredRectItem->selectionType(); - int rectIndex = _hoveredRectItem->selectionIndex(); + FitParams defaultParams; + defaultParams.A = originalCurveData->amplitude; + defaultParams.delt = originalCurveData->sigma; + defaultParams.H = originalCurveData->sigmoidH; + defaultParams.W = originalCurveData->sigmoidW; + defaultParams.C = originalCurveData->baseline; + defaultParams.P = originalCurveData->center; - if (rectType == "current") { - int rectIdxInList = _selectionRectItems.indexOf(_hoveredRectItem); - if (rectIdxInList == -1 || rectIdxInList >= _currentFitRecords.size()) { - QMessageBox::warning(this, QStringLiteral(u"错误"), - QStringLiteral(u"未找到对应区域的拟合记录,无法重新拟合。")); - return; - } - - const CurrentFitRecord& originalRecord = _currentFitRecords[rectIdxInList]; - arma::vec originalParams = originalRecord.params; - - FitParams defaultParams; - defaultParams.A = originalParams(0); - defaultParams.delt = originalParams(1); - defaultParams.H = originalParams(2); - defaultParams.W = originalParams(3); - defaultParams.C = originalParams(4); - defaultParams.P = originalParams(5); - - PeakFitParamsDialog paramDlg(defaultParams, this); - if (paramDlg.exec() != QDialog::Accepted) { - return; - } - - FitParams userParams = paramDlg.getParams(); - arma::vec newParams(6); - newParams(0) = userParams.A; - newParams(1) = userParams.delt; - newParams(2) = userParams.H; - newParams(3) = userParams.W; - newParams(4) = userParams.C; - newParams(5) = userParams.P; - - double newFwhm = newParams(1) * 2.355; - AdaptiveSimpsonIntegrate::FitFunction fitFunc; - fitFunc.A = newParams(0); - fitFunc.delt = newParams(1); - fitFunc.H = newParams(2); - fitFunc.W = newParams(3); - fitFunc.C = newParams(4); - fitFunc.P = newParams(5); - double lower = fitFunc.P - 3.0 * fitFunc.delt; - double upper = fitFunc.P + 3.0 * fitFunc.delt; - double newArea = AdaptiveSimpsonIntegrate::integrate(fitFunc, lower, upper); - - PeakFitResult newResult; - newResult.center = newParams(5); - newResult.amplitude = newParams(0); - newResult.sigma = newParams(1); - newResult.fwhm = newFwhm; - newResult.area = newArea; - newResult.baseline = newParams(4); - newResult.sigmoidH = newParams(2); - newResult.sigmoidW = newParams(3); - - int curveStartIdx = rectIdxInList * 2; - if (curveStartIdx + 1 < _fitCurves.size()) { - _fitCurves[curveStartIdx]->detach(); - _fitCurves[curveStartIdx + 1]->detach(); - delete _fitCurves[curveStartIdx]; - delete _fitCurves[curveStartIdx + 1]; - _fitCurves.removeAt(curveStartIdx + 1); - _fitCurves.removeAt(curveStartIdx); - } - - arma::vec xVec; - QList curves = _plot->GetCurveList(); - if (!curves.isEmpty()) { - QwtPlotCurve* originalCurve = curves.first(); - QVector origX; - for (size_t i = 0; i < originalCurve->dataSize(); ++i) { - origX.push_back(originalCurve->sample(i).x()); - } - QVector roiX; - for (int i = 0; i < origX.size(); ++i) { - if (origX[i] >= originalRecord.xMin && origX[i] <= originalRecord.xMax) { - roiX.push_back(origX[i]); - } - } - xVec.set_size(roiX.size()); - for (int i = 0; i < roiX.size(); ++i) { - xVec(i) = roiX[i]; - } - } - - QList newCurves = createFitCurve(newResult, xVec); - for (int i = 0; i < newCurves.size(); ++i) { - _fitCurves.insert(curveStartIdx + i, newCurves[i]); - } - - _hoveredRectItem->setPeakData(newResult.center, newResult.fwhm, newResult.area, newResult.baseline); - - CurrentFitRecord newRecord; - newRecord.result = newResult; - newRecord.params = newParams; - newRecord.xMin = originalRecord.xMin; - newRecord.xMax = originalRecord.xMax; - newRecord.historyIndex = originalRecord.historyIndex; // 保留历史索引 - _currentFitRecords.replace(rectIdxInList, newRecord); - - _plot->replot(); - - } else if (rectType == "history") { - if (rectIndex < 0 || rectIndex >= _displayedHistoryCurves.size()) { - QMessageBox::warning(this, QStringLiteral(u"错误"), - QStringLiteral(u"未找到对应历史区域的拟合记录,无法重新拟合。")); - return; - } - - // 1. 获取历史数据引用 - const DisplayedCurveRef& ref = _displayedHistoryCurves[rectIndex]; - int historyIdx = ref.historyIndex; - int curveIdxInHistory = ref.curveIndex; - int curveStartIdx = ref.curveStartIndex; - int rectIdxInList = ref.rectIndex; - - if (historyIdx < 0 || historyIdx >= _fitHistoryList.size()) return; - if (curveIdxInHistory < 0 || curveIdxInHistory >= _fitHistoryList[historyIdx].curveList.size()) return; - - // 2. 从历史记录中提取原始参数 - const FitCurveData& originalCurveData = _fitHistoryList[historyIdx].curveList[curveIdxInHistory]; - - FitParams defaultParams; - defaultParams.A = originalCurveData.amplitude; - defaultParams.delt = originalCurveData.sigma; - defaultParams.H = originalCurveData.sigmoidH; - defaultParams.W = originalCurveData.sigmoidW; - defaultParams.C = originalCurveData.baseline; - defaultParams.P = originalCurveData.center; - - // 3. 弹出参数编辑对话框 - PeakFitParamsDialog paramDlg(defaultParams, this); - if (paramDlg.exec() != QDialog::Accepted) { - return; - } - - // 4. 获取用户修改后的参数 - FitParams userParams = paramDlg.getParams(); - arma::vec newParams(6); - newParams(0) = userParams.A; - newParams(1) = userParams.delt; - newParams(2) = userParams.H; - newParams(3) = userParams.W; - newParams(4) = userParams.C; - newParams(5) = userParams.P; - - // 5. 计算新的 FWHM 和峰面积 - double newFwhm = newParams(1) * 2.355; - AdaptiveSimpsonIntegrate::FitFunction fitFunc; - fitFunc.A = newParams(0); - fitFunc.delt = newParams(1); - fitFunc.H = newParams(2); - fitFunc.W = newParams(3); - fitFunc.C = newParams(4); - fitFunc.P = newParams(5); - double lower = fitFunc.P - 3.0 * fitFunc.delt; - double upper = fitFunc.P + 3.0 * fitFunc.delt; - double newArea = AdaptiveSimpsonIntegrate::integrate(fitFunc, lower, upper); - - // 6. 构建新的结果结构体 - PeakFitResult newResult; - newResult.center = newParams(5); - newResult.amplitude = newParams(0); - newResult.sigma = newParams(1); - newResult.fwhm = newFwhm; - newResult.area = newArea; - newResult.baseline = newParams(4); - newResult.sigmoidH = newParams(2); - newResult.sigmoidW = newParams(3); - - // 7. 删除旧的拟合曲线和本底曲线 - if (curveStartIdx + 1 < _fitCurves.size()) { - _fitCurves[curveStartIdx]->detach(); - _fitCurves[curveStartIdx + 1]->detach(); - delete _fitCurves[curveStartIdx]; - delete _fitCurves[curveStartIdx + 1]; - _fitCurves.removeAt(curveStartIdx + 1); - _fitCurves.removeAt(curveStartIdx); - } - - // 8. 从原始数据中提取该区域的 X 值(用于重新生成曲线) - arma::vec xVec; - QList curves = _plot->GetCurveList(); - if (!curves.isEmpty()) { - QwtPlotCurve* originalCurve = curves.first(); - QVector origX; - for (size_t i = 0; i < originalCurve->dataSize(); ++i) { - origX.push_back(originalCurve->sample(i).x()); - } - QVector roiX; - for (int i = 0; i < origX.size(); ++i) { - if (origX[i] >= originalCurveData.xMin && origX[i] <= originalCurveData.xMax) { - roiX.push_back(origX[i]); - } - } - xVec.set_size(roiX.size()); - for (int i = 0; i < roiX.size(); ++i) { - xVec(i) = roiX[i]; - } - } - - // 9. 生成新的拟合曲线并插入到原位置 - QList newCurves = createFitCurve(newResult, xVec); - for (int i = 0; i < newCurves.size(); ++i) { - _fitCurves.insert(curveStartIdx + i, newCurves[i]); - } - - // 10. 更新框选区域的峰数据标签 - _hoveredRectItem->setPeakData(newResult.center, newResult.fwhm, newResult.area, newResult.baseline); - - // 11. 更新内存中的历史数据 - FitCurveData newCurveData = originalCurveData; // 复制旧数据 - newCurveData.amplitude = newParams(0); - newCurveData.sigma = newParams(1); - newCurveData.sigmoidH = newParams(2); - newCurveData.sigmoidW = newParams(3); - newCurveData.baseline = newParams(4); - newCurveData.center = newParams(5); - newCurveData.fwhm = newFwhm; - newCurveData.area = newArea; - // 替换历史列表中的数据 - _fitHistoryList[historyIdx].curveList.replace(curveIdxInHistory, newCurveData); - - // 12. 自动保存到文件 - saveHistoryToFile(); - - // 13. 重绘图表 - _plot->replot(); - - QMessageBox::information(this, QStringLiteral(u"提示"), QStringLiteral(u"重新拟合成功,历史数据已更新。")); + PeakFitParamsDialog paramDlg(defaultParams, this); + if (paramDlg.exec() != QDialog::Accepted) { + return; } + FitParams userParams = paramDlg.getParams(); + + arma::vec newParams(6); + newParams(0) = userParams.A; + newParams(1) = userParams.delt; + newParams(2) = userParams.H; + newParams(3) = userParams.W; + newParams(4) = userParams.C; + newParams(5) = userParams.P; + + double newFwhm = newParams(1) * 2.355; + AdaptiveSimpsonIntegrate::FitFunction fitFunc; + fitFunc.A = newParams(0); + fitFunc.delt = newParams(1); + fitFunc.H = newParams(2); + fitFunc.W = newParams(3); + fitFunc.C = newParams(4); + fitFunc.P = newParams(5); + + double lower = fitFunc.P - 3.0 * fitFunc.delt; + double upper = fitFunc.P + 3.0 * fitFunc.delt; + double newArea = AdaptiveSimpsonIntegrate::integrate(fitFunc, lower, upper); + + PeakFitResult newResult; + newResult.center = newParams(5); + newResult.amplitude = newParams(0); + newResult.sigma = newParams(1); + newResult.fwhm = newFwhm; + newResult.area = newArea; + newResult.baseline = newParams(4); + newResult.sigmoidH = newParams(2); + newResult.sigmoidW = newParams(3); + + int curveStartIdx = historyIndex * 2; + if (curveStartIdx + 1 < _fitCurves.size()) { + _fitCurves[curveStartIdx]->detach(); + _fitCurves[curveStartIdx + 1]->detach(); + delete _fitCurves[curveStartIdx]; + delete _fitCurves[curveStartIdx + 1]; + _fitCurves.removeAt(curveStartIdx + 1); + _fitCurves.removeAt(curveStartIdx); + } + + arma::vec xVec; + QList curves = _plot->GetCurveList(); + if (!curves.isEmpty()) { + QwtPlotCurve* originalCurve = curves.first(); + QVector origX; + for (size_t i = 0; i < originalCurve->dataSize(); ++i) { + origX.push_back(originalCurve->sample(i).x()); + } + QVector roiX; + for (int i = 0; i < origX.size(); ++i) { + if (origX[i] >= originalCurveData->xMin && origX[i] <= originalCurveData->xMax) { + roiX.push_back(origX[i]); + } + } + xVec.set_size(roiX.size()); + for (int i = 0; i < roiX.size(); ++i) { + xVec(i) = roiX[i]; + } + } + + QList newCurves = createFitCurve(newResult, xVec); + _fitCurves.insert(curveStartIdx, newCurves[0]); + _fitCurves.insert(curveStartIdx + 1, newCurves[1]); + + _hoveredRectItem->setPeakData(newResult.center, newResult.fwhm, newResult.area, newResult.baseline); + + FitCurveData newCurveData = *originalCurveData; + newCurveData.amplitude = newParams(0); + newCurveData.sigma = newParams(1); + newCurveData.sigmoidH = newParams(2); + newCurveData.sigmoidW = newParams(3); + newCurveData.baseline = newParams(4); + newCurveData.center = newParams(5); + newCurveData.fwhm = newFwhm; + newCurveData.area = newArea; + _fitHistoryList[historyIndex].curveList.replace(0, newCurveData); + saveHistoryToFile(); + + for (auto& record : _currentFitRecords) { + if (record.historyIndex == historyIndex) { + record.result = newResult; + record.params = newParams; + break; + } + } + + _plot->replot(); + emit newFitResultAdded(); + QMessageBox::information(this, QStringLiteral(u"提示"), QStringLiteral(u"重新拟合成功,历史数据已更新。")); } -//检测鼠标是否悬停在框选区域上 void EnergyCountPeakFitView::updateHoverState(const QPoint& mousePos) { - // 将鼠标像素坐标转换为 plot 坐标 const QwtScaleMap xMap = _plot->canvasMap(QwtPlot::xBottom); const QwtScaleMap yMap = _plot->canvasMap(QwtPlot::yLeft); const double x = xMap.invTransform(mousePos.x()); @@ -1551,7 +1226,6 @@ void EnergyCountPeakFitView::updateHoverState(const QPoint& mousePos) { PlotRectItem* newHoveredItem = nullptr; - // 倒序遍历(优先检测最上层的区域) for (auto it = _selectionRectItems.rbegin(); it != _selectionRectItems.rend(); ++it) { PlotRectItem* item = *it; if (item->rect().contains(plotPos)) { @@ -1560,104 +1234,204 @@ void EnergyCountPeakFitView::updateHoverState(const QPoint& mousePos) { } } - // 更新悬停状态 if (newHoveredItem != _hoveredRectItem) { - // 取消之前的悬停 if (_hoveredRectItem) { _hoveredRectItem->setHovered(false); } - // 设置新的悬停 _hoveredRectItem = newHoveredItem; if (_hoveredRectItem) { _hoveredRectItem->setHovered(true); } - // 重绘 _plot->replot(); } } void EnergyCountPeakFitView::displaySelectedCurves(const QList &curves, const QList& refs) { - // 先清除之前的拟合曲线和框选区域 - clearFitCurves(); - clearAllSelectionRects(); + clearFitCurves(); + clearAllSelectionRects(); + _displayedHistoryCurves.clear(); - //重建显示引用列表,记录索引 - _displayedHistoryCurves.clear(); + for (int i = 0; i < curves.size(); ++i) { + const auto& curve = curves[i]; + const auto& ref = refs[i]; - // 遍历显示曲线 - for (int i = 0; i < curves.size(); ++i) { - const auto& curve = curves[i]; - const auto& ref = refs[i]; - - // 1. 生成X轴数据 - const int numPoints = 200; - QVector xs, ys, ysTw; - double step = (curve.xMax - curve.xMin) / (numPoints - 1); - - // 2. 构建参数向量 - arma::vec p(6); - p(0) = curve.amplitude; - p(1) = curve.sigma; - p(2) = curve.sigmoidH; - p(3) = curve.sigmoidW; - p(4) = curve.baseline; - p(5) = curve.center; - - // 3. 生成拟合曲线数据 - for (int j = 0; j < numPoints; ++j) { - double x = curve.xMin + j * step; - xs.append(x); - ys.append(PhotonPeakModel(x, p)); - } - - //记录当前曲线和框选的起始索引 - DisplayedCurveRef newRef; - newRef.historyIndex = ref.historyIndex; - newRef.curveIndex = ref.curveIndex; - newRef.curveStartIndex = _fitCurves.size(); - newRef.rectIndex = _selectionRectItems.size(); - - // 4. 创建并添加拟合曲线 - QwtPlotCurve* fitCurve = new QwtPlotCurve(QStringLiteral(u"历史拟合数据")); - fitCurve->setPen(QPen(Qt::blue, 2)); - fitCurve->setSamples(xs, ys); - fitCurve->setItemAttribute(QwtPlotItem::Legend, false); - fitCurve->attach(_plot); - _fitCurves.append(fitCurve); - - // 5. 生成本底曲线数据 - arma::vec xVec(numPoints); - for (int j = 0; j < numPoints; ++j) xVec(j) = xs[j]; - arma::vec yTw = PhotonPeakModelTuowei(xVec, p); - for (int j = 0; j < yTw.size(); ++j) ysTw.append(yTw(j)); - - // 6. 创建并添加本底曲线 - QwtPlotCurve* bgCurve = new QwtPlotCurve(QStringLiteral(u"历史本底数据")); - bgCurve->setPen(QPen(Qt::yellow, 2, Qt::DashLine)); - bgCurve->setSamples(xs, ysTw); - bgCurve->setItemAttribute(QwtPlotItem::Legend, false); - bgCurve->attach(_plot); - _fitCurves.append(bgCurve); - - // 7. 恢复并绘制框选区域 - if (!curve.selectionRect.isNull()) { - PlotRectItem* histRect = new PlotRectItem("HistorySelection"); - histRect->setAxes(QwtPlot::xBottom, QwtPlot::yLeft); - histRect->setRect(curve.selectionRect); - histRect->setBrush(QBrush(Qt::transparent)); - histRect->setPen(QPen(Qt::red, 2, Qt::DashLine)); - histRect->setPeakData(curve.center, curve.fwhm, curve.area, curve.baseline); - - histRect->setSelectionType("history"); - histRect->setSelectionIndex(_displayedHistoryCurves.size()); - - histRect->attach(_plot); - _selectionRectItems.append(histRect); - } - - // 8. 将记录了索引的 newRef 加入列表 - _displayedHistoryCurves.append(newRef); + const int numPoints = 200; + QVector xs, ys, ysTw; + double step = (curve.xMax - curve.xMin) / (numPoints - 1); + arma::vec p(6); + p(0) = curve.amplitude; + p(1) = curve.sigma; + p(2) = curve.sigmoidH; + p(3) = curve.sigmoidW; + p(4) = curve.baseline; + p(5) = curve.center; + for (int j = 0; j < numPoints; ++j) { + double x = curve.xMin + j * step; + xs.append(x); + ys.append(PhotonPeakModel(x, p)); } - _plot->replot(); + + int targetRectIdx = ref.historyIndex; + int targetCurveStartIdx = targetRectIdx * 2; + DisplayedCurveRef newRef; + newRef.historyIndex = ref.historyIndex; + newRef.curveIndex = ref.curveIndex; + newRef.rectIndex = targetRectIdx; + newRef.curveStartIndex = targetCurveStartIdx; + + QwtPlotCurve* fitCurve = new QwtPlotCurve(QStringLiteral(u"历史拟合数据")); + fitCurve->setPen(QPen(Qt::blue, 2)); + fitCurve->setSamples(xs, ys); + fitCurve->setItemAttribute(QwtPlotItem::Legend, false); + fitCurve->attach(_plot); + while (_fitCurves.size() < targetCurveStartIdx) { + _fitCurves.append(nullptr); + } + if (targetCurveStartIdx < _fitCurves.size()) { + _fitCurves[targetCurveStartIdx] = fitCurve; + } else { + _fitCurves.append(fitCurve); + } + + arma::vec xVec(numPoints); + for (int j = 0; j < numPoints; ++j) xVec(j) = xs[j]; + arma::vec yTw = PhotonPeakModelTuowei(xVec, p); + for (int j = 0; j < yTw.size(); ++j) ysTw.append(yTw(j)); + + QwtPlotCurve* bgCurve = new QwtPlotCurve(QStringLiteral(u"历史本底数据")); + bgCurve->setPen(QPen(Qt::yellow, 2, Qt::DashLine)); + bgCurve->setSamples(xs, ysTw); + bgCurve->setItemAttribute(QwtPlotItem::Legend, false); + bgCurve->attach(_plot); + if (targetCurveStartIdx + 1 < _fitCurves.size()) { + _fitCurves[targetCurveStartIdx + 1] = bgCurve; + } else { + _fitCurves.append(bgCurve); + } + + if (!curve.selectionRect.isNull()) { + PlotRectItem* histRect = new PlotRectItem("HistorySelection"); + histRect->setAxes(QwtPlot::xBottom, QwtPlot::yLeft); + histRect->setRect(curve.selectionRect); + histRect->setBrush(QBrush(Qt::transparent)); + histRect->setPen(QPen(Qt::red, 2, Qt::DashLine)); + histRect->setPeakData(curve.center, curve.fwhm, curve.area, curve.baseline); + histRect->setSelectionType("history"); + histRect->setSelectionIndex(targetRectIdx); + histRect->attach(_plot); + while (_selectionRectItems.size() <= targetRectIdx) { + _selectionRectItems.append(nullptr); + } + _selectionRectItems[targetRectIdx] = histRect; + } + _displayedHistoryCurves.append(newRef); + } + + for (auto it = _fitCurves.begin(); it != _fitCurves.end();) { + if (*it == nullptr) { + it = _fitCurves.erase(it); + } else { + ++it; + } + } + for (auto it = _selectionRectItems.begin(); it != _selectionRectItems.end();) { + if (*it == nullptr) { + it = _selectionRectItems.erase(it); + } else { + ++it; + } + } + + _plot->replot(); +} + + +bool EnergyCountPeakFitView::isHistoryCurveDisplayed(int historyIndex, int curveIndex) const +{ + for (const auto& ref : _displayedHistoryCurves) { + if (ref.historyIndex == historyIndex && ref.curveIndex == curveIndex) { + return true; + } + } + return false; +} + +void EnergyCountPeakFitView::onNewFitResultAdded() +{ + if (!_historyDlg) return; + + QTableView* tableView = _historyDlg->findChild("historyTableView"); + QStandardItemModel* model = qobject_cast(tableView ? tableView->model() : nullptr); + if (!model) return; + + QVector> oldRowMap = _historyDlg->property("rowToDataIndices").value>>(); + QSet> checkedCurves; + for (int row = 0; row < model->rowCount(); ++row) { + if (model->item(row, 0)->checkState() == Qt::Checked) { + checkedCurves.insert(oldRowMap[row]); + } + } + + int newHistoryIndex = oldRowMap.size(); + if (newHistoryIndex < _fitHistoryList.size()) { + checkedCurves.insert(qMakePair(newHistoryIndex, 0)); + } + + model->clear(); + QStringList headers; + headers << "序号" << "振幅(A)" << "高斯宽度(delt)" << "Sigmoid幅度(H)" << "Sigmoid宽度(W)" << "基线(C)"; + model->setHorizontalHeaderLabels(headers); + + QVector> newRowMap; + model->blockSignals(true); + int rowNumber = 1; + 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 rowItems; + QStandardItem* checkItem = new QStandardItem(QString::number(rowNumber++)); + checkItem->setCheckable(true); + checkItem->setEditable(false); + checkItem->setCheckState(checkedCurves.contains(qMakePair(historyIdx, curveIdx)) + ? Qt::Checked : Qt::Unchecked); + rowItems.append(checkItem); + 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))); + model->appendRow(rowItems); + newRowMap.append(qMakePair(historyIdx, curveIdx)); + } + } + model->blockSignals(false); + + _historyDlg->setProperty("rowToDataIndices", QVariant::fromValue(newRowMap)); + + QList selectedCurves; + QList selectedRefs; + for (int row = 0; row < model->rowCount(); ++row) { + if (model->item(row, 0)->checkState() == Qt::Checked) { + QPair indices = newRowMap[row]; + selectedCurves.append(_fitHistoryList[indices.first].curveList[indices.second]); + DisplayedCurveRef ref; + ref.historyIndex = indices.first; + ref.curveIndex = indices.second; + selectedRefs.append(ref); + } + } + displaySelectedCurves(selectedCurves, selectedRefs); + + tableView->viewport()->update(); + LOG_DEBUG(QStringLiteral(u"历史对话框已同步刷新")); +} + + +void EnergyCountPeakFitView::onHistoryDlgClosed() +{ + _historyDlg = nullptr; + LOG_DEBUG(QStringLiteral(u"峰拟合结果对话框已关闭")); + } diff --git a/src/EnergyCountPeakFitView/EnergyCountPeakFitView.h b/src/EnergyCountPeakFitView/EnergyCountPeakFitView.h index 272ce0f..d81b3b3 100644 --- a/src/EnergyCountPeakFitView/EnergyCountPeakFitView.h +++ b/src/EnergyCountPeakFitView/EnergyCountPeakFitView.h @@ -6,11 +6,11 @@ #include #include #include -#include // 新增 +#include #include #include #include -#include // 新增 +#include #include "DataCalcProcess/AdaptiveSimpsonIntegrate.h" #include "DataCalcProcess/FindPeaksBySvd.h" @@ -38,9 +38,10 @@ struct FitCurveData { double xMin; // 曲线X范围最小值 double xMax; // 曲线X范围最大值 QRectF selectionRect; // 关联的框选区域 - double fwhm; // 半高宽 double area; // 峰面积 + int selectionIndex; // 框选区域在_selectionRectItems中的索引 + int curveStartIndex; // 拟合曲线在_fitCurves中的起始索引(每条对应2条曲线) }; struct PeakFitHistoryItem { @@ -94,24 +95,27 @@ private: void loadHistoryFromFile(); void saveHistoryToFile(); - void saveCurrentFitToHistory(); // 保存当前拟合结果 - void displayFitFromHistory(const PeakFitHistoryItem& item); // 显示历史拟合曲线 - + int saveCurrentFitToHistory(const QRectF& selectionRect); // 处理鼠标悬停检测 void updateHoverState(const QPoint& mousePos); // 显示选中的曲线 void displaySelectedCurves(const QList& curves, const QList& refs); + //检查指定历史曲线是否当前正在显示 + bool isHistoryCurveDisplayed(int historyIndex, int curveIndex) const; + private slots: void onActionCurveShowSetting(); void onActionPlotConfigure(); void clearAllSelectionRects(); void clearFitCurves(); // 清除所有拟合曲线 - void onActionSaveCurrentFit(); void onActionShowFitHistory(); void onActionDeleteHoveredRect(); // 重新拟合当前悬停的框选区域 void onActionRefitCurrentRect(); + void onNewFitResultAdded(); // 新拟合结果同步刷新槽 + void onHistoryDlgClosed(); // 历史对话框关闭清理槽 + protected: bool eventFilter(QObject* watched, QEvent* event) override; // 事件过滤器 @@ -119,15 +123,16 @@ private: void startSelection(const QPoint& pos); void updateSelection(const QPoint& pos); void finishSelection(); - void addSelectionRect(const QRectF& plotRect); + void addSelectionRect(const QRectF& plotRect,int index); void fadeSelectionRectBorders(); QList performPeakFitting(const QVector& x, const QVector& y /*, const arma::vec* userP0 = nullptr*/); // 根据拟合参数生成曲线 - QwtPlotCurve* createFitCurve(const PeakFitResult& result, double xMin, double xMax, const QString& name); - QList createFitCurve(const PeakFitResult& result, arma::vec xVec); +signals: + void newFitResultAdded(); + private: CustomQwtPlot* _plot = nullptr; QMenu* _menu = nullptr; @@ -146,7 +151,6 @@ private: QString _historyFilePath; - // [NEW] 最近一次拟合结果(用于手动保存) PeakFitResult _lastFitResult; arma::vec _lastFitParams; // 6个拟合参数 (A, delt, H, W, C, P) double _lastXMin = 0.0, _lastXMax = 0.0; @@ -160,6 +164,7 @@ private: // 当前悬停的框选区域 PlotRectItem* _hoveredRectItem = nullptr; QList _displayedHistoryCurves; + QDialog* _historyDlg = nullptr; // 跟踪当前打开的峰拟合结果对话框 }; #endif // ENERGYCOUNTPEAKFITVIEW_H