454 lines
14 KiB
C++
454 lines
14 KiB
C++
#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 <QVBoxLayout>
|
||
#include <QFile>
|
||
#include <QTextStream>
|
||
#include <QDebug>
|
||
#include <cmath>
|
||
#include <QwtInterval>
|
||
#include <qwt_matrix_raster_data.h>
|
||
#include <algorithm>
|
||
#include <QFormLayout>
|
||
#include <QLabel>
|
||
#include <QPushButton>
|
||
#include <QLineEdit>
|
||
#include <QMouseEvent>
|
||
#include <QSet>
|
||
#include "BusyIndicator.h"
|
||
#include <QThread>
|
||
|
||
class HeatMapColorMap : public QwtLinearColorMap
|
||
{
|
||
public:
|
||
HeatMapColorMap() : QwtLinearColorMap(Qt::blue, Qt::red)
|
||
{
|
||
addColorStop(0.25, Qt::cyan);
|
||
addColorStop(0.5, Qt::green);
|
||
addColorStop(0.75, Qt::yellow);
|
||
addColorStop(0.90, Qt::darkRed);
|
||
}
|
||
};
|
||
|
||
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();
|
||
createFloatingInfoWidget();
|
||
_busy_indicator = new BusyIndicator(this);
|
||
|
||
}
|
||
|
||
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;
|
||
QString csvFile = data_files_set.first().toString();
|
||
if (csvFile.isEmpty())
|
||
return;
|
||
auto functionToRun = [this, csvFile](){
|
||
_busy_indicator->Start();
|
||
readCsv(csvFile);
|
||
generateSurfaceData();
|
||
updateSpectrogram();
|
||
_busy_indicator->Stop();
|
||
};
|
||
QThread* load_data_thread = QThread::create(functionToRun);
|
||
load_data_thread->start();
|
||
}
|
||
|
||
|
||
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"初级粒子能量");
|
||
energy_label.setFont(font);
|
||
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 = new QwtPlotSpectrogram();
|
||
_spectrogram->setColorMap(new HeatMapColorMap());
|
||
_spectrogram->attach(_plot);
|
||
}
|
||
|
||
void TwoDSpectralCompliance::readCsv(const QString &filename)
|
||
{
|
||
m_rawData.clear();
|
||
QFile file(filename);
|
||
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||
qWarning() << QStringLiteral(u"无法打开文件:") << 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();
|
||
}
|
||
|
||
void TwoDSpectralCompliance::generateSurfaceData()
|
||
{
|
||
m_surfaceData.clear();
|
||
if (m_rawData.isEmpty())
|
||
return;
|
||
|
||
QMap<int, QVector<EventData>> eventMap;
|
||
for (const auto& ev : m_rawData) {
|
||
eventMap[ev.eventId].append(ev);
|
||
}
|
||
|
||
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]++;
|
||
}
|
||
|
||
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);
|
||
}
|
||
|
||
}
|
||
|
||
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;
|
||
const int numRows = 200;
|
||
|
||
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();
|
||
}
|
||
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->setText(QStringLiteral(u"X"));
|
||
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(QStringLiteral(u"⊕"));
|
||
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);
|
||
}
|
||
|
||
void TwoDSpectralCompliance::showEvent(QShowEvent *e)
|
||
{
|
||
Q_UNUSED(e);
|
||
if (_busy_indicator) {
|
||
_busy_indicator->setGeometry(this->rect());
|
||
this->update();
|
||
}
|
||
}
|