From 08eaff912200c9c3353a7f2d4234288bd5a1933e Mon Sep 17 00:00:00 2001 From: anxinglong <2910824064@qq.com> Date: Thu, 16 Jul 2026 17:54:53 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=89=8B=E5=8A=A8=E5=AF=BB?= =?UTF-8?q?=E5=B3=B0=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CountRateAnalysisView.cpp | 1 - src/CustomQwtPlot.cpp | 19 +- src/CustomQwtPlot.h | 6 +- src/DataProcessWorkPool.cpp | 221 +++++++++++++++++- src/DataProcessWorkPool.h | 30 ++- .../BatchEnergyScaleDialog.cpp | 2 +- .../FindPeaksResultDialog.cpp | 5 + .../FindPeaksResultDialog.h | 6 +- .../ParticleCountPlotView.cpp | 68 +++++- .../ParticleCountPlotView.h | 5 +- 10 files changed, 340 insertions(+), 23 deletions(-) diff --git a/src/CountRateAnalysisView/CountRateAnalysisView.cpp b/src/CountRateAnalysisView/CountRateAnalysisView.cpp index 3f31803..402e36b 100644 --- a/src/CountRateAnalysisView/CountRateAnalysisView.cpp +++ b/src/CountRateAnalysisView/CountRateAnalysisView.cpp @@ -84,7 +84,6 @@ void CountRateAnalysisView::setData(QVector data) const int nVecSize = nAllS / nInv; QVector vec(nVecSize); // 默认初始化为0 - double x_max = 0; double y_max = 0; for(auto info : data) { diff --git a/src/CustomQwtPlot.cpp b/src/CustomQwtPlot.cpp index 292a8ff..20c45c8 100644 --- a/src/CustomQwtPlot.cpp +++ b/src/CustomQwtPlot.cpp @@ -159,11 +159,28 @@ bool CustomQwtPlotXaxisSelector::end(bool ok) plot()->replot(); } if ( (_max_x - _min_x) > 3 ) { - emit selectionFinished(_min_x, _max_x); + if(_isShowEnergy) + { + emit selectionFinished(_min_x, _max_x); + } + else + { + emit selectionFinishedPeak(_min_x, _max_x); + } } return QwtPlotPicker::end(ok); } +bool CustomQwtPlotXaxisSelector::isShowEnergy() const +{ + return _isShowEnergy; +} + +void CustomQwtPlotXaxisSelector::setIsShowEnergy(bool newIsShowEnergy) +{ + _isShowEnergy = newIsShowEnergy; +} + CustomQwtPlot::CustomQwtPlot(QWidget *parent) : QwtPlot(parent) { diff --git a/src/CustomQwtPlot.h b/src/CustomQwtPlot.h index 1847176..eb317d5 100644 --- a/src/CustomQwtPlot.h +++ b/src/CustomQwtPlot.h @@ -43,9 +43,12 @@ public: double selectedMaxX() const; void clearSelection(); + bool isShowEnergy() const; + void setIsShowEnergy(bool newIsShowEnergy); + signals: void selectionFinished(double min_x, double max_x); - + void selectionFinishedPeak(double min_x, double max_x); protected: virtual void move(const QPoint& pos) override; virtual bool end(bool ok) override; @@ -54,6 +57,7 @@ private: QwtPlotShapeItem* _overlay = nullptr; double _min_x; double _max_x; + bool _isShowEnergy; }; class CustomQwtPlot : public QwtPlot diff --git a/src/DataProcessWorkPool.cpp b/src/DataProcessWorkPool.cpp index 6b43c28..45ce378 100644 --- a/src/DataProcessWorkPool.cpp +++ b/src/DataProcessWorkPool.cpp @@ -20,6 +20,7 @@ #include "DataCalcProcess/FindPeaksBySvd.h" #include "DataCalcProcess/GaussPolyCoe.h" #include "DataCalcProcess/NolinearLeastSquaresCurveFit.h" +#include "DataCalcProcess/AdaptiveSimpsonIntegrate.h" #include "EnergyScaleDataModel.h" #include "DataCalcProcess/CoincidenceSpectrumProcess.h" #include "BackgroundTaskListModel.h" @@ -588,17 +589,50 @@ bool AutoFindPeaksTask::IsValidSetWorkParameters() const bool AutoFindPeaksTask::processTask() { QString result_filename = QDir(this->_result_dir).filePath(QStringLiteral(u"自动寻峰结果.csv")); - std::ofstream out_file(QStrToSysPath(result_filename)); - std::string channel_str = QString(QStringLiteral(u"通道")).toStdString(); - std::string addr_str = QString(QStringLiteral(u"峰位")).toStdString(); - std::string left_addr_str = QString(QStringLiteral(u"左边界")).toStdString(); - std::string lright_addr_str = QString(QStringLiteral(u"右边界")).toStdString(); - std::string width_str = QString(QStringLiteral(u"峰宽")).toStdString(); - std::string height_str = QString(QStringLiteral(u"峰高")).toStdString(); - std::string fwhm_str = QString(QStringLiteral(u"FWHM")).toStdString(); - std::string area_str = QString(QStringLiteral(u"峰面积")).toStdString(); - out_file << channel_str << "," << addr_str << "," << left_addr_str << "," << lright_addr_str - << "," << width_str << "," << height_str << "," << fwhm_str << "," << area_str << "\n"; + const std::string sys_result_path = QStrToSysPath(result_filename); + bool need_write_header = false; + { + // 以只读模式打开文件,判断是否为空 + std::ifstream check_file(sys_result_path); + if (!check_file.is_open()) + { + // 文件不存在,需要写表头 + need_write_header = true; + } + else + { + // 文件存在,判断是否为空文件 + check_file.seekg(0, std::ios::end); + std::streampos file_size = check_file.tellg(); + if (file_size == 0) + { + need_write_header = true; + } + check_file.close(); + } + } + + std::ofstream out_file(QStrToSysPath(result_filename),std::ios::app); + if (!out_file.is_open()) + { + LOG_WARN(QStringLiteral(u"[%1]无法打开寻峰结果文件:%2").arg(this->GetProjectName()).arg(result_filename)); + return false; + } + + // 仅空文件才写入表头 + if (need_write_header) + { + std::string channel_str = QString(QStringLiteral(u"通道")).toStdString(); + std::string addr_str = QString(QStringLiteral(u"峰位")).toStdString(); + std::string left_addr_str = QString(QStringLiteral(u"左边界")).toStdString(); + std::string lright_addr_str = QString(QStringLiteral(u"右边界")).toStdString(); + std::string width_str = QString(QStringLiteral(u"峰宽")).toStdString(); + std::string height_str = QString(QStringLiteral(u"峰高")).toStdString(); + std::string fwhm_str = QString(QStringLiteral(u"FWHM")).toStdString(); + std::string area_str = QString(QStringLiteral(u"峰面积")).toStdString(); + out_file << channel_str << "," << addr_str << "," << left_addr_str << "," << lright_addr_str + << "," << width_str << "," << height_str << "," << fwhm_str << "," << area_str << "\n"; + } QStringList ch_count_data_name = this->_data_files_set.keys(); std::sort(ch_count_data_name.begin(), ch_count_data_name.end(), [](const QString& a, const QString& b) { @@ -1479,4 +1513,167 @@ void RealtimeGvfDataWorker::changeEnergyCountData(QList &dataList, if (hasError) { LOG_WARN(QStringLiteral(u"部分通道的能量计数数据写入失败")); } -} \ No newline at end of file +} +QString ManualFindPeaksTask::GetTaskName() +{ + return QStringLiteral(u"[%1]粒子通道计数谱手动寻峰处理").arg(this->GetProjectName()); + +} + +void ManualFindPeaksTask::SetAnalysisType(AnalysisType analysis_type) +{ + this->_analysis_type = analysis_type; +} + +void ManualFindPeaksTask::SetDataMap(const QMap &data_set) +{ + _dataManual = data_set; +} + +void ManualFindPeaksTask::SetResultDir(const QString &result_dir) +{ + this->_result_dir = result_dir; +} + +void ManualFindPeaksTask::SetFindPeakSetpWinWidth(int step_win_width) +{ + this->_step_win_width = step_win_width; +} + +bool ManualFindPeaksTask::IsValidSetWorkParameters() const +{ + return (!this->_dataManual.isEmpty()) + && (!this->_result_dir.isEmpty()) + && (this->_step_win_width > 0) + && DataProcessTask::IsValidSetWorkParameters(); +} + +bool ManualFindPeaksTask::processTask() +{ + QString result_filename = QDir(this->_result_dir).filePath(QStringLiteral(u"自动寻峰结果.csv")); + const std::string sys_result_path = QStrToSysPath(result_filename); + bool need_write_header = false; + { + // 以只读模式打开文件,判断是否为空 + std::ifstream check_file(sys_result_path); + if (!check_file.is_open()) + { + need_write_header = true; + } + else + { + check_file.seekg(0, std::ios::end); + std::streampos file_size = check_file.tellg(); + if (file_size == 0) + { + need_write_header = true; + } + check_file.close(); + } + } + std::ofstream out_file(sys_result_path,std::ios::app); + + // 仅空文件才写入表头 + if (need_write_header) + { + std::string channel_str = QString(QStringLiteral(u"通道")).toStdString(); + std::string addr_str = QString(QStringLiteral(u"峰位")).toStdString(); + std::string left_addr_str = QString(QStringLiteral(u"左边界")).toStdString(); + std::string lright_addr_str = QString(QStringLiteral(u"右边界")).toStdString(); + std::string width_str = QString(QStringLiteral(u"峰宽")).toStdString(); + std::string height_str = QString(QStringLiteral(u"峰高")).toStdString(); + std::string fwhm_str = QString(QStringLiteral(u"FWHM")).toStdString(); + std::string area_str = QString(QStringLiteral(u"峰面积")).toStdString(); + out_file << channel_str << "," << addr_str << "," << left_addr_str << "," << lright_addr_str + << "," << width_str << "," << height_str << "," << fwhm_str << "," << area_str << "\n"; + } + + // 按通道号排序 + QStringList ch_name_list = this->_dataManual.keys(); + std::sort(ch_name_list.begin(), ch_name_list.end(), [](const QString& a, const QString& b) { + int num_a = ExtractNumberFromString(a); + int num_b = ExtractNumberFromString(b); + return num_a < num_b; + }); + for (const QString& channel_name : ch_name_list) { + const ChannelSelectionData& chData = _dataManual.value(channel_name); + + if (chData.x.size() < 6) { + LOG_WARN(QStringLiteral(u"[%1]%2 框选数据点过少(<6),跳过拟合") + .arg(this->GetProjectName()).arg(channel_name)); + continue; + } + + // 转 arma 格式 + arma::vec armaX(chData.x.size()), armaY(chData.x.size()); + for (size_t i = 0; i < chData.x.size(); ++i) { + armaX(i) = chData.x[i]; + armaY(i) = chData.y[i]; + } + try { + arma::vec p0 = EstimatePhotonPeakModelInitialParams(armaX, armaY); + arma::vec p_fit = NolinearLeastSquaresCurveFit::Lsqcurvefit( + PhotonPeakModel, armaX, armaY, p0 + ); + double amplitude = p_fit(0); // 峰振幅 + double sigma = p_fit(1); // 高斯sigma + double center = p_fit(5); // 峰中心(峰位) + double fwhm = sigma * 2.355; + double baseline = p_fit(4); // 本底 + double peakHeight = amplitude + baseline; // 峰高 = 振幅 + 本底 + + int left_bound = static_cast(std::round(center - 3.0 * sigma)); + int right_bound = static_cast(std::round(center + 3.0 * sigma)); + int peak_width = right_bound - left_bound; + + int peak_pos = static_cast(std::round(center)); + + AdaptiveSimpsonIntegrate::FitFunction fitFunc; + fitFunc.A = p_fit(0); + fitFunc.delt = p_fit(1); + fitFunc.H = p_fit(2); + fitFunc.W = p_fit(3); + fitFunc.C = p_fit(4); + fitFunc.P = p_fit(5); + double lower = fitFunc.P - 3.0 * fitFunc.delt; + double upper = fitFunc.P + 3.0 * fitFunc.delt; + double peak_area = AdaptiveSimpsonIntegrate::integrate(fitFunc, lower, upper); + + // 5. 写入CSV + std::string channel_std = channel_name.toStdString(); + out_file << channel_std << "," + << peak_pos << "," + << left_bound << "," + << right_bound << "," + << peak_width << "," + << static_cast(std::round(peakHeight)) << "," + << static_cast(std::round(fwhm)) << "," + << peak_area << "\n"; + // LOG_INFO(QStringLiteral(u"[%1]%2 手动寻峰完成:峰位=%3, FWHM=%4, 面积=%5") + // .arg(this->GetProjectName()).arg(channel_name) + // .arg(peak_pos).arg(fwhm, 0, 'f', 2).arg(peak_area, 0, 'f', 2)); + + } catch (const std::exception& e) { + LOG_WARN(QStringLiteral(u"[%1]%2 手动寻峰拟合异常:%3") + .arg(this->GetProjectName()).arg(channel_name) + .arg(QString::fromStdString(e.what()))); + continue; + } catch (...) { + LOG_WARN(QStringLiteral(u"[%1]%2 手动寻峰拟合未知异常") + .arg(this->GetProjectName()).arg(channel_name)); + continue; + } + } + out_file.close(); + const QString& project_name = GetProjectName(); + MeasureAnalysisProjectModel* project_model = ProjectList::Instance()->GetProjectModel(project_name); + if (project_model != nullptr) { + project_model->SetAnalysisCustomData( + this->_analysis_type, + QString("AutoFindPeaksResult"), + result_filename + ); + } + LOG_INFO(QStringLiteral(u"[%1]手动框选区域寻峰全部完成.").arg(this->GetProjectName())); + return true; +} diff --git a/src/DataProcessWorkPool.h b/src/DataProcessWorkPool.h index 5aa1275..0aa3607 100644 --- a/src/DataProcessWorkPool.h +++ b/src/DataProcessWorkPool.h @@ -14,6 +14,13 @@ class EnergyScaleDataModel; class MeasureAnalysisProjectModel; +struct ChannelSelectionData { + QString channelName; // 通道名,如"通道1" + QVector x; // 框选范围内的道址 + QVector y; // 对应计数 +}; + + namespace DataProcessWorkPool { class DataProcessTask : public QRunnable @@ -103,7 +110,7 @@ namespace DataProcessWorkPool private: virtual bool processTask() override; }; - + //自动寻峰 class AutoFindPeaksTask : public DataProcessTask { public: @@ -121,6 +128,27 @@ namespace DataProcessWorkPool QString _result_dir; int _step_win_width = 7; }; + //手动寻峰 + class ManualFindPeaksTask : public DataProcessTask + { + public: + QString GetTaskName(); + void SetAnalysisType(AnalysisType analysis_type); + // void SetDataFileList(const QMap& data_files_set); + void SetDataMap(const QMap& data_set); + void SetResultDir(const QString& result_dir); + void SetFindPeakSetpWinWidth(int step_win_width); + virtual bool IsValidSetWorkParameters() const override; + private: + virtual bool processTask() override; + private: + AnalysisType _analysis_type; + // QMap _data_files_set; + QString _result_dir; + QMap _dataManual; + int _step_win_width = 7; + }; + class ChannelEnergyScaleFittingTask : public DataProcessTask { diff --git a/src/ParticleCountPlotView/BatchEnergyScaleDialog.cpp b/src/ParticleCountPlotView/BatchEnergyScaleDialog.cpp index c70decf..62b59ff 100644 --- a/src/ParticleCountPlotView/BatchEnergyScaleDialog.cpp +++ b/src/ParticleCountPlotView/BatchEnergyScaleDialog.cpp @@ -23,7 +23,7 @@ BatchEnergyScaleDialog::BatchEnergyScaleDialog(QWidget* parent) ui->tablew_process_data->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); ui->tablew_process_data->horizontalHeader()->setSectionResizeMode( - ui->tablew_process_data->columnCount() - 1, QHeaderView::ResizeToContents); + ui->tablew_process_data->columnCount() - 1, QHeaderView::ResizeToContents); ui->tablew_process_data->horizontalHeader()->setDefaultAlignment(Qt::AlignLeft | Qt::AlignVCenter); ui->filter_set_energy_combo_box->addItem(QStringLiteral(u"所有设置能量")); diff --git a/src/ParticleCountPlotView/FindPeaksResultDialog.cpp b/src/ParticleCountPlotView/FindPeaksResultDialog.cpp index cec4c04..6a1b259 100644 --- a/src/ParticleCountPlotView/FindPeaksResultDialog.cpp +++ b/src/ParticleCountPlotView/FindPeaksResultDialog.cpp @@ -290,3 +290,8 @@ QVariantMap FindPeaksResultDialog::peakInfo(QTableWidgetItem *item, bool show_pe peak_infos["show_peak_area"] = show_peak_area && show_peak; return peak_infos; } + +QTableWidget *FindPeaksResultDialog::peaks_result_table() const +{ + return _peaks_result_table; +} diff --git a/src/ParticleCountPlotView/FindPeaksResultDialog.h b/src/ParticleCountPlotView/FindPeaksResultDialog.h index b699ce9..8dbb867 100644 --- a/src/ParticleCountPlotView/FindPeaksResultDialog.h +++ b/src/ParticleCountPlotView/FindPeaksResultDialog.h @@ -20,11 +20,13 @@ public: void SetChannelNameList(const QStringList& ch_name_list); void UpdatePeakResult(); QAbstractTableModel *GetPeakResultDataModel() const; - -private: void saveCurrentPeakResult(); QVariantMap peakInfo(QTableWidgetItem* item, bool show_peak, bool show_peak_area); + QTableWidget *peaks_result_table() const; + +private: + signals: void peakInfoChanged(QVariantMap peak_infos); diff --git a/src/ParticleCountPlotView/ParticleCountPlotView.cpp b/src/ParticleCountPlotView/ParticleCountPlotView.cpp index d59b3fc..dbc8d87 100644 --- a/src/ParticleCountPlotView/ParticleCountPlotView.cpp +++ b/src/ParticleCountPlotView/ParticleCountPlotView.cpp @@ -1,6 +1,5 @@ #include "ParticleCountPlotView.h" #include "CustomQwtPlot.h" -#include "DataProcessWorkPool.h" #include "MeasureAnalysisProjectModel.h" #include "csv.h" #include @@ -161,7 +160,7 @@ void ParticleCountPlotView::setupMenu() connect(action_auto_find_peaks, &QAction::triggered, this, &ParticleCountPlotView::onActionAutoFindPeaks); QAction* action_manual_find_peaks = menu_find_peaks->addAction(QStringLiteral(u"手动寻峰")); action_manual_find_peaks->setObjectName("manual_find_peaks"); - connect(action_auto_find_peaks, &QAction::triggered, this, &ParticleCountPlotView::onActionManualFindPeaks); + connect(action_manual_find_peaks, &QAction::triggered, this, &ParticleCountPlotView::onActionManualFindPeaks); QAction* action_set_energy_scale = this->_menu->addAction(QStringLiteral(u"能量刻度")); action_set_energy_scale->setObjectName("energy_scale"); connect(action_set_energy_scale, &QAction::triggered, this, &ParticleCountPlotView::onActionEnergyScale); @@ -205,6 +204,7 @@ void ParticleCountPlotView::setupPlot() _data_selector = new CustomQwtPlotXaxisSelector(_plot->canvas()); _data_selector->setEnabled(false); + connect(_data_selector,&CustomQwtPlotXaxisSelector::selectionFinishedPeak,this,&ParticleCountPlotView::onManualPeakRangeSelected); } void ParticleCountPlotView::setupPeakResultDlg() @@ -316,6 +316,8 @@ void ParticleCountPlotView::updatePlotPeakInfo(const QVariantMap &peak_infos) this->_plot->replot(); } + + void ParticleCountPlotView::onAutoFindPeaksFinished(bool ok, const QString &project_name, const QVariant& data) { Q_UNUSED(project_name); @@ -460,7 +462,65 @@ void ParticleCountPlotView::onActionAutoFindPeaks() void ParticleCountPlotView::onActionManualFindPeaks() { - // _data_selector->setEnabled(true); + if (!_data_selector){ + return; + } + _data_selector->setEnabled(true); + _data_selector->setIsShowEnergy(false); + + // 自动打开寻峰结果表格方便查看新增峰 + if (_find_peaks_result_dlg) + _find_peaks_result_dlg->show(); +} + +void ParticleCountPlotView::onManualPeakRangeSelected(double minX, double maxX) +{ + // 关闭框选工具 + _data_selector->setEnabled(false); + // 获取32个通道的框选数据 + QMap allData = getSelectedRangeData(minX, maxX); + + auto manual_find_peaks_task = new DataProcessWorkPool::ManualFindPeaksTask; + const QString& project_name = GetProjectName(); + manual_find_peaks_task->SetAnalysisType(this->GetAnalyzeType()); + // manual_find_peaks_task->SetDataFileList(this->_data_files_set_ptr); + manual_find_peaks_task->SetFindPeakSetpWinWidth(35); + manual_find_peaks_task->SetResultDir(this->_workspace); + manual_find_peaks_task->SetDataMap(allData); + manual_find_peaks_task->SetFinishedNotifier(this, "onAutoFindPeaksFinished", project_name); + manual_find_peaks_task->StartTask(); +} + +QMap ParticleCountPlotView::getSelectedRangeData(double minX, double maxX) +{ + QMap result; + + // 获取所有曲线 + QList curveList = _plot->GetCurveList(); + + for (QwtPlotCurve* curve : curveList) { + const QString& chName = curve->title().text(); + + ChannelSelectionData chData; + chData.channelName = chName; + + // 取曲线原始样本 + const QwtSeriesData& series = *curve->data(); + size_t total = series.size(); + + for (size_t i = 0; i < total; ++i) { + double xVal = series.sample(i).x(); + double yVal = series.sample(i).y(); + + // 筛选在框选范围内的点(含边界) + if (xVal >= minX && xVal <= maxX) { + chData.x.push_back(xVal); + chData.y.push_back(yVal); + } + } + result.insert(chName, chData); + } + return result; } void ParticleCountPlotView::onActionEnergyScale() @@ -468,6 +528,8 @@ void ParticleCountPlotView::onActionEnergyScale() if (_batch_energy_scale_dlg && _data_selector) { _batch_energy_scale_dlg->show(); _data_selector->setEnabled(true); + _data_selector->setIsShowEnergy(true); + } } diff --git a/src/ParticleCountPlotView/ParticleCountPlotView.h b/src/ParticleCountPlotView/ParticleCountPlotView.h index 8547c50..160ccba 100644 --- a/src/ParticleCountPlotView/ParticleCountPlotView.h +++ b/src/ParticleCountPlotView/ParticleCountPlotView.h @@ -5,6 +5,7 @@ #include #include #include "MeasureAnalysisView.h" +#include "DataProcessWorkPool.h" class QDialog; class QMenu; @@ -15,6 +16,7 @@ class CustomQwtPlotXaxisSelector; class BatchEnergyScaleDialog; class FindPeaksResultDialog; + class ParticleCountPlotView : public MeasureAnalysisView { Q_OBJECT @@ -35,6 +37,8 @@ private: void loadDataFromFile(const QString &data_name, const QString& filename); void loadPeaksResultToTable(QTableWidget* peaks_result_table); void updatePlotPeakInfo(const QVariantMap& peak_infos); + void onManualPeakRangeSelected(double minX, double maxX); + QMap getSelectedRangeData(double minX, double maxX); private slots: void onAutoFindPeaksFinished(bool ok, const QString &project_name, const QVariant& data); @@ -45,7 +49,6 @@ private slots: void onActionManualFindPeaks(); void onActionEnergyScale(); void onActionPlotConfigure(); - signals: void applyEnergyScale();