新增符合能谱视图,新增2维视图显示左侧悬浮窗口
This commit is contained in:
parent
b209ba9737
commit
65e7a5787c
|
|
@ -8,6 +8,7 @@
|
|||
#include <QwtLinearColorMap>
|
||||
#include <QwtMatrixRasterData>
|
||||
#include <QHBoxLayout>
|
||||
#include <QVBoxLayout>
|
||||
#include <QFile>
|
||||
#include <QTextStream>
|
||||
#include <QDebug>
|
||||
|
|
@ -15,16 +16,21 @@
|
|||
#include <QwtInterval>
|
||||
#include <qwt_matrix_raster_data.h>
|
||||
#include <algorithm>
|
||||
|
||||
// 自定义颜色映射(热力图)
|
||||
#include <QFormLayout>
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
#include <QLineEdit>
|
||||
#include <QMouseEvent>
|
||||
#include <QSet>
|
||||
class HeatMapColorMap : public QwtLinearColorMap
|
||||
{
|
||||
public:
|
||||
HeatMapColorMap() : QwtLinearColorMap(Qt::white, Qt::red)
|
||||
HeatMapColorMap() : QwtLinearColorMap(Qt::blue, Qt::red)
|
||||
{
|
||||
addColorStop(0.25, Qt::blue);
|
||||
addColorStop(0.25, Qt::cyan);
|
||||
addColorStop(0.5, Qt::green);
|
||||
addColorStop(0.75, Qt::yellow);
|
||||
addColorStop(0.90, Qt::darkRed);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -38,6 +44,7 @@ TwoDSpectralCompliance::TwoDSpectralCompliance(QWidget *parent) :
|
|||
this->_plot = new CustomQwtPlot(this);
|
||||
layout->addWidget(this->_plot);
|
||||
setupPlot();
|
||||
createFloatingInfoWidget();
|
||||
}
|
||||
|
||||
TwoDSpectralCompliance::~TwoDSpectralCompliance()
|
||||
|
|
@ -47,7 +54,6 @@ TwoDSpectralCompliance::~TwoDSpectralCompliance()
|
|||
|
||||
void TwoDSpectralCompliance::InitViewWorkspace(const QString &project_name)
|
||||
{
|
||||
// 可预留用于项目初始化
|
||||
Q_UNUSED(project_name);
|
||||
}
|
||||
|
||||
|
|
@ -55,9 +61,6 @@ void TwoDSpectralCompliance::SetAnalyzeDataFilename(const QMap<QString, QVariant
|
|||
{
|
||||
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;
|
||||
|
|
@ -67,6 +70,7 @@ void TwoDSpectralCompliance::SetAnalyzeDataFilename(const QMap<QString, QVariant
|
|||
updateSpectrogram();
|
||||
}
|
||||
|
||||
|
||||
void TwoDSpectralCompliance::setupPlot()
|
||||
{
|
||||
_plot->setCanvasBackground(Qt::white);
|
||||
|
|
@ -76,38 +80,29 @@ void TwoDSpectralCompliance::setupPlot()
|
|||
|
||||
QFont font = this->font();
|
||||
font.setBold(false);
|
||||
QwtText energy_label = QStringLiteral(u"次级粒子能量和 (keV)");
|
||||
QwtText energy_label = QStringLiteral(u"初级粒子能量");
|
||||
energy_label.setFont(font);
|
||||
QwtText count_label = QStringLiteral(u"初级粒子能量 (keV)");
|
||||
QwtText count_label = QStringLiteral(u"次级粒子能量");
|
||||
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)
|
||||
|
|
@ -115,12 +110,12 @@ void TwoDSpectralCompliance::readCsv(const QString &filename)
|
|||
m_rawData.clear();
|
||||
QFile file(filename);
|
||||
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
qWarning() << "无法打开文件:" << filename;
|
||||
qWarning() << QStringLiteral(u"无法打开文件:") << filename;
|
||||
return;
|
||||
}
|
||||
|
||||
QTextStream stream(&file);
|
||||
QString header = stream.readLine(); // 跳过标题行
|
||||
QString header = stream.readLine();
|
||||
Q_UNUSED(header);
|
||||
|
||||
while (!stream.atEnd()) {
|
||||
|
|
@ -141,7 +136,6 @@ void TwoDSpectralCompliance::readCsv(const QString &filename)
|
|||
m_rawData.append(data);
|
||||
}
|
||||
file.close();
|
||||
qDebug() << "读取了" << m_rawData.size() << "条事件记录";
|
||||
}
|
||||
|
||||
void TwoDSpectralCompliance::generateSurfaceData()
|
||||
|
|
@ -150,13 +144,11 @@ void TwoDSpectralCompliance::generateSurfaceData()
|
|||
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) {
|
||||
|
|
@ -164,7 +156,6 @@ void TwoDSpectralCompliance::generateSurfaceData()
|
|||
if (events.isEmpty())
|
||||
continue;
|
||||
|
||||
// 第一个事件作为初级粒子
|
||||
float primaryEnergy = events[0].energy;
|
||||
float secondarySum = 0.0f;
|
||||
for (int i = 1; i < events.size(); ++i) {
|
||||
|
|
@ -175,7 +166,6 @@ void TwoDSpectralCompliance::generateSurfaceData()
|
|||
countMap[key]++;
|
||||
}
|
||||
|
||||
// 转换为 SurfacePoint 列表
|
||||
for (auto it = countMap.begin(); it != countMap.end(); ++it) {
|
||||
TwoSurfacePoint point;
|
||||
point.primaryEnergy = it.key().first;
|
||||
|
|
@ -184,7 +174,6 @@ void TwoDSpectralCompliance::generateSurfaceData()
|
|||
m_surfaceData.append(point);
|
||||
}
|
||||
|
||||
qDebug() << "生成了" << m_surfaceData.size() << "个唯一坐标点";
|
||||
}
|
||||
|
||||
void TwoDSpectralCompliance::updateSpectrogram()
|
||||
|
|
@ -195,7 +184,6 @@ void TwoDSpectralCompliance::updateSpectrogram()
|
|||
return;
|
||||
}
|
||||
|
||||
// 找出能量范围
|
||||
float minPrim = 1e9f, maxPrim = -1e9f;
|
||||
float minSec = 1e9f, maxSec = -1e9f;
|
||||
int maxCount = 0;
|
||||
|
|
@ -208,7 +196,6 @@ void TwoDSpectralCompliance::updateSpectrogram()
|
|||
maxCount = std::max(maxCount, pt.count);
|
||||
}
|
||||
|
||||
// 增加边界
|
||||
double primRange = maxPrim - minPrim;
|
||||
double secRange = maxSec - minSec;
|
||||
if (primRange < 1e-6) primRange = 1.0;
|
||||
|
|
@ -218,9 +205,8 @@ void TwoDSpectralCompliance::updateSpectrogram()
|
|||
double secStart = minSec - secRange * 0.05;
|
||||
double secEnd = maxSec + secRange * 0.05;
|
||||
|
||||
// 使用不同名称,避免宏冲突
|
||||
const int numCols = 200; // X轴方向点数(初级能量)
|
||||
const int numRows = 200; // Y轴方向点数(次级能量)
|
||||
const int numCols = 200;
|
||||
const int numRows = 200;
|
||||
|
||||
double stepX = (primEnd - primStart) / numCols;
|
||||
double stepY = (secEnd - secStart) / numRows;
|
||||
|
|
@ -246,3 +232,203 @@ void TwoDSpectralCompliance::updateSpectrogram()
|
|||
_plot->setAxisScale(QwtPlot::yLeft, secStart, secEnd);
|
||||
_plot->replot();
|
||||
}
|
||||
void TwoDSpectralCompliance::createFloatingInfoWidget()
|
||||
{
|
||||
// 悬浮窗口
|
||||
m_floatingWidget = new QWidget(this);
|
||||
m_floatingWidget->setObjectName("FloatingInfoWidget");
|
||||
m_floatingWidget->setStyleSheet(
|
||||
"QWidget#FloatingInfoWidget {"
|
||||
" background-color: rgba(240, 240, 240, 240);"
|
||||
" border: 1px solid gray;"
|
||||
" border-radius: 5px;"
|
||||
"}"
|
||||
);
|
||||
m_floatingWidget->setFixedSize(320, 220);
|
||||
m_floatingWidget->setAttribute(Qt::WA_TranslucentBackground, false);
|
||||
|
||||
QVBoxLayout* mainLayout = new QVBoxLayout(m_floatingWidget);
|
||||
mainLayout->setSpacing(5);
|
||||
mainLayout->setContentsMargins(8, 8, 8, 8);
|
||||
|
||||
// 标题栏(可拖动,并包含隐藏按钮)
|
||||
QWidget* titleBar = new QWidget();
|
||||
QHBoxLayout* titleLayout = new QHBoxLayout(titleBar);
|
||||
titleLayout->setContentsMargins(0, 0, 0, 0);
|
||||
QLabel* titleLabel = new QLabel(QStringLiteral(u"数据统计信息"));
|
||||
titleLabel->setStyleSheet("font-weight: bold;");
|
||||
titleLayout->addWidget(titleLabel);
|
||||
titleLayout->addStretch();
|
||||
QPushButton* hideBtn = new QPushButton(QStringLiteral("−"));
|
||||
hideBtn->setFixedSize(20, 20);
|
||||
hideBtn->setToolTip(QStringLiteral(u"隐藏窗口"));
|
||||
hideBtn->setStyleSheet("border: none; font-weight: bold;");
|
||||
titleLayout->addWidget(hideBtn);
|
||||
mainLayout->addWidget(titleBar);
|
||||
|
||||
QWidget* contentWidget = new QWidget();
|
||||
QFormLayout* formLayout = new QFormLayout(contentWidget);
|
||||
formLayout->setSpacing(8);
|
||||
formLayout->setLabelAlignment(Qt::AlignRight);
|
||||
|
||||
m_totalEventsEdit = new QLineEdit;
|
||||
m_totalEventsEdit->setReadOnly(true);
|
||||
m_totalEventsEdit->setFixedWidth(130);
|
||||
|
||||
m_totalPointsEdit = new QLineEdit;
|
||||
m_totalPointsEdit->setReadOnly(true);
|
||||
m_totalPointsEdit->setFixedWidth(130);
|
||||
m_maxCountEdit = new QLineEdit;
|
||||
m_maxCountEdit->setReadOnly(true);
|
||||
m_maxCountEdit->setFixedWidth(130);
|
||||
|
||||
m_primRangeEdit = new QLineEdit;
|
||||
m_primRangeEdit->setReadOnly(true);
|
||||
m_primRangeEdit->setFixedWidth(130);
|
||||
|
||||
m_secRangeEdit = new QLineEdit;
|
||||
m_secRangeEdit->setReadOnly(true);
|
||||
m_secRangeEdit->setFixedWidth(130);
|
||||
|
||||
formLayout->addRow(QStringLiteral(u"总事件数:"), m_totalEventsEdit);
|
||||
formLayout->addRow(QStringLiteral(u"有效数据点:"), m_totalPointsEdit);
|
||||
formLayout->addRow(QStringLiteral(u"最大计数:"), m_maxCountEdit);
|
||||
formLayout->addRow(QStringLiteral(u"初级能量范围:"), m_primRangeEdit);
|
||||
formLayout->addRow(QStringLiteral(u"次级能量和范围:"), m_secRangeEdit);
|
||||
|
||||
mainLayout->addWidget(contentWidget);
|
||||
mainLayout->addStretch();
|
||||
|
||||
connect(hideBtn, &QPushButton::clicked, this, &TwoDSpectralCompliance::hideFloatingWidget);
|
||||
|
||||
titleBar->setCursor(Qt::SizeAllCursor);
|
||||
m_floatingWidget->installEventFilter(this);
|
||||
|
||||
m_showButton = new QPushButton(this);
|
||||
m_showButton->setFixedSize(28, 28);
|
||||
m_showButton->setToolTip(QStringLiteral(u"显示信息窗口"));
|
||||
m_showButton->setStyleSheet(
|
||||
"QPushButton {"
|
||||
" background-color: rgba(200,200,200,200);"
|
||||
" border: 1px solid #888;"
|
||||
" border-radius: 14px;"
|
||||
" font-weight: bold;"
|
||||
"}"
|
||||
"QPushButton:hover { background-color: rgba(150,150,150,200); }"
|
||||
);
|
||||
m_showButton->setText("⊕");
|
||||
connect(m_showButton, &QPushButton::clicked, this, &TwoDSpectralCompliance::showFloatingWidget);
|
||||
|
||||
m_floatingWidget->move(10, 10);
|
||||
updateShowButtonPosition();
|
||||
m_floatingWidget->hide();
|
||||
m_showButton->show();
|
||||
|
||||
// m_floatingWidget->raise();
|
||||
updateInfoContent();
|
||||
}
|
||||
|
||||
void TwoDSpectralCompliance::hideFloatingWidget()
|
||||
{
|
||||
m_floatingWidget->hide();
|
||||
m_showButton->show();
|
||||
updateShowButtonPosition();
|
||||
}
|
||||
|
||||
void TwoDSpectralCompliance::showFloatingWidget()
|
||||
{
|
||||
m_floatingWidget->show();
|
||||
m_floatingWidget->raise();
|
||||
m_showButton->hide();
|
||||
updateInfoContent(); // 刷新内容
|
||||
}
|
||||
|
||||
void TwoDSpectralCompliance::updateShowButtonPosition()
|
||||
{
|
||||
if (m_showButton && m_showButton->isVisible()) {
|
||||
m_showButton->move(10, 10);
|
||||
}
|
||||
}
|
||||
|
||||
void TwoDSpectralCompliance::updateInfoContent()
|
||||
{
|
||||
if (!m_totalEventsEdit)
|
||||
return;
|
||||
|
||||
if (m_surfaceData.isEmpty()) {
|
||||
m_totalEventsEdit->setText(QStringLiteral(u"无数据"));
|
||||
m_totalPointsEdit->setText(QStringLiteral(u"无数据"));
|
||||
m_maxCountEdit->setText(QStringLiteral(u"无数据"));
|
||||
m_primRangeEdit->setText(QStringLiteral(u"无数据"));
|
||||
m_secRangeEdit->setText(QStringLiteral(u"无数据"));
|
||||
return;
|
||||
}
|
||||
|
||||
int totalPoints = m_surfaceData.size();
|
||||
int maxCount = 0;
|
||||
float minPrim = 1e9f, maxPrim = -1e9f;
|
||||
float minSec = 1e9f, maxSec = -1e9f;
|
||||
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);
|
||||
}
|
||||
|
||||
QSet<int> eventIds;
|
||||
for (const auto& ev : m_rawData) {
|
||||
eventIds.insert(ev.eventId);
|
||||
}
|
||||
int totalEvents = eventIds.size();
|
||||
|
||||
m_totalEventsEdit->setText(QString::number(totalEvents));
|
||||
m_totalPointsEdit->setText(QString::number(totalPoints));
|
||||
m_maxCountEdit->setText(QString::number(maxCount));
|
||||
m_primRangeEdit->setText(QString("[%1, %2]").arg(minPrim, 0, 'f', 2).arg(maxPrim, 0, 'f', 2));
|
||||
m_secRangeEdit->setText(QString("[%1, %2]").arg(minSec, 0, 'f', 2).arg(maxSec, 0, 'f', 2));
|
||||
}
|
||||
|
||||
void TwoDSpectralCompliance::resizeEvent(QResizeEvent *event)
|
||||
{
|
||||
QWidget::resizeEvent(event);
|
||||
updateShowButtonPosition();
|
||||
if (m_floatingWidget && m_floatingWidget->isVisible()) {
|
||||
// 限制悬浮窗口不超出父窗口边界
|
||||
QRect rect = m_floatingWidget->geometry();
|
||||
int newX = qBound(0, rect.x(), width() - rect.width());
|
||||
int newY = qBound(0, rect.y(), height() - rect.height());
|
||||
if (newX != rect.x() || newY != rect.y())
|
||||
m_floatingWidget->move(newX, newY);
|
||||
m_floatingWidget->raise();
|
||||
}
|
||||
}
|
||||
|
||||
bool TwoDSpectralCompliance::eventFilter(QObject *obj, QEvent *event)
|
||||
{
|
||||
if (obj == m_floatingWidget) {
|
||||
if (event->type() == QEvent::MouseButtonPress) {
|
||||
QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
|
||||
if (mouseEvent->button() == Qt::LeftButton) {
|
||||
m_dragging = true;
|
||||
m_dragPosition = mouseEvent->globalPos() - m_floatingWidget->frameGeometry().topLeft();
|
||||
event->accept();
|
||||
return true;
|
||||
}
|
||||
} else if (event->type() == QEvent::MouseMove) {
|
||||
if (m_dragging) {
|
||||
QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
|
||||
m_floatingWidget->move(mouseEvent->globalPos() - m_dragPosition);
|
||||
event->accept();
|
||||
return true;
|
||||
}
|
||||
} else if (event->type() == QEvent::MouseButtonRelease) {
|
||||
if (m_dragging) {
|
||||
m_dragging = false;
|
||||
event->accept();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return QWidget::eventFilter(obj, event);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,15 +4,17 @@
|
|||
#include <QWidget>
|
||||
#include <MeasureAnalysisView.h>
|
||||
#include <QVector>
|
||||
#include <QPoint>
|
||||
|
||||
class CustomQwtPlot;
|
||||
class QwtPlotSpectrogram;
|
||||
|
||||
class QPushButton;
|
||||
class QLineEdit;
|
||||
class ScatterPlotItem;
|
||||
namespace Ui {
|
||||
class TwoDSpectralCompliance;
|
||||
}
|
||||
|
||||
//// 用于存储三维数据点(初级能量,次级能量和,计数)
|
||||
struct TwoSurfacePoint {
|
||||
float primaryEnergy;
|
||||
float secondaryEnergySum;
|
||||
|
|
@ -29,6 +31,9 @@ public:
|
|||
|
||||
virtual void InitViewWorkspace(const QString& project_name) override final;
|
||||
virtual void SetAnalyzeDataFilename(const QMap<QString, QVariant>& data_files_set) override;
|
||||
protected:
|
||||
void resizeEvent(QResizeEvent *event) override;
|
||||
bool eventFilter(QObject *obj, QEvent *event) override;
|
||||
|
||||
private:
|
||||
void setupPlot();
|
||||
|
|
@ -36,12 +41,23 @@ private:
|
|||
void generateSurfaceData();
|
||||
void updateSpectrogram();
|
||||
|
||||
void createFloatingInfoWidget();
|
||||
void updateInfoContent();
|
||||
void updateToggleButtonText();
|
||||
void updateFloatingWidgetPosition();
|
||||
void updateToggleButtonPosition();
|
||||
void updateShowButtonPosition();
|
||||
|
||||
private slots:
|
||||
void showFloatingWidget();
|
||||
void hideFloatingWidget();
|
||||
|
||||
|
||||
private:
|
||||
Ui::TwoDSpectralCompliance *ui;
|
||||
CustomQwtPlot* _plot = nullptr;
|
||||
QwtPlotSpectrogram* _spectrogram = nullptr;
|
||||
|
||||
// 原始数据
|
||||
struct EventData {
|
||||
int eventId;
|
||||
int board;
|
||||
|
|
@ -49,8 +65,21 @@ private:
|
|||
double energy;
|
||||
unsigned long long timeCounter;
|
||||
};
|
||||
QVector<EventData> m_rawData; // 从 CSV 读取的原始数据
|
||||
QVector<TwoSurfacePoint> m_surfaceData; // 生成的曲面点(每个事件一个点,计数累加)
|
||||
QVector<EventData> m_rawData;
|
||||
QVector<TwoSurfacePoint> m_surfaceData;
|
||||
|
||||
QWidget* m_floatingWidget = nullptr;
|
||||
QPushButton* m_toggleButton = nullptr;
|
||||
QPushButton* m_showButton = nullptr;
|
||||
|
||||
QLineEdit* m_totalEventsEdit = nullptr;
|
||||
QLineEdit* m_totalPointsEdit = nullptr;
|
||||
QLineEdit* m_maxCountEdit = nullptr;
|
||||
QLineEdit* m_primRangeEdit = nullptr;
|
||||
QLineEdit* m_secRangeEdit = nullptr;
|
||||
|
||||
QPoint m_dragPosition;
|
||||
bool m_dragging = false;
|
||||
};
|
||||
|
||||
#endif // TWODSPECTRALCOMPLIANCE_H
|
||||
|
|
|
|||
216
src/ConformToTheEnergySpectrum/ConformToTheEnergySpectrum.cpp
Normal file
216
src/ConformToTheEnergySpectrum/ConformToTheEnergySpectrum.cpp
Normal file
|
|
@ -0,0 +1,216 @@
|
|||
#include "ConformToTheEnergySpectrum.h"
|
||||
#include <QVBoxLayout>
|
||||
#include <QwtPlotCurve>
|
||||
#include <QwtPlotCanvas>
|
||||
#include <QwtText>
|
||||
#include "CustomQwtPlot.h"
|
||||
#include <GlobalDefine.h>
|
||||
#include "csv.h"
|
||||
#include <QFileInfo>
|
||||
#include <QThread>
|
||||
#include "BusyIndicator.h"
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <QPen>
|
||||
struct SpectrumData
|
||||
{
|
||||
int board_id;
|
||||
int channel_id;
|
||||
double energy;
|
||||
uint64_t timestamp;
|
||||
};
|
||||
|
||||
struct CoincidenceEvent
|
||||
{
|
||||
std::vector<SpectrumData> events;
|
||||
};
|
||||
|
||||
ConformToTheEnergySpectrum::ConformToTheEnergySpectrum(QWidget *parent) :
|
||||
MeasureAnalysisView(parent)
|
||||
{
|
||||
this->setViewType(PlotFrame);
|
||||
|
||||
_plot = new CustomQwtPlot();
|
||||
QVBoxLayout* layout = new QVBoxLayout(this);
|
||||
layout->addWidget(_plot);
|
||||
|
||||
_plot->setCanvasBackground(Qt::white);
|
||||
QwtPlotCanvas* canvas = qobject_cast<QwtPlotCanvas*>(_plot->canvas());
|
||||
canvas->setFrameStyle(QFrame::NoFrame);
|
||||
|
||||
QFont font = this->font();
|
||||
font.setBold(false);
|
||||
QwtText x_label = QStringLiteral(u"能量(KeV)");
|
||||
QwtText y_label = QStringLiteral(u"符合事件次数");
|
||||
x_label.setFont(font);
|
||||
y_label.setFont(font);
|
||||
_plot->setAxisTitle(QwtPlot::xBottom, x_label);
|
||||
_plot->setAxisTitle(QwtPlot::yLeft, y_label);
|
||||
|
||||
_plot->setAxisAutoScale(QwtPlot::xBottom, true);
|
||||
_plot->setAxisAutoScale(QwtPlot::yLeft, true);
|
||||
_plot->enableAxis(QwtPlot::xBottom);
|
||||
_plot->enableAxis(QwtPlot::yLeft);
|
||||
_plot->SetAxisDragScale(QwtPlot::xBottom, true);
|
||||
|
||||
_curve = new QwtPlotCurve();
|
||||
_curve->setStyle(QwtPlotCurve::Lines);
|
||||
_curve->setPen(QPen(QColor(23, 229, 238), 2));
|
||||
_plot->AddCurve(_curve);
|
||||
|
||||
_busy_indicator = new BusyIndicator(this);
|
||||
}
|
||||
|
||||
ConformToTheEnergySpectrum::~ConformToTheEnergySpectrum()
|
||||
{
|
||||
}
|
||||
|
||||
void ConformToTheEnergySpectrum::InitViewWorkspace(const QString &project_name)
|
||||
{
|
||||
Q_UNUSED(project_name);
|
||||
}
|
||||
|
||||
void ConformToTheEnergySpectrum::SetAnalyzeDataFilename(const QMap<QString, QVariant> &data_files_set)
|
||||
{
|
||||
const QString& data_filename = data_files_set.first().toString();
|
||||
if (!data_filename.isEmpty() && QFileInfo(data_filename).exists()) {
|
||||
this->_data_filename = data_filename;
|
||||
this->loadAndProcess();
|
||||
}
|
||||
}
|
||||
|
||||
static std::vector<CoincidenceEvent> processCoincidence(const std::vector<SpectrumData>& data,
|
||||
uint64_t time_window_ns = 50,
|
||||
int min_order = 2,
|
||||
int max_order = 9)
|
||||
{
|
||||
std::vector<CoincidenceEvent> result;
|
||||
if (data.empty()) return result;
|
||||
|
||||
std::vector<SpectrumData> sorted = data;
|
||||
std::sort(sorted.begin(), sorted.end(),
|
||||
[](const SpectrumData& a, const SpectrumData& b) { return a.timestamp < b.timestamp; });
|
||||
|
||||
size_t n = sorted.size();
|
||||
for (size_t i = 0; i < n; ++i)
|
||||
{
|
||||
std::vector<SpectrumData> cluster;
|
||||
cluster.push_back(sorted[i]);
|
||||
uint64_t base = sorted[i].timestamp;
|
||||
|
||||
for (size_t j = i + 1; j < n && (sorted[j].timestamp - base) <= time_window_ns; ++j)
|
||||
cluster.push_back(sorted[j]);
|
||||
for (size_t j = i; j > 0 && (base - sorted[j-1].timestamp) <= time_window_ns; --j)
|
||||
if (j-1 != i) cluster.push_back(sorted[j-1]);
|
||||
|
||||
std::sort(cluster.begin(), cluster.end(),
|
||||
[](const SpectrumData& a, const SpectrumData& b) { return a.timestamp < b.timestamp; });
|
||||
cluster.erase(std::unique(cluster.begin(), cluster.end(),
|
||||
[](const SpectrumData& a, const SpectrumData& b) {
|
||||
return a.timestamp == b.timestamp && a.board_id == b.board_id && a.channel_id == b.channel_id;
|
||||
}), cluster.end());
|
||||
|
||||
int order = (int)cluster.size();
|
||||
if (order >= min_order && order <= max_order)
|
||||
{
|
||||
CoincidenceEvent ev;
|
||||
ev.events = cluster;
|
||||
result.push_back(ev);
|
||||
i += (cluster.size() - 1);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void ConformToTheEnergySpectrum::loadAndProcess()
|
||||
{
|
||||
auto functionToRun = [this]() {
|
||||
if (_data_filename.isEmpty()) return;
|
||||
|
||||
_busy_indicator->Start();
|
||||
|
||||
std::vector<SpectrumData> rawData;
|
||||
io::CSVReader<4> in(QStrToSysPath(_data_filename));
|
||||
in.read_header(io::ignore_extra_column,
|
||||
QString(QStringLiteral(u"板卡号")).toStdString(),
|
||||
QString(QStringLiteral(u"通道号")).toStdString(),
|
||||
QString(QStringLiteral(u"能量(KeV)")).toStdString(),
|
||||
QString(QStringLiteral(u"时间计数")).toStdString());
|
||||
|
||||
int board, channel;
|
||||
double energy;
|
||||
unsigned long long time_count;
|
||||
while (in.read_row(board, channel, energy, time_count)) {
|
||||
SpectrumData sd;
|
||||
sd.board_id = board;
|
||||
sd.channel_id = channel;
|
||||
sd.energy = energy;
|
||||
sd.timestamp = time_count;
|
||||
rawData.push_back(sd);
|
||||
}
|
||||
|
||||
if (rawData.empty()) {
|
||||
_busy_indicator->Stop();
|
||||
return;
|
||||
}
|
||||
|
||||
const uint64_t TIME_WINDOW_NS = 50;
|
||||
const int MIN_ORDER = 2;
|
||||
const int MAX_ORDER = 9;
|
||||
std::vector<CoincidenceEvent> coincidences = processCoincidence(rawData, TIME_WINDOW_NS, MIN_ORDER, MAX_ORDER);
|
||||
|
||||
if (coincidences.empty()) {
|
||||
_busy_indicator->Stop();
|
||||
return;
|
||||
}
|
||||
|
||||
const int STEP = 1;
|
||||
std::map<int, double> hist;
|
||||
|
||||
for (const auto& ev : coincidences) {
|
||||
for (const auto& spdt : ev.events) {
|
||||
int idx = static_cast<int>(spdt.energy) / STEP;
|
||||
hist[idx] += 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
QVector<double> vx, vy;
|
||||
for (const auto& pair : hist) {
|
||||
vx.push_back(pair.first * STEP);
|
||||
vy.push_back(pair.second);
|
||||
}
|
||||
|
||||
if (vx.isEmpty()) {
|
||||
_busy_indicator->Stop();
|
||||
return;
|
||||
}
|
||||
|
||||
double dmaxx = *std::max_element(vx.begin(), vx.end());
|
||||
double dmaxy = *std::max_element(vy.begin(), vy.end());
|
||||
|
||||
QMetaObject::invokeMethod(this, [this, vx, vy, dmaxx, dmaxy]() {
|
||||
_curve->setSamples(vx, vy);
|
||||
|
||||
_plot->setAxisScale(QwtPlot::xBottom, 0, dmaxx);
|
||||
_plot->setAxisScale(QwtPlot::yLeft, 0, dmaxy * 1.1);
|
||||
_plot->replot();
|
||||
_busy_indicator->Stop();
|
||||
}, Qt::QueuedConnection);
|
||||
};
|
||||
|
||||
QThread* load_thread = QThread::create(functionToRun);
|
||||
load_thread->start();
|
||||
}
|
||||
|
||||
void ConformToTheEnergySpectrum::showEvent(QShowEvent *e)
|
||||
{
|
||||
Q_UNUSED(e);
|
||||
if (_busy_indicator) {
|
||||
_busy_indicator->setGeometry(this->rect());
|
||||
this->update();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
34
src/ConformToTheEnergySpectrum/ConformToTheEnergySpectrum.h
Normal file
34
src/ConformToTheEnergySpectrum/ConformToTheEnergySpectrum.h
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
#ifndef CONFORMTOTHEENERGYSPECTRUM_H
|
||||
#define CONFORMTOTHEENERGYSPECTRUM_H
|
||||
|
||||
#include "MeasureAnalysisView.h"
|
||||
|
||||
class CustomQwtPlot;
|
||||
class QwtPlotCurve;
|
||||
class BusyIndicator;
|
||||
|
||||
class ConformToTheEnergySpectrum : public MeasureAnalysisView
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ConformToTheEnergySpectrum(QWidget *parent = nullptr);
|
||||
virtual ~ConformToTheEnergySpectrum();
|
||||
virtual void InitViewWorkspace(const QString& project_name) override final;
|
||||
virtual void SetAnalyzeDataFilename(const QMap<QString, QVariant>& data_files_set) override;
|
||||
|
||||
protected:
|
||||
virtual void showEvent(QShowEvent* e) override final;
|
||||
|
||||
private:
|
||||
void loadAndProcess(); // 读取CSV,执行符合处理,绘制能谱折线图
|
||||
|
||||
private:
|
||||
BusyIndicator* _busy_indicator = nullptr;
|
||||
CustomQwtPlot* _plot = nullptr;
|
||||
QwtPlotCurve* _curve = nullptr;
|
||||
QString _data_filename;
|
||||
};
|
||||
|
||||
#endif // CONFORMTOTHEENERGYSPECTRUM_H
|
||||
|
||||
|
||||
|
|
@ -260,6 +260,15 @@ void MeasureAnalysisTreeView::onNodeDoubleClicked(const QModelIndex& index)
|
|||
}
|
||||
}
|
||||
} break;
|
||||
case AnalysisType::CoincidenceParticleEnergySpectrumView: {
|
||||
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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
#include "ParticleInjectTimeAnalysisView.h"
|
||||
#include "ParticleTimeDifferenceView.h"
|
||||
#include "TwoDSpectralCompliance.h"
|
||||
#include "ConformToTheEnergySpectrum.h"
|
||||
#include <QMap>
|
||||
|
||||
MeasureAnalysisView* MeasureAnalysisView::NewAnalyzeView(AnalysisType view_type)
|
||||
|
|
@ -96,8 +97,8 @@ MeasureAnalysisView* MeasureAnalysisView::NewAnalyzeView(AnalysisType view_type)
|
|||
// new_view->setDeleteOnClose(false);
|
||||
} break;
|
||||
case AnalysisType::CoincidenceParticleEnergySpectrumView: {
|
||||
// new_view = new MeasureAnalysisDataTableView;
|
||||
// new_view->setDeleteOnClose(false);
|
||||
new_view = new ConformToTheEnergySpectrum;
|
||||
new_view->setDeleteOnClose(false);
|
||||
} break;
|
||||
case AnalysisType::AntiCoincidenceSpectrumView: {
|
||||
// new_view = new MeasureAnalysisDataTableView;
|
||||
|
|
|
|||
|
|
@ -13,7 +13,12 @@ ThreeDDisplay::ThreeDDisplay(QWidget *parent) :
|
|||
|
||||
ThreeDDisplay::~ThreeDDisplay()
|
||||
{
|
||||
|
||||
delete ui;
|
||||
// if(m_surface != nullptr) delete m_surface;
|
||||
// if(m_dataProxy != nullptr) delete m_dataProxy;
|
||||
// if(m_series != nullptr) delete m_series;
|
||||
// if(m_surfaceContainer != nullptr) delete m_surfaceContainer;
|
||||
}
|
||||
|
||||
void ThreeDDisplay::_init3DSurface()
|
||||
|
|
|
|||
13
src/src.pro
13
src/src.pro
|
|
@ -36,7 +36,9 @@ INCLUDEPATH += \
|
|||
$${PWD}/MeasureAnalysisHistoryForm \
|
||||
$${PWD}/MeasureDeviceParamsConfigView \
|
||||
$${PWD}/DeviceParameterConfig \
|
||||
$${PWD}/2DSpectralCompliance
|
||||
$${PWD}/2DSpectralCompliance \
|
||||
$${PWD}/ConformToTheEnergySpectrum
|
||||
|
||||
|
||||
DEPENDPATH += \
|
||||
$${PWD}/BusyIndicator \
|
||||
|
|
@ -52,7 +54,9 @@ DEPENDPATH += \
|
|||
$${PWD}/MeasureAnalysisHistoryForm \
|
||||
$${PWD}/MeasureDeviceParamsConfigView \
|
||||
$${PWD}/DeviceParameterConfig \
|
||||
$${PWD}/2DSpectralCompliance
|
||||
$${PWD}/2DSpectralCompliance \
|
||||
$${PWD}/ConformToTheEnergySpectrum
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -95,6 +99,7 @@ SOURCES += \
|
|||
ThreeDimensionalConformityAnalysisView/ThreeDDisplay.cpp \
|
||||
EnergyCountPeakFitView/EnergyCountPeakFitView.cpp \
|
||||
DeviceParameterConfig/DeviceParameterProxy.cpp \
|
||||
ConformToTheEnergySpectrum/ConformToTheEnergySpectrum.cpp \
|
||||
main.cpp
|
||||
|
||||
HEADERS += \
|
||||
|
|
@ -138,7 +143,9 @@ HEADERS += \
|
|||
ThreeDimensionalConformityAnalysisView/ParticleDataStatistics.h \
|
||||
ThreeDimensionalConformityAnalysisView/ThreeDDisplay.h \
|
||||
EnergyCountPeakFitView/EnergyCountPeakFitView.h \
|
||||
DeviceParameterConfig/DeviceParameterProxy.h
|
||||
DeviceParameterConfig/DeviceParameterProxy.h \
|
||||
ConformToTheEnergySpectrum/ConformToTheEnergySpectrum.h \
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user