迁移二维符合谱

This commit is contained in:
anxinglong 2026-04-07 18:15:20 +08:00
parent 829e322b7c
commit 905614ff6f
6 changed files with 345 additions and 5 deletions

View File

@ -0,0 +1,248 @@
#include "TwoDSpectralCompliance.h"
#include "ui_TwoDSpectralCompliance.h"
#include "CustomQwtPlot.h"
#include <QwtPlotCanvas>
#include <QwtText>
#include <QwtLegend>
#include <QwtPlotSpectrogram>
#include <QwtLinearColorMap>
#include <QwtMatrixRasterData>
#include <QHBoxLayout>
#include <QFile>
#include <QTextStream>
#include <QDebug>
#include <cmath>
#include <QwtInterval>
#include <qwt_matrix_raster_data.h>
#include <algorithm>
// 自定义颜色映射(热力图)
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<QString, QVariant> &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<QwtPlotCanvas*>(_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<int, QVector<EventData>> eventMap;
for (const auto& ev : m_rawData) {
eventMap[ev.eventId].append(ev);
}
// 使用 QMap 来累加相同坐标的计数(初级能量,次级能量和)
QMap<QPair<float, float>, 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<double> zValues(numRows * numCols, 0.0);
for (const auto& pt : m_surfaceData) {
int col = static_cast<int>((pt.primaryEnergy - primStart) / stepX);
int row = static_cast<int>((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();
}

View File

@ -0,0 +1,56 @@
#ifndef TWODSPECTRALCOMPLIANCE_H
#define TWODSPECTRALCOMPLIANCE_H
#include <QWidget>
#include <MeasureAnalysisView.h>
#include <QVector>
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<QString, QVariant>& 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<EventData> m_rawData; // 从 CSV 读取的原始数据
QVector<TwoSurfacePoint> m_surfaceData; // 生成的曲面点(每个事件一个点,计数累加)
};
#endif // TWODSPECTRALCOMPLIANCE_H

View File

@ -0,0 +1,21 @@
<ui version="4.0">
<author/>
<comment/>
<exportmacro/>
<class>TwoDSpectralCompliance</class>
<widget name="TwoDSpectralCompliance" class="QWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
</widget>
<pixmapfunction/>
<connections/>
</ui>

View File

@ -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;
}

View File

@ -8,6 +8,7 @@
#include "ParticleCountPlotView.h"
#include "ParticleInjectTimeAnalysisView.h"
#include "ParticleTimeDifferenceView.h"
#include "TwoDSpectralCompliance.h"
#include <QMap>
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;

View File

@ -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 \