EnergySpectrumAnalyer/src/2DSpectralCompliance/TwoDSpectralCompliance.cpp

454 lines
14 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#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();
}
}