From 905614ff6f41a1f107522537fc8d852ee8092374 Mon Sep 17 00:00:00 2001 From: anxinglong <2910824064@qq.com> Date: Tue, 7 Apr 2026 18:15:20 +0800 Subject: [PATCH] =?UTF-8?q?=E8=BF=81=E7=A7=BB=E4=BA=8C=E7=BB=B4=E7=AC=A6?= =?UTF-8?q?=E5=90=88=E8=B0=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../TwoDSpectralCompliance.cpp | 248 ++++++++++++++++++ .../TwoDSpectralCompliance.h | 56 ++++ .../TwoDSpectralCompliance.ui | 21 ++ src/MeasureAnalysisTreeView.cpp | 9 + src/MeasureAnalysisView.cpp | 5 +- src/src.pro | 11 +- 6 files changed, 345 insertions(+), 5 deletions(-) create mode 100644 src/2DSpectralCompliance/TwoDSpectralCompliance.cpp create mode 100644 src/2DSpectralCompliance/TwoDSpectralCompliance.h create mode 100644 src/2DSpectralCompliance/TwoDSpectralCompliance.ui diff --git a/src/2DSpectralCompliance/TwoDSpectralCompliance.cpp b/src/2DSpectralCompliance/TwoDSpectralCompliance.cpp new file mode 100644 index 0000000..5c97464 --- /dev/null +++ b/src/2DSpectralCompliance/TwoDSpectralCompliance.cpp @@ -0,0 +1,248 @@ +#include "TwoDSpectralCompliance.h" +#include "ui_TwoDSpectralCompliance.h" +#include "CustomQwtPlot.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// 自定义颜色映射(热力图) +class HeatMapColorMap : public QwtLinearColorMap +{ +public: + HeatMapColorMap() : QwtLinearColorMap(Qt::white, Qt::red) + { + addColorStop(0.25, Qt::blue); + addColorStop(0.5, Qt::green); + addColorStop(0.75, Qt::yellow); + } +}; + +TwoDSpectralCompliance::TwoDSpectralCompliance(QWidget *parent) : + MeasureAnalysisView(parent), + ui(new Ui::TwoDSpectralCompliance) +{ + ui->setupUi(this); + QHBoxLayout* layout = new QHBoxLayout(this); + layout->setContentsMargins(0, 0, 0, 0); + this->_plot = new CustomQwtPlot(this); + layout->addWidget(this->_plot); + setupPlot(); +} + +TwoDSpectralCompliance::~TwoDSpectralCompliance() +{ + delete ui; +} + +void TwoDSpectralCompliance::InitViewWorkspace(const QString &project_name) +{ + // 可预留用于项目初始化 + Q_UNUSED(project_name); +} + +void TwoDSpectralCompliance::SetAnalyzeDataFilename(const QMap &data_files_set) +{ + if (data_files_set.isEmpty()) + return; + + // 约定:data_files_set 的 key 为符合次数(如 "2", "3"...),value 为 CSV 文件路径 + // 我们只处理第一个文件(通常只有一个) + QString csvFile = data_files_set.first().toString(); + if (csvFile.isEmpty()) + return; + + readCsv(csvFile); + generateSurfaceData(); + updateSpectrogram(); +} + +void TwoDSpectralCompliance::setupPlot() +{ + _plot->setCanvasBackground(Qt::white); + QwtPlotCanvas* canvas = qobject_cast(_plot->canvas()); + if (canvas) + canvas->setFrameStyle(QFrame::NoFrame); + + QFont font = this->font(); + font.setBold(false); + QwtText energy_label = QStringLiteral(u"次级粒子能量和 (keV)"); + energy_label.setFont(font); + QwtText count_label = QStringLiteral(u"初级粒子能量 (keV)"); + count_label.setFont(font); + _plot->setAxisTitle(QwtPlot::xBottom, energy_label); + _plot->setAxisTitle(QwtPlot::yLeft, count_label); + + // 自动缩放 + _plot->setAxisAutoScale(QwtPlot::xBottom, true); + _plot->setAxisAutoScale(QwtPlot::yLeft, true); + + _plot->enableAxis(QwtPlot::xBottom); + _plot->enableAxis(QwtPlot::yLeft); + + // 图例 + QwtLegend* legend = new QwtLegend(); + legend->setDefaultItemMode(QwtLegendData::ReadOnly); + _plot->insertLegend(legend, QwtPlot::RightLegend); + + // 启用拖拽缩放 + _plot->SetAxisDragScale(QwtPlot::xBottom, true); + _plot->SetAxisDragScale(QwtPlot::yLeft, true); + + // 创建 spectrogram + _spectrogram = new QwtPlotSpectrogram(); + _spectrogram->setColorMap(new HeatMapColorMap()); + _spectrogram->attach(_plot); + + _plot->setAutoFillBackground(true); + QPalette pal = _plot->palette(); + pal.setColor(QPalette::Window, Qt::white); + _plot->setPalette(pal); +} + +void TwoDSpectralCompliance::readCsv(const QString &filename) +{ + m_rawData.clear(); + QFile file(filename); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + qWarning() << "无法打开文件:" << filename; + return; + } + + QTextStream stream(&file); + QString header = stream.readLine(); // 跳过标题行 + Q_UNUSED(header); + + while (!stream.atEnd()) { + QString line = stream.readLine(); + if (line.isEmpty()) + continue; + + QStringList fields = line.split(','); + if (fields.size() < 5) + continue; + + EventData data; + data.eventId = fields[0].toInt(); + data.board = fields[1].toInt(); + data.channel = fields[2].toInt(); + data.energy = fields[3].toDouble(); + data.timeCounter = fields[4].toULongLong(); + m_rawData.append(data); + } + file.close(); + qDebug() << "读取了" << m_rawData.size() << "条事件记录"; +} + +void TwoDSpectralCompliance::generateSurfaceData() +{ + m_surfaceData.clear(); + if (m_rawData.isEmpty()) + return; + + // 按事件ID分组,每个事件中第一个粒子为初级,其余为次级 + QMap> eventMap; + for (const auto& ev : m_rawData) { + eventMap[ev.eventId].append(ev); + } + + // 使用 QMap 来累加相同坐标的计数(初级能量,次级能量和) + QMap, int> countMap; + + for (auto it = eventMap.begin(); it != eventMap.end(); ++it) { + const auto& events = it.value(); + if (events.isEmpty()) + continue; + + // 第一个事件作为初级粒子 + float primaryEnergy = events[0].energy; + float secondarySum = 0.0f; + for (int i = 1; i < events.size(); ++i) { + secondarySum += events[i].energy; + } + + auto key = qMakePair(primaryEnergy, secondarySum); + countMap[key]++; + } + + // 转换为 SurfacePoint 列表 + for (auto it = countMap.begin(); it != countMap.end(); ++it) { + TwoSurfacePoint point; + point.primaryEnergy = it.key().first; + point.secondaryEnergySum = it.key().second; + point.count = it.value(); + m_surfaceData.append(point); + } + + qDebug() << "生成了" << m_surfaceData.size() << "个唯一坐标点"; +} + +void TwoDSpectralCompliance::updateSpectrogram() +{ + if (m_surfaceData.isEmpty()) { + _spectrogram->setData(new QwtMatrixRasterData()); + _plot->replot(); + return; + } + + // 找出能量范围 + float minPrim = 1e9f, maxPrim = -1e9f; + float minSec = 1e9f, maxSec = -1e9f; + int maxCount = 0; + + for (const auto& pt : m_surfaceData) { + minPrim = std::min(minPrim, pt.primaryEnergy); + maxPrim = std::max(maxPrim, pt.primaryEnergy); + minSec = std::min(minSec, pt.secondaryEnergySum); + maxSec = std::max(maxSec, pt.secondaryEnergySum); + maxCount = std::max(maxCount, pt.count); + } + + // 增加边界 + double primRange = maxPrim - minPrim; + double secRange = maxSec - minSec; + if (primRange < 1e-6) primRange = 1.0; + if (secRange < 1e-6) secRange = 1.0; + double primStart = minPrim - primRange * 0.05; + double primEnd = maxPrim + primRange * 0.05; + double secStart = minSec - secRange * 0.05; + double secEnd = maxSec + secRange * 0.05; + + // 使用不同名称,避免宏冲突 + const int numCols = 200; // X轴方向点数(初级能量) + const int numRows = 200; // Y轴方向点数(次级能量) + + double stepX = (primEnd - primStart) / numCols; + double stepY = (secEnd - secStart) / numRows; + + QVector zValues(numRows * numCols, 0.0); + + for (const auto& pt : m_surfaceData) { + int col = static_cast((pt.primaryEnergy - primStart) / stepX); + int row = static_cast((pt.secondaryEnergySum - secStart) / stepY); + col = qBound(0, col, numCols - 1); + row = qBound(0, row, numRows - 1); + zValues[row * numCols + col] += pt.count; + } + + QwtMatrixRasterData* data = new QwtMatrixRasterData(); + data->setInterval(Qt::XAxis, QwtInterval(primStart, primEnd)); + data->setInterval(Qt::YAxis, QwtInterval(secStart, secEnd)); + data->setInterval(Qt::ZAxis, QwtInterval(0, maxCount)); + data->setValueMatrix(zValues, numCols); + + _spectrogram->setData(data); + _plot->setAxisScale(QwtPlot::xBottom, primStart, primEnd); + _plot->setAxisScale(QwtPlot::yLeft, secStart, secEnd); + _plot->replot(); +} diff --git a/src/2DSpectralCompliance/TwoDSpectralCompliance.h b/src/2DSpectralCompliance/TwoDSpectralCompliance.h new file mode 100644 index 0000000..b8e9bdc --- /dev/null +++ b/src/2DSpectralCompliance/TwoDSpectralCompliance.h @@ -0,0 +1,56 @@ +#ifndef TWODSPECTRALCOMPLIANCE_H +#define TWODSPECTRALCOMPLIANCE_H + +#include +#include +#include + +class CustomQwtPlot; +class QwtPlotSpectrogram; + +namespace Ui { +class TwoDSpectralCompliance; +} + +//// 用于存储三维数据点(初级能量,次级能量和,计数) +struct TwoSurfacePoint { + float primaryEnergy; + float secondaryEnergySum; + int count; +}; + +class TwoDSpectralCompliance : public MeasureAnalysisView +{ + Q_OBJECT + +public: + explicit TwoDSpectralCompliance(QWidget *parent = nullptr); + ~TwoDSpectralCompliance(); + + virtual void InitViewWorkspace(const QString& project_name) override final; + virtual void SetAnalyzeDataFilename(const QMap& data_files_set) override; + +private: + void setupPlot(); + void readCsv(const QString& filename); + void generateSurfaceData(); + void updateSpectrogram(); + +private: + Ui::TwoDSpectralCompliance *ui; + CustomQwtPlot* _plot = nullptr; + QwtPlotSpectrogram* _spectrogram = nullptr; + + // 原始数据 + struct EventData { + int eventId; + int board; + int channel; + double energy; + unsigned long long timeCounter; + }; + QVector m_rawData; // 从 CSV 读取的原始数据 + QVector m_surfaceData; // 生成的曲面点(每个事件一个点,计数累加) +}; + +#endif // TWODSPECTRALCOMPLIANCE_H diff --git a/src/2DSpectralCompliance/TwoDSpectralCompliance.ui b/src/2DSpectralCompliance/TwoDSpectralCompliance.ui new file mode 100644 index 0000000..dd761c5 --- /dev/null +++ b/src/2DSpectralCompliance/TwoDSpectralCompliance.ui @@ -0,0 +1,21 @@ + + + + + TwoDSpectralCompliance + + + + 0 + 0 + 400 + 300 + + + + Form + + + + + diff --git a/src/MeasureAnalysisTreeView.cpp b/src/MeasureAnalysisTreeView.cpp index a1c9edb..6d008d8 100644 --- a/src/MeasureAnalysisTreeView.cpp +++ b/src/MeasureAnalysisTreeView.cpp @@ -251,6 +251,15 @@ void MeasureAnalysisTreeView::onNodeDoubleClicked(const QModelIndex& index) } } } break; + case AnalysisType::CoincidenceParticleEnergySpectrum2DView: { + MeasureAnalysisProjectModel* project_model = _model->GetProjectModel(project_name); + if (project_model) { + auto file_name_list = project_model->GetTimeWinConformEnergyDataFilenameList(project_model->GetConformTimeWin()); + for (auto it = file_name_list.constBegin(); it!=file_name_list.constEnd(); ++it) { + data_files_set[QString::number(it.key())] = it.value(); + } + } + } break; default: break; } diff --git a/src/MeasureAnalysisView.cpp b/src/MeasureAnalysisView.cpp index d0b5373..b92294d 100644 --- a/src/MeasureAnalysisView.cpp +++ b/src/MeasureAnalysisView.cpp @@ -8,6 +8,7 @@ #include "ParticleCountPlotView.h" #include "ParticleInjectTimeAnalysisView.h" #include "ParticleTimeDifferenceView.h" +#include "TwoDSpectralCompliance.h" #include MeasureAnalysisView* MeasureAnalysisView::NewAnalyzeView(AnalysisType view_type) @@ -63,8 +64,8 @@ MeasureAnalysisView* MeasureAnalysisView::NewAnalyzeView(AnalysisType view_type) new_view->setDeleteOnClose(false); } break; case AnalysisType::CoincidenceParticleEnergySpectrum2DView: { - // new_view = new MeasureAnalysisDataTableView; - // new_view->setDeleteOnClose(false); + new_view = new TwoDSpectralCompliance; + new_view->setDeleteOnClose(false); } break; case AnalysisType::CoincidenceParticleEnergySpectrum3DView: { new_view = new ConformityAnalysis; diff --git a/src/src.pro b/src/src.pro index 0aeaa9f..f14caec 100644 --- a/src/src.pro +++ b/src/src.pro @@ -43,8 +43,8 @@ INCLUDEPATH += \ $${PWD}/ParticleTimeDifferenceView \ $${PWD}/MeasureAnalysisHistoryForm \ $${PWD}/MeasureDeviceParamsConfigView \ - $${PWD}/DeviceParameterConfig - + $${PWD}/DeviceParameterConfig \ + $${PWD}/2DSpectralCompliance DEPENDPATH += \ $${PWD}/BusyIndicator \ @@ -59,10 +59,13 @@ DEPENDPATH += \ $${PWD}/ParticleTimeDifferenceView \ $${PWD}/MeasureAnalysisHistoryForm \ $${PWD}/MeasureDeviceParamsConfigView \ - $${PWD}/DeviceParameterConfig + $${PWD}/DeviceParameterConfig \ + $${PWD}/2DSpectralCompliance + SOURCES += \ + 2DSpectralCompliance/TwoDSpectralCompliance.cpp \ AboutDlg.cpp \ BusyIndicator/BusyIndicator.cpp \ CountRateAnalysisView/CountRateAnalysisView.cpp \ @@ -103,6 +106,7 @@ SOURCES += \ main.cpp HEADERS += \ + 2DSpectralCompliance/TwoDSpectralCompliance.h \ AboutDlg.h \ AnalysisTypeDefine.h \ BusyIndicator/BusyIndicator.h \ @@ -147,6 +151,7 @@ HEADERS += \ FORMS += \ + 2DSpectralCompliance/TwoDSpectralCompliance.ui \ AboutDlg.ui \ CountRateAnalysisView/CountRateAnalysisView.ui \ EnergyScaleForm.ui \