二维符合能谱成员变量存储数据优化,二维符合能谱框选区域显示统计信息,二维符合能谱增加右键菜单还原

This commit is contained in:
anxinglong 2026-05-19 18:21:58 +08:00
parent e7f7e31d85
commit 3a8825beb9
4 changed files with 223 additions and 63 deletions

View File

@ -19,8 +19,11 @@
#include <QwtLinearColorMap>
#include <QwtMatrixRasterData>
#include <QwtPlotCanvas>
#include <QwtPlotPicker>
#include <QwtPlotSpectrogram>
#include <QwtScaleMap>
#include <QwtText>
#include <qwt_picker_machine.h>
#include <algorithm>
#include <cmath>
#include <QFileInfo>
@ -45,6 +48,8 @@ TwoDSpectralCompliance::TwoDSpectralCompliance(QWidget* parent)
, ui(new Ui::TwoDSpectralCompliance)
{
ui->setupUi(this);
this->_menu = new QMenu(this);
setupMenu();
QHBoxLayout* layout = new QHBoxLayout(this);
layout->setContentsMargins(0, 0, 0, 0);
this->_plot = new CustomQwtPlot(this);
@ -79,8 +84,8 @@ void TwoDSpectralCompliance::SetAnalyzeDataFilename(const QMap<QString, QVariant
_busy_indicator->Start();
auto functionToRun = [this, _data_filenames]() {
readCsv(_data_filenames);
generateSurfaceData();
QVector<EventData> rawData = readCsv(_data_filenames);
generateSurfaceData(rawData);
QMetaObject::invokeMethod(this, [this]() {
updateSpectrogram();
_busy_indicator->Stop();
@ -122,15 +127,45 @@ void TwoDSpectralCompliance::setupPlot()
_spectrogram = new QwtPlotSpectrogram();
_spectrogram->setColorMap(new HeatMapColorMap());
_spectrogram->attach(_plot);
_picker = new QwtPlotPicker(
QwtPlot::xBottom, // X 轴
QwtPlot::yLeft, // Y 轴
QwtPicker::RectRubberBand, // 橡皮筋样式
QwtPicker::AlwaysOff, // 不显示坐标追踪
_plot->canvas() // 作用于画布
);
QwtPickerDragRectMachine* rectMachine = new QwtPickerDragRectMachine();
_picker->setStateMachine(rectMachine);
connect(_picker, SIGNAL(selected(const QRectF&)),
this, SLOT(onSelection(const QRectF&)));
}
void TwoDSpectralCompliance::readCsv(const QStringList& filename)
void TwoDSpectralCompliance::setupMenu()
{
this->setContextMenuPolicy(Qt::CustomContextMenu);
connect(this, &TwoDSpectralCompliance::customContextMenuRequested, [this](const QPoint& pos) {
this->_menu->exec(this->mapToGlobal(pos));
});
QAction* action_plot_reset = this->_menu->addAction(QStringLiteral(u"还原"));
action_plot_reset->setObjectName("plot_reset");
connect(action_plot_reset, &QAction::triggered, [this]() {
this->_plot->ResetPlot();
});
this->_menu->addSeparator();
QAction* action_plot_config = this->_menu->addAction(QStringLiteral(u"图表配置"));
action_plot_config->setObjectName("plot_config");
connect(action_plot_config, &QAction::triggered, this, &TwoDSpectralCompliance::onActionPlotConfigure);
}
QVector<EventData> TwoDSpectralCompliance::readCsv(const QStringList& filename)
{
QVector<EventData> rawData;
if (filename.isEmpty()) {
QMetaObject::invokeMethod(this, [this]() {
_busy_indicator->Stop();
}, Qt::QueuedConnection);
return;
return rawData;
}
for (const QString& filename : filename) {
io::CSVReader<5> in(QStrToSysPath(filename));
@ -140,29 +175,31 @@ void TwoDSpectralCompliance::readCsv(const QStringList& filename)
QString(QStringLiteral(u"通道号")).toStdString(),
QString(QStringLiteral(u"能量(KeV)")).toStdString(),
QString(QStringLiteral(u"时间计数")).toStdString());
int eventId,board, channel;
int eventId, board, channel;
double energy;
unsigned long long time_count;
while (in.read_row(eventId,board, channel, energy, time_count)) {
while (in.read_row(eventId, board, channel, energy, time_count)) {
EventData sd;
sd.eventId = eventId;
sd.board = board;
sd.channel = channel;
sd.energy = energy;
sd.timeCounter = time_count;
m_rawData.push_back(sd);
rawData.push_back(sd);
}
}
return rawData;
}
void TwoDSpectralCompliance::generateSurfaceData()
void TwoDSpectralCompliance::generateSurfaceData(QVector<EventData> rawData)
{
m_surfaceData.clear();
if (m_rawData.isEmpty())
m_eventSummaries.clear();
if (rawData.isEmpty())
return;
QMap<int, QVector<EventData>> eventMap;
for (const auto& ev : m_rawData) {
for (const auto& ev : rawData) {
eventMap[ev.eventId].append(ev);
}
@ -179,6 +216,12 @@ void TwoDSpectralCompliance::generateSurfaceData()
secondarySum += events[i].energy;
}
EventSummary summary;
summary.eventId = it.key();
summary.primaryEnergy = primaryEnergy;
summary.secondaryEnergySum = secondarySum;
m_eventSummaries.append(summary);
auto key = qMakePair(primaryEnergy, secondarySum);
countMap[key]++;
}
@ -242,17 +285,23 @@ void TwoDSpectralCompliance::updateSpectrogram()
QwtMatrixRasterData* data = new QwtMatrixRasterData();
data->setInterval(Qt::XAxis, QwtInterval(primStart, primEnd));
data->setInterval(Qt::YAxis, QwtInterval(secStart, secEnd));
_plot->SetAxisInitRange(QwtPlot::xBottom, primStart, primEnd);
_plot->SetAxisInitRange(QwtPlot::yLeft, 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);
m_globalXMin = primStart;
m_globalXMax = primEnd;
m_globalYMin = secStart;
m_globalYMax = secEnd;
_plot->replot();
}
void TwoDSpectralCompliance::createFloatingInfoWidget()
{
// 悬浮窗口
m_floatingWidget = new QWidget(this);
m_floatingWidget->setObjectName("FloatingInfoWidget");
m_floatingWidget->setStyleSheet(
@ -268,7 +317,7 @@ void TwoDSpectralCompliance::createFloatingInfoWidget()
mainLayout->setSpacing(5);
mainLayout->setContentsMargins(8, 8, 8, 8);
// 标题栏(可拖动,包含隐藏按钮)
// 标题栏(可拖动,包含重置和隐藏按钮)
QWidget* titleBar = new QWidget();
QHBoxLayout* titleLayout = new QHBoxLayout(titleBar);
titleLayout->setContentsMargins(0, 0, 0, 0);
@ -276,9 +325,17 @@ void TwoDSpectralCompliance::createFloatingInfoWidget()
titleLabel->setStyleSheet("font-weight: bold;");
titleLayout->addWidget(titleLabel);
titleLayout->addStretch();
QPushButton* hideBtn = new QPushButton(QStringLiteral(""));
// 重置按钮:清除选区,显示全局统计
QPushButton* resetBtn = new QPushButton("重置");
resetBtn->setFixedSize(40, 20);
resetBtn->setToolTip(QStringLiteral(u"显示全局统计"));
resetBtn->setStyleSheet("border: none; font-weight: bold;");
titleLayout->addWidget(resetBtn);
// 隐藏按钮
QPushButton* hideBtn = new QPushButton(QStringLiteral("X"));
hideBtn->setFixedSize(20, 20);
hideBtn->setText(QStringLiteral(u"X"));
hideBtn->setStyleSheet("border: none; font-weight: bold;");
titleLayout->addWidget(hideBtn);
mainLayout->addWidget(titleBar);
@ -295,6 +352,7 @@ void TwoDSpectralCompliance::createFloatingInfoWidget()
m_totalPointsEdit = new QLineEdit;
m_totalPointsEdit->setReadOnly(true);
m_totalPointsEdit->setFixedWidth(130);
m_maxCountEdit = new QLineEdit;
m_maxCountEdit->setReadOnly(true);
m_maxCountEdit->setFixedWidth(130);
@ -307,7 +365,7 @@ void TwoDSpectralCompliance::createFloatingInfoWidget()
m_secRangeEdit->setReadOnly(true);
m_secRangeEdit->setFixedWidth(130);
formLayout->addRow(QStringLiteral(u"事件数:"), m_totalEventsEdit);
formLayout->addRow(QStringLiteral(u"符合事件数:"), m_totalEventsEdit);
formLayout->addRow(QStringLiteral(u"有效数据点:"), m_totalPointsEdit);
formLayout->addRow(QStringLiteral(u"最大计数:"), m_maxCountEdit);
formLayout->addRow(QStringLiteral(u"初级能量范围:"), m_primRangeEdit);
@ -317,6 +375,10 @@ void TwoDSpectralCompliance::createFloatingInfoWidget()
mainLayout->addStretch();
connect(hideBtn, &QPushButton::clicked, this, &TwoDSpectralCompliance::hideFloatingWidget);
connect(resetBtn, &QPushButton::clicked, this, [this]() {
m_hasSelection = false;
updateInfoContent();
});
titleBar->setCursor(Qt::SizeAllCursor);
m_floatingWidget->installEventFilter(this);
@ -340,7 +402,6 @@ void TwoDSpectralCompliance::createFloatingInfoWidget()
m_floatingWidget->hide();
m_showButton->show();
// m_floatingWidget->raise();
updateInfoContent();
}
@ -356,7 +417,7 @@ void TwoDSpectralCompliance::showFloatingWidget()
m_floatingWidget->show();
m_floatingWidget->raise();
m_showButton->hide();
updateInfoContent(); // 刷新内容
updateInfoContent();
}
void TwoDSpectralCompliance::updateShowButtonPosition()
@ -366,7 +427,45 @@ void TwoDSpectralCompliance::updateShowButtonPosition()
}
}
void TwoDSpectralCompliance::updateInfoContent()
void TwoDSpectralCompliance::onSelection(const QRectF& rect)
{
// 忽略过小的选区(可能是误点击)
if (rect.width() < 5 || rect.height() < 5)
return;
// 将画布像素矩形转换为轴坐标
const QwtScaleMap xMap = _plot->canvasMap(QwtPlot::xBottom);
const QwtScaleMap yMap = _plot->canvasMap(QwtPlot::yLeft);
// qDebug()<<"左边:" << rect.left() << "右边:" << rect.right()<< "上边:" << rect.top()<< "下边:" << rect.bottom();
// double x1 = xMap.invTransform(rect.left());
// double x2 = xMap.invTransform(rect.right());
// double y1 = yMap.invTransform(rect.bottom()); // 注意画布Y轴方向
// double y2 = yMap.invTransform(rect.top());
double x1 = rect.left();
double x2 = rect.right();
double y1 = rect.bottom();
double y2 = rect.top();
m_selXMin = std::min(x1, x2);
m_selXMax = std::max(x1, x2);
m_selYMin = std::min(y1, y2);
m_selYMax = std::max(y1, y2);
_plot->setAxisScale(QwtPlot::xBottom, m_selXMin, m_selXMax);
_plot->setAxisScale(QwtPlot::yLeft, m_selYMin, m_selYMax);
_plot->replot();
m_hasSelection = true;
updateInfoContent();
}
void TwoDSpectralCompliance::onActionPlotConfigure()
{
}
void TwoDSpectralCompliance:: updateInfoContent()
{
if (!m_totalEventsEdit)
return;
@ -380,29 +479,66 @@ void TwoDSpectralCompliance::updateInfoContent()
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);
}
if (m_hasSelection) {
// ----- 框选区域统计(基于热图数据点 m_surfaceData-----
int totalEvents = 0; // 总事件数 = 区域内所有点的计数值之和
int totalPoints = 0;
int maxCount = 0;
float minPrim = 1e9f, maxPrim = -1e9f;
float minSec = 1e9f, maxSec = -1e9f;
QSet<int> eventIds;
for (const auto& ev : m_rawData) {
eventIds.insert(ev.eventId);
}
int totalEvents = eventIds.size();
for (const auto& pt : m_surfaceData) {
if (pt.primaryEnergy >= m_selXMin && pt.primaryEnergy <= m_selXMax &&
pt.secondaryEnergySum >= m_selYMin && pt.secondaryEnergySum <= m_selYMax) {
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));
totalPoints++;
totalEvents += pt.count;
maxCount = std::max(maxCount, pt.count);
minPrim = std::min(minPrim, pt.primaryEnergy);
maxPrim = std::max(maxPrim, pt.primaryEnergy);
minSec = std::min(minSec, pt.secondaryEnergySum);
maxSec = std::max(maxSec, pt.secondaryEnergySum);
}
}
if (totalPoints == 0) {
m_totalEventsEdit->setText(QStringLiteral(u"0"));
m_totalPointsEdit->setText(QStringLiteral(u"0"));
m_maxCountEdit->setText(QStringLiteral(u"0"));
m_primRangeEdit->setText(QStringLiteral(u"无数据"));
m_secRangeEdit->setText(QStringLiteral(u"无数据"));
return;
}
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));
} else {
// ----- 全局统计(无选区时)-----
int totalPoints = m_surfaceData.size();
int totalEvents = 0;
int maxCount = 0;
float minPrim = 1e9f, maxPrim = -1e9f;
float minSec = 1e9f, maxSec = -1e9f;
for (const auto& pt : m_surfaceData) {
totalEvents += pt.count;
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);
}
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)
@ -410,7 +546,6 @@ 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());
@ -456,4 +591,4 @@ void TwoDSpectralCompliance::showEvent(QShowEvent* e)
_busy_indicator->setGeometry(this->rect());
this->update();
}
}
}

View File

@ -5,9 +5,10 @@
#include <QPoint>
#include <QVector>
#include <QWidget>
#include <QMenu>
class CustomQwtPlot;
class QwtPlotSpectrogram;
class QwtPlotPicker;
class QPushButton;
class QLineEdit;
class ScatterPlotItem;
@ -22,6 +23,14 @@ struct TwoSurfacePoint {
int count;
};
struct EventData {
int eventId;
int board;
int channel;
double energy;
unsigned long long timeCounter;
};
class TwoDSpectralCompliance : public MeasureAnalysisView {
Q_OBJECT
@ -39,8 +48,9 @@ protected:
private:
void setupPlot();
void readCsv(const QStringList& filename);
void generateSurfaceData();
void setupMenu();
QVector<EventData> readCsv(const QStringList& filename);
void generateSurfaceData(QVector<EventData> rawData);
void updateSpectrogram();
void createFloatingInfoWidget();
@ -53,21 +63,32 @@ private:
private slots:
void showFloatingWidget();
void hideFloatingWidget();
void onSelection(const QRectF& rect);
void onActionPlotConfigure();
private:
Ui::TwoDSpectralCompliance* ui;
QMenu* _menu = nullptr;
CustomQwtPlot* _plot = nullptr;
QwtPlotSpectrogram* _spectrogram = nullptr;
QwtPlotPicker* _picker = nullptr;
struct EventData {
struct EventSummary {
int eventId;
int board;
int channel;
double energy;
unsigned long long timeCounter;
float primaryEnergy;
float secondaryEnergySum;
};
QVector<EventData> m_rawData;
// QVector<EventData> m_rawData;
QVector<TwoSurfacePoint> m_surfaceData;
QVector<EventSummary> m_eventSummaries;
// 框选区域
bool m_hasSelection = false;
double m_selXMin = 0.0, m_selXMax = 0.0;
double m_selYMin = 0.0, m_selYMax = 0.0;
QWidget* m_floatingWidget = nullptr;
QPushButton* m_toggleButton = nullptr;
@ -82,6 +103,10 @@ private:
QPoint m_dragPosition;
bool m_dragging = false;
BusyIndicator* _busy_indicator = nullptr;
// 保存全局轴范围(用于重置放大)
double m_globalXMin = 0.0, m_globalXMax = 0.0;
double m_globalYMin = 0.0, m_globalYMax = 0.0;
};
#endif // TWODSPECTRALCOMPLIANCE_H
#endif // TWODSPECTRALCOMPLIANCE_H

View File

@ -33,11 +33,11 @@ void NuclideEditDialog::initUI()
m_leChildNuclide = new QLineEdit(this);
// 设置表单项(标签不变,用户界面无感知)
formLayout->addRow("核素名称", m_leName);
formLayout->addRow("半衰期", m_leHalfLife);
formLayout->addRow("半衰期不确定度", m_leHalfLifeUnc);
formLayout->addRow("母体核素名称", m_leParentNuclide);
formLayout->addRow("子体核素名称", m_leChildNuclide);
formLayout->addRow("核素名称", m_leName);
formLayout->addRow("半衰期", m_leHalfLife);
formLayout->addRow("半衰期不确定度", m_leHalfLifeUnc);
formLayout->addRow("母体核素名称", m_leParentNuclide);
formLayout->addRow("子体核素名称", m_leChildNuclide);
// 按钮盒
QDialogButtonBox *btnBox = new QDialogButtonBox(

View File

@ -31,32 +31,32 @@ void NuclideRayDialog::initUI()
// 射线类型下拉框(常用核素射线类型)
m_cmbRayType = new QComboBox(this);
m_cmbRayType->addItems({QStringLiteral(u"γ"), QStringLiteral(u"β⁻"), QStringLiteral(u"β⁺"), QStringLiteral(u"α"), QStringLiteral(u"X射线"), QStringLiteral(u"中子"), QStringLiteral(u"电子俘获")});
formLayout->addRow(QStringLiteral(u"射线类型"), m_cmbRayType);
formLayout->addRow(QStringLiteral(u"射线类型"), m_cmbRayType);
m_leRayMev = new QLineEdit(this);
m_leRayMev->setPlaceholderText(QStringLiteral(u"例如1.332 MeV"));
formLayout->addRow(QStringLiteral(u"射线能量"), m_leRayMev);
formLayout->addRow(QStringLiteral(u"射线能量"), m_leRayMev);
m_leRayMevUnc = new QLineEdit(this);
m_leRayMevUnc->setPlaceholderText(QStringLiteral(u"例如0.001 MeV"));
formLayout->addRow(QStringLiteral(u"能量不确定度"), m_leRayMevUnc);
formLayout->addRow(QStringLiteral(u"能量不确定度"), m_leRayMevUnc);
m_leBranchRatio = new QLineEdit(this);
m_leBranchRatio->setPlaceholderText(QStringLiteral(u"例如0.9998"));
formLayout->addRow("分支比", m_leBranchRatio);
formLayout->addRow("分支比", m_leBranchRatio);
m_leBranchRatioUnc = new QLineEdit(this);
m_leBranchRatioUnc->setPlaceholderText(QStringLiteral(u"例如0.0001"));
formLayout->addRow(QStringLiteral(u"分支比不确定度"), m_leBranchRatioUnc);
formLayout->addRow(QStringLiteral(u"分支比不确定度"), m_leBranchRatioUnc);
// 主射线标识下拉框
m_cmbMainRayIdent = new QComboBox(this);
m_cmbMainRayIdent->addItems({QStringLiteral(u""), QStringLiteral(u"")});
formLayout->addRow(QStringLiteral(u"是否为主射线"), m_cmbMainRayIdent);
formLayout->addRow(QStringLiteral(u"是否为主射线"), m_cmbMainRayIdent);
m_leJiahePeak = new QLineEdit(this);
m_leJiahePeak->setPlaceholderText(QStringLiteral(u"无则留空"));
formLayout->addRow(QStringLiteral(u"加和峰"), m_leJiahePeak);
formLayout->addRow(QStringLiteral(u"加和峰"), m_leJiahePeak);
// 按钮盒
QDialogButtonBox *btnBox = new QDialogButtonBox(