#include "TwoDSpectralCompliance.h" #include "ui_TwoDSpectralCompliance.h" #include "CustomQwtPlot.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include // 自定义颜色映射(热力图) 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 &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(_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> eventMap; for (const auto& ev : m_rawData) { eventMap[ev.eventId].append(ev); } // 使用 QMap 来累加相同坐标的计数(初级能量,次级能量和) QMap, 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 zValues(numRows * numCols, 0.0); for (const auto& pt : m_surfaceData) { int col = static_cast((pt.primaryEnergy - primStart) / stepX); int row = static_cast((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(); }