diff --git a/src/ThreeDimensionalConformityAnalysisView/ConformityAnalysis.cpp b/src/ThreeDimensionalConformityAnalysisView/ConformityAnalysis.cpp index f745a6f..c02b653 100644 --- a/src/ThreeDimensionalConformityAnalysisView/ConformityAnalysis.cpp +++ b/src/ThreeDimensionalConformityAnalysisView/ConformityAnalysis.cpp @@ -2,49 +2,781 @@ #include "ui_ConformityAnalysis.h" #include #include +#include +#include +#include +#include +#include +#include +#include "MeasureAnalysisProjectModel.h" + +QDataStream& operator<<(QDataStream& stream, const SurfacePoint& point) +{ + stream << point.primaryEnergy; + stream << point.secondaryEnergySum; + stream << point.count; + return stream; +} + +QDataStream& operator>>(QDataStream& stream, SurfacePoint& point) +{ + stream >> point.primaryEnergy; + stream >> point.secondaryEnergySum; + stream >> point.count; + return stream; +} + +QJsonObject ConformityHistoryItem::toJson() const +{ + QJsonObject obj; + obj["timestamp"] = timestamp.toString(Qt::ISODate); + obj["dataFileName"] = dataFileName; + obj["conformCount"] = conformCount; + obj["totalEvents"] = totalEvents; + obj["primaryEnergyStart"] = primaryEnergyStart; + obj["primaryEnergyEnd"] = primaryEnergyEnd; + obj["secondaryEnergyStart"] = secondaryEnergyStart; + obj["secondaryEnergyEnd"] = secondaryEnergyEnd; + + // ✅ 修复致命BUG:不再无限递归嵌套 + QJsonArray boardChannelJson; + for (int board = 0; board < MAX_BOARD; ++board) { + QJsonArray channelJson; + for (int channel = 0; channel < MAX_CHANNEL; ++channel) { + channelJson.append(boardChannelData[board][channel]); + } + boardChannelJson.append(channelJson); + } + obj["boardChannelData"] = boardChannelJson; + + QJsonObject firstParticleJson; + for (auto it = firstParticleData.begin(); it != firstParticleData.end(); ++it) { + firstParticleJson[it.key()] = it.value(); + } + obj["firstParticleData"] = firstParticleJson; + + return obj; +} + +ConformityHistoryItem ConformityHistoryItem::fromJson(const QJsonObject& obj) +{ + ConformityHistoryItem item; + item.timestamp = QDateTime::fromString(obj["timestamp"].toString(), Qt::ISODate); + item.dataFileName = obj["dataFileName"].toString(); + item.conformCount = obj["conformCount"].toInt(2); + item.totalEvents = obj["totalEvents"].toInt(); + item.primaryEnergyStart = obj["primaryEnergyStart"].toDouble(); + item.primaryEnergyEnd = obj["primaryEnergyEnd"].toDouble(); + item.secondaryEnergyStart = obj["secondaryEnergyStart"].toDouble(); + item.secondaryEnergyEnd = obj["secondaryEnergyEnd"].toDouble(); + + memset(item.boardChannelData, 0, sizeof(item.boardChannelData)); + QJsonArray boardChannelJson = obj["boardChannelData"].toArray(); + for (int board = 0; board < MAX_BOARD && board < boardChannelJson.size(); ++board) { + QJsonArray channelJson = boardChannelJson[board].toArray(); + for (int channel = 0; channel < MAX_CHANNEL && channel < channelJson.size(); ++channel) { + item.boardChannelData[board][channel] = channelJson[channel].toInt(); + } + } + + QJsonObject firstParticleJson = obj["firstParticleData"].toObject(); + for (const QString& key : firstParticleJson.keys()) { + item.firstParticleData[key] = firstParticleJson[key].toInt(); + } + + item.surfaceData.clear(); + return item; +} + +ConformityCalculatedResult ConformityHistoryItem::toCalculatedResult() const +{ + ConformityCalculatedResult result; + result.conformCount = conformCount; + result.dataFileName = dataFileName; + result.totalEvents = totalEvents; + result.primaryEnergyStart = primaryEnergyStart; + result.primaryEnergyEnd = primaryEnergyEnd; + result.secondaryEnergyStart = secondaryEnergyStart; + result.secondaryEnergyEnd = secondaryEnergyEnd; + memcpy(result.boardChannelData, boardChannelData, sizeof(boardChannelData)); + result.firstParticleData = firstParticleData; + result.surfaceData = surfaceData; + return result; +} + +ConformityHistoryItem ConformityHistoryItem::fromCalculatedResult(const ConformityCalculatedResult& result) +{ + ConformityHistoryItem item; + item.timestamp = QDateTime::currentDateTime(); + item.dataFileName = result.dataFileName; + item.conformCount = result.conformCount; + item.totalEvents = result.totalEvents; + item.primaryEnergyStart = result.primaryEnergyStart; + item.primaryEnergyEnd = result.primaryEnergyEnd; + item.secondaryEnergyStart = result.secondaryEnergyStart; + item.secondaryEnergyEnd = result.secondaryEnergyEnd; + memcpy(item.boardChannelData, result.boardChannelData, sizeof(result.boardChannelData)); + item.firstParticleData = result.firstParticleData; + item.surfaceData = result.surfaceData; + return item; +} ConformityAnalysis::ConformityAnalysis(QWidget *parent) : MeasureAnalysisView(parent), ui(new Ui::ConformityAnalysis) { + qRegisterMetaType(); + qRegisterMetaType(); + ui->setupUi(this); - memset(m_boardChannel, 0, sizeof(m_boardChannel)); // 新增 + memset(m_boardChannel, 0, sizeof(m_boardChannel)); + connect(ui->widget,SIGNAL(Signal_ClickedBoard(int,int)),this,SLOT(slot_ClickedBoard(int,int))); connect(ui->widget,SIGNAL(Signal_InitialState()),this,SLOT(slot_InitialState())); - + connect(ui->widget_3D,SIGNAL(emitCurrentIndexChanged(int)),this,SLOT(slot_ConformCountChanged(int))); } ConformityAnalysis::~ConformityAnalysis() { - qDeleteAll(_spectrumDataList); - _spectrumDataList.clear(); + if (m_parseWatcher && m_parseWatcher->isRunning()) { + m_parseWatcher->cancel(); + m_parseWatcher->waitForFinished(); + } + + clearAllCachedData(); + freeEventVector(_currentSpectrumData); delete ui; } void ConformityAnalysis::InitViewWorkspace(const QString &project_name) { + if (project_name.isEmpty()) + return; + auto project_model = ProjectList::Instance()->GetProjectModel(project_name); + if (!project_model) + return; + QString projectRootDir = project_model->GetProjectDir(); + qDebug() << "[ConformityAnalysis] 项目根目录:" << projectRootDir; + + _workspace = QDir(projectRootDir).filePath(this->GetViewName()); + qDebug() << "[ConformityAnalysis] 工作空间目录:" << _workspace; + + QDir dir; + if (!dir.mkpath(_workspace)) { + qCritical() << "[ConformityAnalysis] 无法创建工作空间目录:" << _workspace; + } else { + qDebug() << "[ConformityAnalysis] 工作空间目录创建成功"; + } + + _conformityHistoryList.clear(); + loadHistoryFromFile(); + + qDebug() << "[ConformityAnalysis] 工作空间初始化完成,已加载" << _conformityHistoryList.size() << "条历史记录"; } void ConformityAnalysis::SetAnalyzeDataFilename(const QMap &data_files_set) { - QStringList ch_count_data_name = data_files_set.keys(); - int conformCount = ui->comboBox->currentIndex() + 2; + if (data_files_set.isEmpty()) return; - for (const QString& ch_count_data_name : ch_count_data_name) - { - if(conformCount == ch_count_data_name.toInt()) - { - setCsvFile(data_files_set[ch_count_data_name].toString()); - readCsv(); - handleBoard(); - m_FirstParticle = statisticsFirstParticleQuantity(); - setAllBoardData(); - calculateFirstSecondRange(); - setThreeUiData(); - generateSurfaceData(_spectrumDataList); - } + clearAllCachedData(); + freeEventVector(_currentSpectrumData); + m_conformFileMap.clear(); + + qDebug() << "[ConformityAnalysis] 开始导入数据,共" << data_files_set.size() << "个文件"; + for (const QString& key : data_files_set.keys()) { + bool ok; + int cnt = key.toInt(&ok); + if (ok && cnt >=2 && cnt <=9) { + m_conformFileMap[cnt] = data_files_set[key].toString(); + qDebug() << "[ConformityAnalysis] 已添加" << cnt << "重符合文件:" << data_files_set[key].toString(); + } } + + if (m_conformFileMap.isEmpty()) { + qWarning() << "[ConformityAnalysis] 未找到任何有效的符合数数据文件"; + return; + } + + if (m_conformFileMap.contains(2)) { + qDebug() << "[ConformityAnalysis] ========================================"; + qDebug() << "[ConformityAnalysis] 开始解析2重符合数据(优先显示)"; + ConformityCalculatedResult result = streamParseAndCalculate(2, m_conformFileMap[2]); + + QMutexLocker cacheLocker(&m_cacheMutex); + m_calculatedCache[2] = result; + cacheLocker.unlock(); + + qDebug() << "[ConformityAnalysis] 正在将2重符合数据显示到界面"; + displayConformData(2); + qDebug() << "[ConformityAnalysis] ✅ 2重符合数据已成功显示到界面"; + + qDebug() << "[ConformityAnalysis] 开始后台保存2重符合数据到文件"; + QMetaObject::invokeMethod(this, [=]() { + saveResultToHistory(result); + }, Qt::QueuedConnection); + } else { + qCritical() << "[ConformityAnalysis] 未找到2重符合文件!"; + } + + m_isParsing = true; + parseAllConformDataInBackground(); +} + +void ConformityAnalysis::parseAllConformDataInBackground() +{ + QList conformCounts = m_conformFileMap.keys(); + // 移除已处理的2重符合 + conformCounts.removeOne(2); + std::sort(conformCounts.begin(), conformCounts.end()); + + qDebug() << "[ConformityAnalysis] 开始后台处理剩余符合数:" << conformCounts; + + auto parseTask = [this, conformCounts]() { + for (int conformCount : conformCounts) { + if (m_parseWatcher && m_parseWatcher->isCanceled()) + break; + + QString fileName = m_conformFileMap[conformCount]; + qDebug() << "[ConformityAnalysis] ========================================"; + qDebug() << "[ConformityAnalysis] 开始解析" << conformCount << "重符合数据:" << fileName; + + ConformityCalculatedResult result = streamParseAndCalculate(conformCount, fileName); + + QMutexLocker cacheLocker(&m_cacheMutex); + m_calculatedCache[conformCount] = result; + cacheLocker.unlock(); + + // 如果当前显示的是这个符合数,先显示 + if (conformCount == m_currentConformCount) { + qDebug() << "[ConformityAnalysis] 当前显示" << conformCount << "重符合,立即更新界面"; + QMetaObject::invokeMethod(this, "displayConformData", Qt::QueuedConnection, + Q_ARG(int, conformCount)); + } + + // 后台异步保存 + QMetaObject::invokeMethod(this, [=]() { + saveResultToHistory(result); + }, Qt::QueuedConnection); + + QMetaObject::invokeMethod(this, "onSingleParseFinished", Qt::QueuedConnection, Q_ARG(int, conformCount)); + } + }; + + m_parseWatcher = new QFutureWatcher(this); + connect(m_parseWatcher, &QFutureWatcher::finished, this, &ConformityAnalysis::onAllParseFinished); + m_parseWatcher->setFuture(QtConcurrent::run(parseTask)); +} + +ConformityCalculatedResult ConformityAnalysis::streamParseAndCalculate(int conformCount, const QString& fileName) +{ + ConformityCalculatedResult result; + result.conformCount = conformCount; + result.dataFileName = fileName; + + memset(result.boardChannelData, 0, sizeof(result.boardChannelData)); + for (int board = 1; board <= 8; ++board) { + for (int channel = 1; channel <= 4; ++channel) { + QString key = QStringLiteral(u"widget_%1_%2").arg(board).arg(channel); + result.firstParticleData[key] = 0; + } + } + + double minFirstVal = std::numeric_limits::max(); + double maxFirstVal = 0.0; + double minSecondVal = std::numeric_limits::max(); + double maxSecondVal = 0.0; + + int currentEventId = -1; + float primaryEnergy = 0.0f; + float secondaryEnergySum = 0.0f; + int eventCount = 0; + + if (!QFileInfo::exists(fileName)) { + qCritical() << "[ConformityAnalysis] CSV文件不存在:" << fileName; + return result; + } + + try { + io::CSVReader< + 5, + io::trim_chars<' ', '\t'>, + io::double_quote_escape<',', '"'>, + io::throw_on_overflow, + io::empty_line_comment> + reader(QStrToSysPath(fileName)); + reader.read_header(io::ignore_extra_column, + QStringLiteral(u"事件ID").toStdString(), + QStringLiteral(u"板卡号").toStdString(), + QStringLiteral(u"通道号").toStdString(), + QStringLiteral(u"能量(KeV)").toStdString(), + QStringLiteral(u"时间计数").toStdString()); + + particleCoincidenceEvent event; + while (reader.read_row(event.eventId, event.board, event.channel, event.energy, event.timeCounter)) { + eventCount++; + + // 1. 更新板卡通道计数 + result.boardChannelData[event.board][event.channel]++; + + // 2. 处理事件分组和能量统计 + if (event.eventId != currentEventId) { + // 保存上一个事件的曲面数据 + if (currentEventId != -1) { + SurfacePoint point; + point.primaryEnergy = primaryEnergy; + point.secondaryEnergySum = secondaryEnergySum; + point.count = 1; + result.surfaceData.append(point); + } + + // 新事件开始(初级粒子) + currentEventId = event.eventId; + primaryEnergy = static_cast(event.energy); + secondaryEnergySum = 0.0f; + + // 更新初级粒子计数 + int boardId = event.board + 1; + int channelId = event.channel + 1; + if (boardId >= 1 && boardId <= 8 && channelId >= 1 && channelId <= 4) { + QString key = QStringLiteral(u"widget_%1_%2").arg(boardId).arg(channelId); + result.firstParticleData[key]++; + } + + // 更新初级粒子能量范围 + if (event.energy < minFirstVal) minFirstVal = event.energy; + if (event.energy > maxFirstVal) maxFirstVal = event.energy; + } else { + // 次级粒子 + secondaryEnergySum += static_cast(event.energy); + + // 更新次级粒子能量范围 + if (event.energy < minSecondVal) minSecondVal = event.energy; + if (event.energy > maxSecondVal) maxSecondVal = event.energy; + } + } + + // 保存最后一个事件的曲面数据 + if (currentEventId != -1) { + SurfacePoint point; + point.primaryEnergy = primaryEnergy; + point.secondaryEnergySum = secondaryEnergySum; + point.count = 1; + result.surfaceData.append(point); + } + + // 压缩曲面数据内存 + result.surfaceData.squeeze(); + + } catch (const std::exception& e) { + qCritical() << "[ConformityAnalysis] CSV解析异常:" << e.what(); + return result; + } + + // 计算最终结果 + result.totalEvents = eventCount; + result.primaryEnergyStart = (minFirstVal == std::numeric_limits::max()) ? 0.0 : minFirstVal; + result.primaryEnergyEnd = maxFirstVal; + result.secondaryEnergyStart = (minSecondVal == std::numeric_limits::max()) ? 0.0 : minSecondVal; + result.secondaryEnergyEnd = maxSecondVal; + + qDebug() << "[ConformityAnalysis] " << conformCount << "重符合流式解析完成"; + qDebug() << "[ConformityAnalysis] 总事件数:" << result.totalEvents; + qDebug() << "[ConformityAnalysis] 初级能量范围:" << result.primaryEnergyStart << "-" << result.primaryEnergyEnd; + qDebug() << "[ConformityAnalysis] 次级能量范围:" << result.secondaryEnergyStart << "-" << result.secondaryEnergyEnd; + qDebug() << "[ConformityAnalysis] 曲面数据点数:" << result.surfaceData.size(); + qDebug() << "[ConformityAnalysis] 当前内存占用:约" << (result.surfaceData.size() * sizeof(SurfacePoint) + 1024*1024) / 1024 / 1024 << "MB"; + + return result; +} + +// ==================== 保存单个结果到历史记录 ==================== +void ConformityAnalysis::saveResultToHistory(const ConformityCalculatedResult& result) +{ + qDebug() << "[ConformityAnalysis] 开始保存" << result.conformCount << "重符合数据到历史记录"; + + QMutexLocker locker(&m_historyMutex); + ConformityHistoryItem item = ConformityHistoryItem::fromCalculatedResult(result); + + // 1. 流式写入JSON(零大小限制) + saveJsonStream(result.conformCount, item); + // 2. 二进制保存超大曲面数据 + saveSurfaceDataToBinary(result.conformCount, item.surfaceData); +} + +// ✅ 核心:流式逐行写入JSON,完全绕过QJsonDocument大小限制 +void ConformityAnalysis::saveJsonStream(int conformCount, const ConformityHistoryItem& item) +{ + QDir dir(_workspace); + dir.mkpath("."); + QString path = dir.filePath(QString("conformity_%1.json").arg(conformCount)); + + QFile file(path); + if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) { + qCritical() << "无法打开文件写入:" << file.errorString(); + return; + } + + QTextStream stream(&file); + stream.setCodec("UTF-8"); + // 强制浮点数用固定小数格式,避免科学计数法 + stream.setRealNumberPrecision(10); + stream.setRealNumberNotation(QTextStream::FixedNotation); + + // JSON字符串转义函数 + auto escapeJson = [](const QString& str) -> QString { + QString result; + result.reserve(str.size() + 10); + for (QChar c : str) { + switch (c.unicode()) { + case '"': result += "\\\""; break; + case '\\': result += "\\\\"; break; + case '\b': result += "\\b"; break; + case '\f': result += "\\f"; break; + case '\n': result += "\\n"; break; + case '\r': result += "\\r"; break; + case '\t': result += "\\t"; break; + default: + if (c.unicode() < 0x20) { + result += QString("\\u%1").arg(c.unicode(), 4, 16, QChar('0')); + } else { + result += c; + } + break; + } + } + return result; + }; + + // 所有Qt::endl都替换为'\n' + stream << "{" << '\n'; + + // 基础字段 + stream << " \"timestamp\": \"" << escapeJson(item.timestamp.toString(Qt::ISODate)) << "\"," << '\n'; + stream << " \"dataFileName\": \"" << escapeJson(item.dataFileName) << "\"," << '\n'; + stream << " \"conformCount\": " << item.conformCount << "," << '\n'; + stream << " \"totalEvents\": " << item.totalEvents << "," << '\n'; + stream << " \"primaryEnergyStart\": " << item.primaryEnergyStart << "," << '\n'; + stream << " \"primaryEnergyEnd\": " << item.primaryEnergyEnd << "," << '\n'; + stream << " \"secondaryEnergyStart\": " << item.secondaryEnergyStart << "," << '\n'; + stream << " \"secondaryEnergyEnd\": " << item.secondaryEnergyEnd << "," << '\n'; + + // 板卡通道数据 + stream << " \"boardChannelData\": [" << '\n'; + for (int board = 0; board < MAX_BOARD; ++board) { + stream << " ["; + for (int channel = 0; channel < MAX_CHANNEL; ++channel) { + stream << item.boardChannelData[board][channel]; + if (channel < MAX_CHANNEL - 1) stream << ", "; + } + stream << "]"; + if (board < MAX_BOARD - 1) stream << ","; + stream << '\n'; + } + stream << " ]," << '\n'; + + // 初级粒子数据 + stream << " \"firstParticleData\": {" << '\n'; + QList keys = item.firstParticleData.keys(); + for (int i = 0; i < keys.size(); ++i) { + stream << " \"" << escapeJson(keys[i]) << "\": " << item.firstParticleData[keys[i]]; + if (i < keys.size() - 1) stream << ","; + stream << '\n'; + } + stream << " }" << '\n'; + + stream << "}" << '\n'; + + file.close(); + qDebug() << "✅ JSON流式保存成功(兼容Qt6):" << path; +} + +// 二进制保存超大曲面数据(无大小限制) +void ConformityAnalysis::saveSurfaceDataToBinary(int conformCount, const QVector& data) +{ + QDir dir(_workspace); + QString path = dir.filePath(QString("surface_%1.bin").arg(conformCount)); + + QFile file(path); + if (!file.open(QIODevice::WriteOnly)) { + qCritical() << "无法打开二进制文件写入:" << file.errorString(); + return; + } + + QDataStream stream(&file); + stream << data; + file.close(); + qDebug() << "✅ 三维曲面数据二进制保存成功:" << path; +} + +// ==================== 加载历史数据 ==================== +void ConformityAnalysis::loadHistoryFromFile() +{ + _conformityHistoryList.clear(); + for (int c = 2; c <= 9; ++c) { + QString jsonPath = QDir(_workspace).filePath(QString("conformity_%1.json").arg(c)); + QString binPath = QDir(_workspace).filePath(QString("surface_%1.bin").arg(c)); + + if (!QFile::exists(jsonPath)) { + qDebug() << "[ConformityAnalysis] " << c << "重符合历史文件不存在:" << jsonPath; + continue; + } + + // 加载JSON小数据 + QFile f(jsonPath); + if (!f.open(QIODevice::ReadOnly)) { + qWarning() << "[ConformityAnalysis] 无法打开JSON文件:" << jsonPath; + continue; + } + QJsonDocument doc = QJsonDocument::fromJson(f.readAll()); + f.close(); + + ConformityHistoryItem item = ConformityHistoryItem::fromJson(doc.object()); + + // 加载二进制曲面数据 + if (QFile::exists(binPath)) { + QFile binFile(binPath); + if (binFile.open(QIODevice::ReadOnly)) { + QDataStream stream(&binFile); + stream >> item.surfaceData; + binFile.close(); + } + } + + _conformityHistoryList.append(item); + qDebug() << "[ConformityAnalysis] 加载" << c << "重符合历史记录完成"; + } +} + +// ==================== 单个符合数解析完成 ==================== +void ConformityAnalysis::onSingleParseFinished(int conformCount) +{ + qDebug() << "[ConformityAnalysis] " << conformCount << "重符合数据处理完成"; +} + +// ==================== 所有符合数解析完成 ==================== +void ConformityAnalysis::onAllParseFinished() +{ + m_isParsing = false; + qDebug() << "[ConformityAnalysis] ========================================"; + qDebug() << "[ConformityAnalysis] ✅ 所有符合数数据处理完成并已全部保存"; + qDebug() << "[ConformityAnalysis] 所有文件已保存到:" << _workspace; +} + +// ==================== 显示指定符合数的数据 ==================== +void ConformityAnalysis::displayConformData(int conformCount) +{ + if (!m_conformFileMap.contains(conformCount)) { + qWarning() << "[ConformityAnalysis] 无" << conformCount << "重符合数据"; + return; + } + + freeEventVector(_currentSpectrumData); + m_currentConformCount = conformCount; + + QMutexLocker locker(&m_cacheMutex); + if (!m_calculatedCache.contains(conformCount)) { + qWarning() << "[ConformityAnalysis] " << conformCount << "重符合数据尚未准备完成"; + return; + } + + const ConformityCalculatedResult& result = m_calculatedCache[conformCount]; + locker.unlock(); + + m_iComply = result.totalEvents; + m_dFirstStart = result.primaryEnergyStart; + m_dFirstEnd = result.primaryEnergyEnd; + m_dSecondStart = result.secondaryEnergyStart; + m_dSecondEnd = result.secondaryEnergyEnd; + memcpy(m_boardChannel, result.boardChannelData, sizeof(m_boardChannel)); + m_FirstParticle = result.firstParticleData; + m_surfaceData = result.surfaceData; + + setAllBoardData(); + setThreeUiData(); + ui->widget_3D->setSurfaceData(m_surfaceData); + + qDebug() << "[ConformityAnalysis] 显示" << conformCount << "重符合数据完成"; +} + +// ==================== 符合数切换槽函数 ==================== +void ConformityAnalysis::slot_ConformCountChanged(int index) +{ + int conformCount = index + 2; + if (conformCount == m_currentConformCount) + return; + + displayConformData(conformCount); +} + +// ==================== CSV文件读取(仅用于点击板卡时按需加载) ==================== +QVector ConformityAnalysis::readCsv(const QString& fileName) +{ + QVector dataList; + + if (!QFileInfo::exists(fileName)) { + qWarning() << "[ConformityAnalysis] 文件不存在:" << fileName; + return dataList; + } + + try { + io::CSVReader< + 5, + io::trim_chars<' ', '\t'>, + io::double_quote_escape<',', '"'>, + io::throw_on_overflow, + io::empty_line_comment> + reader(QStrToSysPath(fileName)); + reader.read_header(io::ignore_extra_column, + QStringLiteral(u"事件ID").toStdString(), + QStringLiteral(u"板卡号").toStdString(), + QStringLiteral(u"通道号").toStdString(), + QStringLiteral(u"能量(KeV)").toStdString(), + QStringLiteral(u"时间计数").toStdString()); + + dataList.reserve(1000000); + + while (true) { + particleCoincidenceEvent *event = new particleCoincidenceEvent; + bool flag = reader.read_row(event->eventId, event->board, event->channel, event->energy, event->timeCounter); + if (!flag) { + delete event; + break; + } + dataList.push_back(event); + } + } catch (const std::exception& e) { + qWarning() << "[ConformityAnalysis] CSV解析失败:" << e.what(); + freeEventVector(dataList); + } + + return dataList; +} + +// 释放事件数组内存 +void ConformityAnalysis::freeEventVector(QVector& vec) +{ + qDeleteAll(vec); + vec.clear(); + vec.squeeze(); +} + +// ==================== 以下原有方法保持不变 ==================== +void ConformityAnalysis::handleBoard(const QVector& data, int boardChannelData[MAX_BOARD][MAX_CHANNEL]) +{ + memset(boardChannelData, 0, sizeof(int) * MAX_BOARD * MAX_CHANNEL); + for(auto event : data) { + boardChannelData[event->board][event->channel]++; + } +} + +QMap ConformityAnalysis::statisticsFirstParticleQuantity(const QVector& data) +{ + QMap firstParticleCount; + for (int board = 1; board <= 8; ++board) { + for (int channel = 1; channel <= 4; ++channel) { + QString key = QStringLiteral(u"widget_%1_%2").arg(board).arg(channel); + firstParticleCount[key] = 0; + } + } + if (data.empty()) return firstParticleCount; + + int eventId = -1; + for (const auto& event : data) { + if (event->eventId == eventId) continue; + eventId = event->eventId; + + int boardId = event->board + 1; + int channelId = event->channel + 1; + if (boardId >= 1 && boardId <= 8 && channelId >= 1 && channelId <= 4) { + QString key = QStringLiteral(u"widget_%1_%2").arg(boardId).arg(channelId); + firstParticleCount[key]++; + } + } + return firstParticleCount; +} + +void ConformityAnalysis::calculateFirstSecondRange(const QVector& data, + double& firstStart, double& firstEnd, + double& secondStart, double& secondEnd) +{ + if (data.empty()) { + firstStart = firstEnd = secondStart = secondEnd = 0.0; + return; + } + + double minFirstVal = 1e9; + double maxFirstVal = 0.0; + double minSecondVal = 1e9; + double maxSecondVal = 0.0; + + int eventId = -1; + for (int j = 0; j < data.size(); j++) { + particleCoincidenceEvent *spectrum = data.at(j); + if (spectrum->eventId != eventId) { + eventId = spectrum->eventId; + if (spectrum->energy < minFirstVal) minFirstVal = spectrum->energy; + if (spectrum->energy > maxFirstVal) maxFirstVal = spectrum->energy; + } else { + if (spectrum->energy < minSecondVal) minSecondVal = spectrum->energy; + if (spectrum->energy > maxSecondVal) maxSecondVal = spectrum->energy; + } + } + + firstStart = minFirstVal; + firstEnd = maxFirstVal; + secondStart = (minSecondVal == 1e9) ? 0.0 : minSecondVal; + secondEnd = (maxSecondVal == 0.0) ? 0.0 : maxSecondVal; +} + +QVector ConformityAnalysis::generateSurfaceData(const QVector events) +{ + QVector surfaceData; + if (events.empty()) return surfaceData; + + surfaceData.reserve(events.size() / 2); + int eventId = 0; + float primaryEnergy = 0.0f; + float secondaryEnergySum = 0.0f; + + for (const auto& event : events) { + if(eventId != event->eventId) { + SurfacePoint point; + point.primaryEnergy = primaryEnergy; + point.secondaryEnergySum = secondaryEnergySum; + point.count = 1; + surfaceData.append(point); + primaryEnergy = 0.0f; + secondaryEnergySum = 0.0f; + primaryEnergy = event->energy; + eventId = event->eventId; + continue; + } else { + secondaryEnergySum += event->energy; + } + } + + surfaceData.squeeze(); + return surfaceData; +} + +QVector ConformityAnalysis::handleBasicSubordinate(int Board, int Channel, const QVector& data) +{ + QVector EventVector; + int eventId = 0; + m_beginVector.clear(); + + for(auto event : data) { + if(eventId == event->eventId) { + EventVector.push_back(event); + } + if(event->board == Board - 1 && event->channel == Channel - 1) { + m_beginVector.push_back(event); + EventVector.push_back(event); + eventId = event->eventId; + } + } + return EventVector; } void ConformityAnalysis::setCsvFile(QString fileName) @@ -54,41 +786,69 @@ void ConformityAnalysis::setCsvFile(QString fileName) void ConformityAnalysis::readCsv() { - qDebug()< reader(QStrToSysPath(m_fileName)); - reader.read_header(io::ignore_extra_column, QStringLiteral(u"事件ID").toStdString(),QStringLiteral(u"板卡号").toStdString(), QStringLiteral(u"通道号").toStdString(), QStringLiteral(u"能量(KeV)").toStdString(), QStringLiteral(u"时间计数").toStdString()); - // 逐行读取数据 - while (true) - { - particleCoincidenceEvent *SpectrumData = new particleCoincidenceEvent; - bool flag = reader.read_row(SpectrumData->eventId,SpectrumData->board, SpectrumData->channel, SpectrumData->energy,SpectrumData->timeCounter); - - if(!flag) - { - break; - } - _spectrumDataList.push_back(SpectrumData); - } + freeEventVector(_currentSpectrumData); + _currentSpectrumData = readCsv(m_fileName); } void ConformityAnalysis::slot_InitialState() { + QMutexLocker locker(&m_cacheMutex); + if (m_calculatedCache.contains(m_currentConformCount)) { + const ConformityCalculatedResult& result = m_calculatedCache[m_currentConformCount]; + m_iComply = result.totalEvents; + m_dFirstStart = result.primaryEnergyStart; + m_dFirstEnd = result.primaryEnergyEnd; + m_dSecondStart = result.secondaryEnergyStart; + m_dSecondEnd = result.secondaryEnergyEnd; + } + locker.unlock(); + + int maxCount = getMaxValue(); + ui->widget->setAllWidgetColorMaxValue(maxCount); setAllBoardData(); - calculateFirstSecondRange(); setThreeUiData(); - generateSurfaceData(_spectrumDataList); + ui->widget_3D->setSurfaceData(m_surfaceData); } void ConformityAnalysis::slot_ClickedBoard(int board,int channel) { - //获取当前板卡的初级粒子数据 - QVector data = handleBasicSubordinate(board,channel); + QMutexLocker locker(&m_cacheMutex); + if (!m_calculatedCache.contains(m_currentConformCount)) { + return; + } + QString fileName = m_calculatedCache[m_currentConformCount].dataFileName; + locker.unlock(); + + if (_currentSpectrumData.isEmpty()) { + _currentSpectrumData = readCsv(fileName); + } + + QVector data = handleBasicSubordinate(board, channel, _currentSpectrumData); if(data.size() <= 0) return; - generateSurfaceData(data); + + QVector tempSurface = generateSurfaceData(data); + ui->widget_3D->setSurfaceData(tempSurface); + + m_iComply = data.size(); + + double localFirstStart, localFirstEnd; + double localSecondStart, localSecondEnd; + calculateFirstSecondRange(data, localFirstStart, localFirstEnd, localSecondStart, localSecondEnd); + + m_dFirstStart = localFirstStart; + m_dFirstEnd = localFirstEnd; + m_dSecondStart = localSecondStart; + m_dSecondEnd = localSecondEnd; + + setThreeUiData(); + m_subordinate = handleSubordinate(data, board, channel); - for (int i = 0; i < m_subordinate.keys().size();i++) - { + int maxSubordinate = 0; + for (int val : m_subordinate.values()) { + if (val > maxSubordinate) maxSubordinate = val; + } + ui->widget->setAllWidgetColorMaxValue(maxSubordinate); + for (int i = 0; i < m_subordinate.keys().size();i++) { QString objectName =m_subordinate.keys().at(i); QStringList parts = objectName.split('_'); int bd = parts[1].toInt(); @@ -98,84 +858,18 @@ void ConformityAnalysis::slot_ClickedBoard(int board,int channel) ui->widget->setWidgetData(board,channel,m_boardChannel[board - 1][channel - 1],data.size()); } - - -void ConformityAnalysis::handleBoard() -{ - for(auto spetruData:_spectrumDataList) - { - m_boardChannel[spetruData->board][spetruData->channel]++; - } -} - -QMap ConformityAnalysis::statisticsFirstParticleQuantity() -{ - QMap firstParticleCount; - for (int board = 1; board <= 8; ++board) - { - for (int channel = 1; channel <= 4; ++channel) - { - QString key = QStringLiteral(u"widget_%1_%2").arg(board).arg(channel); - firstParticleCount[key] = 0; - } - } - if (_spectrumDataList.empty()) return firstParticleCount; - - int eventId = 0; - // 遍历所有符合事件 - for (const auto& event : _spectrumDataList) - { - // 第一个事件是初级粒子 - const auto& firstParticle = event; - int boardId = firstParticle->board + 1; - int channelId = firstParticle->channel + 1; - if(firstParticle->eventId == eventId) continue; - eventId = firstParticle->eventId; - if (boardId >= 1 && boardId <= 8 && channelId >= 1 && channelId <= 4) { - QString key = QStringLiteral(u"widget_%1_%2").arg(boardId).arg(channelId); - firstParticleCount[key]++; - } - } - return firstParticleCount; -} - -QVector ConformityAnalysis::handleBasicSubordinate(int Board, int Channel) -{ - QVector EventVector;//所有的初级粒子符合事件 - int eventId = 0; - for(auto data:_spectrumDataList) - { - if(eventId == data->eventId) - { - EventVector.push_back(data); - } - - if(data->board == Board - 1&& data->channel == Channel - 1) - { - m_beginVector.push_back(data); - EventVector.push_back(data); - eventId = data->eventId; - } - } - return EventVector; -} - QMap ConformityAnalysis::handleSubordinate(QVector &eventData,int Board, int Channel) { m_secondVector.clear(); QMap data; - for (int board = 1; board <= 8; ++board) - { - for (int channel = 1; channel <= 4; ++channel) - { + for (int board = 1; board <= 8; ++board) { + for (int channel = 1; channel <= 4; ++channel) { QString key = QStringLiteral(u"widget_%1_%2").arg(board).arg(channel); data[key] = 0; - for(auto dataSpectrum : eventData) - { + for(auto dataSpectrum : eventData) { if (dataSpectrum->board == Board - 1 && dataSpectrum->channel == Channel - 1) - continue; - if (dataSpectrum->board == board - 1 && dataSpectrum->channel == channel - 1) - { + continue; + if (dataSpectrum->board == board - 1 && dataSpectrum->channel == channel - 1) { data[key]++; m_secondVector.push_back(dataSpectrum); } @@ -192,92 +886,99 @@ int ConformityAnalysis::getMaxValue() return *std::max_element(begin, end); } -void ConformityAnalysis::calculateFirstSecondRange() -{ - //符合事件总计数 - m_iComply = 0; - // 找出所有数值中的最小值和最大值 - double minFirstVal = 11111111.1111; - double maxFirstVal = 0.0; - - double minSecondVal = 11111111.1111; - double maxSecondVal = 0.0; - - m_iComply = _spectrumDataList.size(); - for (int j = 0; j < _spectrumDataList.size(); j++) - { - particleCoincidenceEvent *spectrum = _spectrumDataList.at(j); - if (j == 0) - { - if (spectrum->energy < minFirstVal) minFirstVal = spectrum->energy; - if (spectrum->energy > maxFirstVal) maxFirstVal = spectrum->energy; - continue; - } - if (spectrum->energy < minSecondVal) minSecondVal = spectrum->energy; - if (spectrum->energy > maxSecondVal) maxSecondVal = spectrum->energy; - } - - m_dFirstStart = minFirstVal; - m_dFirstEnd = maxFirstVal; - m_dSecondStart = minSecondVal; - m_dSecondEnd = maxSecondVal; -} - void ConformityAnalysis::setAllBoardData() { - for (int board = 1; board <= 8; ++board) - { - for (int channel = 1; channel <= 4; ++channel) - { + int maxCount = getMaxValue(); + ui->widget->setAllWidgetColorMaxValue(maxCount); + for (int board = 1; board <= 8; ++board) { + for (int channel = 1; channel <= 4; ++channel) { QString key = QStringLiteral(u"widget_%1_%2").arg(board).arg(channel); - qDebug()<widget->setInitWidgetData(board,channel,m_boardChannel[board - 1][channel-1],m_FirstParticle[key]); - } + if (m_FirstParticle.contains(key)) { + int totalCount = m_boardChannel[board - 1][channel-1]; + int firstCount = m_FirstParticle[key]; + ui->widget->setInitWidgetData(board, channel, totalCount, firstCount); + } } } } - void ConformityAnalysis::setThreeUiData() { - qDebug()<< m_dSecondStart << m_dSecondEnd; ui->widget_3D->setBasicParticle(m_dFirstStart,m_dFirstEnd); ui->widget_3D->setSecondParticle(m_dSecondStart,m_dSecondEnd); - ui->widget_3D->setComplyWithEvent(m_iComply); + ui->widget_3D->setComplyWithEvent(QString::number(m_iComply)); } -void ConformityAnalysis::generateSurfaceData(const QVector events) +void ConformityAnalysis::saveHistoryToFile() { - m_surfaceData.clear(); - if (events.empty()) return; - int eventId = 0; - - float primaryEnergy = 0.0f; - float secondaryEnergySum = 0.0f; - for (const auto& event : events) - { - - if(eventId != event->eventId) - { - SurfacePoint point; - point.primaryEnergy = primaryEnergy; - point.secondaryEnergySum = secondaryEnergySum; - point.count = 1; - m_surfaceData.append(point); - primaryEnergy = 0.0f; - secondaryEnergySum = 0.0f; - primaryEnergy = event->energy; - eventId = event->eventId; - continue; - } - else - { - secondaryEnergySum += event->energy; + qDebug() << "[ConformityAnalysis] 执行全局保存所有符合数文件"; + for (int conformCount = 2; conformCount <= 9; ++conformCount) { + int idx = findHistoryIndex(m_conformFileMap.value(conformCount), conformCount); + if (idx >= 0) { + saveJsonStream(conformCount, _conformityHistoryList[idx]); + saveSurfaceDataToBinary(conformCount, _conformityHistoryList[idx].surfaceData); } } - ui->widget_3D->setSurfaceData(m_surfaceData); } +int ConformityAnalysis::saveCurrentAnalysisToHistory(int conformCount) +{ + QString fileName = m_conformFileMap[conformCount]; + int existingIndex = findHistoryIndex(fileName, conformCount); + if (existingIndex >= 0) { + ConformityHistoryItem& item = _conformityHistoryList[existingIndex]; + item.timestamp = QDateTime::currentDateTime(); + item.totalEvents = m_iComply; + item.primaryEnergyStart = m_dFirstStart; + item.primaryEnergyEnd = m_dFirstEnd; + item.secondaryEnergyStart = m_dSecondStart; + item.secondaryEnergyEnd = m_dSecondEnd; + memcpy(item.boardChannelData, m_boardChannel, sizeof(m_boardChannel)); + item.firstParticleData = m_FirstParticle; + item.surfaceData = m_surfaceData; + return existingIndex; + } + ConformityHistoryItem item; + item.timestamp = QDateTime::currentDateTime(); + item.dataFileName = fileName; + item.conformCount = conformCount; + item.totalEvents = m_iComply; + item.primaryEnergyStart = m_dFirstStart; + item.primaryEnergyEnd = m_dFirstEnd; + item.secondaryEnergyStart = m_dSecondStart; + item.secondaryEnergyEnd = m_dSecondEnd; + + memcpy(item.boardChannelData, m_boardChannel, sizeof(m_boardChannel)); + item.firstParticleData = m_FirstParticle; + item.surfaceData = m_surfaceData; + + int newHistoryIndex = _conformityHistoryList.size(); + _conformityHistoryList.append(item); + + return newHistoryIndex; +} + +int ConformityAnalysis::findHistoryIndex(const QString& fileName, int conformCount) const +{ + for (int i = 0; i < _conformityHistoryList.size(); ++i) { + const ConformityHistoryItem& item = _conformityHistoryList[i]; + if (item.dataFileName == fileName && item.conformCount == conformCount) { + return i; + } + } + return -1; +} + +void ConformityAnalysis::clearAllCachedData() +{ + QMutexLocker locker(&m_cacheMutex); + m_calculatedCache.clear(); +} + +void ConformityAnalysis::saveSingleHistoryToFile(int conformCount, const ConformityHistoryItem& item) +{ + // 兼容旧接口,实际使用流式写入 + saveJsonStream(conformCount, item); +} \ No newline at end of file diff --git a/src/ThreeDimensionalConformityAnalysisView/ConformityAnalysis.h b/src/ThreeDimensionalConformityAnalysisView/ConformityAnalysis.h index 486f7ba..e3955f2 100644 --- a/src/ThreeDimensionalConformityAnalysisView/ConformityAnalysis.h +++ b/src/ThreeDimensionalConformityAnalysisView/ConformityAnalysis.h @@ -8,15 +8,21 @@ #include "csv.h" #include "MeasureAnalysisView.h" #include +#include +#include +#include +#include +#include +#include +#include +#include +#include -//using namespace CoincidenceSpectrum; - - +// Ui类前向声明 namespace Ui { class ConformityAnalysis; } - typedef struct particleCoincidenceEvent { int eventId;//事件ID @@ -26,77 +32,166 @@ typedef struct particleCoincidenceEvent qulonglong timeCounter;//时间计数 }PARTICLECOINCIDENCEEVENT; -//// 用于存储三维数据 -//typedef struct SurfacePoint { -// float primaryEnergy; // 初级粒子能量 -// float secondaryEnergySum; // 次级粒子能量和 -// int count; // 符合事件计数 -//}SURFACEPOINT; +// 预计算结果结构体(仅保存计算后的数据) +struct ConformityCalculatedResult { + int conformCount; // 符合粒子数 + QString dataFileName; // 原始CSV文件名 + int totalEvents; // 符合事件总计数 + double primaryEnergyStart; // 初级粒子能量范围-起始 + double primaryEnergyEnd; // 初级粒子能量范围-结束 + double secondaryEnergyStart; // 次级粒子能量范围-起始 + double secondaryEnergyEnd; // 次级粒子能量范围-结束 + + // 板卡通道计数数据(8板×4通道) + int boardChannelData[MAX_BOARD][MAX_CHANNEL]; + // 初级粒子计数数据 + QMap firstParticleData; + // 三维曲面数据 + QVector surfaceData; + + // 构造函数 + ConformityCalculatedResult() { + memset(boardChannelData, 0, sizeof(boardChannelData)); + totalEvents = 0; + primaryEnergyStart = primaryEnergyEnd = 0.0; + secondaryEnergyStart = secondaryEnergyEnd = 0.0; + } +}; + +Q_DECLARE_METATYPE(ConformityCalculatedResult) + +// 一致性分析历史数据结构体 +struct ConformityHistoryItem { + QDateTime timestamp; // 保存时间戳 + QString dataFileName; // 原始CSV文件名 + int conformCount; // 符合粒子数(2/3/4...9) + int totalEvents; // 符合事件总计数 + double primaryEnergyStart; // 初级粒子能量范围-起始 + double primaryEnergyEnd; // 初级粒子能量范围-结束 + double secondaryEnergyStart; // 次级粒子能量范围-起始 + double secondaryEnergyEnd; // 次级粒子能量范围-结束 + + // 板卡通道计数数据(8板×4通道) + int boardChannelData[MAX_BOARD][MAX_CHANNEL]; + // 初级粒子计数数据 + QMap firstParticleData; + // 三维曲面数据 + QVector surfaceData; + + // JSON序列化/反序列化方法 + QJsonObject toJson() const; + static ConformityHistoryItem fromJson(const QJsonObject& obj); + // 转换为预计算结果 + ConformityCalculatedResult toCalculatedResult() const; + // 从预计算结果创建历史记录 + static ConformityHistoryItem fromCalculatedResult(const ConformityCalculatedResult& result); +}; + +Q_DECLARE_METATYPE(ConformityHistoryItem) class ConformityAnalysis : public MeasureAnalysisView { Q_OBJECT - private: QMap m_FirstParticle; QMap m_subordinate; QVector m_beginVector; QVector m_secondVector; QVector m_surfaceData; - public: explicit ConformityAnalysis(QWidget *parent = nullptr); ~ConformityAnalysis(); - virtual void InitViewWorkspace(const QString& project_name) override final; virtual void SetAnalyzeDataFilename(const QMap& data_files_set); //设置csv文件路径及文件名称 void setCsvFile(QString fileName); - //读取csv文件 + //读取csv文件(兼容旧代码) void readCsv(); - private slots: void slot_InitialState(); void slot_ClickedBoard(int board,int channel); + // 符合数切换槽函数 + void slot_ConformCountChanged(int index); + // 单个符合数解析完成槽函数 + void onSingleParseFinished(int conformCount); + // 所有符合数解析完成槽函数 + void onAllParseFinished(); + // 保存单个预计算结果到历史记录(主线程执行) + void saveResultToHistory(const ConformityCalculatedResult& result); + private: //处理板卡信息 - void handleBoard(); + void handleBoard(const QVector& data, int boardChannelData[MAX_BOARD][MAX_CHANNEL]); //统计每个板卡的初级粒子计数 - QMap statisticsFirstParticleQuantity(); + QMap statisticsFirstParticleQuantity(const QVector& data); //处理当前板卡初级粒子信息 - QVector handleBasicSubordinate(int Board, int Channel); + QVector handleBasicSubordinate(int Board, int Channel, const QVector& data); //处理次级粒子信息 - QMap handleSubordinate(QVector &eventData, int Board, int Channel); + QMap handleSubordinate(QVector &eventData,int Board, int Channel); // 获取最大值 int getMaxValue(); //计算全部的初级粒子范围 和 次级粒子范围 - void calculateFirstSecondRange(); + void calculateFirstSecondRange(const QVector& data, + double& firstStart, double& firstEnd, + double& secondStart, double& secondEnd); //设置板卡数据信息 void setAllBoardData(); //设置符合事件相关信息 void setThreeUiData(); //全部谱图数据处理 - void generateSurfaceData(const QVector events); + QVector generateSurfaceData(const QVector events); + + // 数据保存/加载核心方法(按符合数分文件) + void loadHistoryFromFile(); + void saveHistoryToFile(); + // 流式写入JSON(零大小限制) + void saveJsonStream(int conformCount, const ConformityHistoryItem& item); + // 二进制保存超大曲面数据 + void saveSurfaceDataToBinary(int conformCount, const QVector& data); + // 仅保存指定符合数的历史记录到对应文件 + void saveSingleHistoryToFile(int conformCount, const ConformityHistoryItem& item); + int saveCurrentAnalysisToHistory(int conformCount); + // 根据符合数和文件名查找历史记录 + int findHistoryIndex(const QString& fileName, int conformCount) const; + + // 后台解析所有符合数数据(单线程串行,内存稳定) + void parseAllConformDataInBackground(); + // 流式解析并计算单个符合数,内存占用<10MB + ConformityCalculatedResult streamParseAndCalculate(int conformCount, const QString& fileName); + // 显示指定符合数的数据(优先从历史/预计算缓存加载) + void displayConformData(int conformCount); + // 释放所有缓存数据 + void clearAllCachedData(); + // 读取csv文件(仅用于点击板卡时按需加载) + QVector readCsv(const QString& fileName); + // 释放事件数组内存 + void freeEventVector(QVector& vec); private: Ui::ConformityAnalysis *ui; - QString m_fileName; - -// std::vector m_CoincidenceEventVector;//所有的能谱符合处理 - int m_boardChannel[MAX_BOARD][MAX_CHANNEL]; - - QVector _spectrumDataList;//所有的能谱符合处理 + // 当前显示的符合数数据(仅保留当前显示的原始数据) + QVector _currentSpectrumData; double m_dFirstStart = 0.0;//初级粒子起始能量 double m_dFirstEnd = 0.0;//初级粒子终止能量 - double m_dSecondStart = 0.0;//初级粒子起始能量 - double m_dSecondEnd = 0.0;//初级粒子终止能量 + double m_dSecondStart = 0.0;//次级粒子起始能量 + double m_dSecondEnd = 0.0;//次级粒子终止能量 int m_iComply = 0;//符合事件总计数 + int m_currentConformCount = 2; // 当前选中的符合粒子数(默认2重) + + // 数据持久化相关成员变量 + QString _workspace; // 项目工作空间路径 + QList _conformityHistoryList; // 所有历史记录列表 + + // 预计算结果缓存(仅保存计算后的数据,内存占用极小) + QMap m_calculatedCache; + QMutex m_cacheMutex; // 缓存访问互斥锁 + QMutex m_historyMutex; // 历史记录访问互斥锁 + QFutureWatcher* m_parseWatcher = nullptr; // 后台解析监视器 + QMap m_conformFileMap; // 符合数->文件路径映射 + bool m_isParsing = false; // 是否正在解析 }; - - - -#endif // CONFORMITYANALYSIS_H +#endif // CONFORMITYANALYSIS_H \ No newline at end of file diff --git a/src/ThreeDimensionalConformityAnalysisView/ConformityAnalysis.ui b/src/ThreeDimensionalConformityAnalysisView/ConformityAnalysis.ui index c053dbe..bd3dc58 100644 --- a/src/ThreeDimensionalConformityAnalysisView/ConformityAnalysis.ui +++ b/src/ThreeDimensionalConformityAnalysisView/ConformityAnalysis.ui @@ -13,140 +13,9 @@ ConformityAnalysis - + - - - 6 - - - 6 - - - - - 通过在下图中框选区域,自动识别出符合时间进入下方列表,选择符合时间右侧界面内进行统计 - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - 符合时间窗: - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - - 50 - - - - - - - ns - - - - - - - - 0 - 26 - - - - - - - 修改 - - - - - - - 选择符合事件: - - - - - - - - 100 - 22 - - - - - 二次符合 - - - - - 三次符合 - - - - - 四次符合 - - - - - 五次符合 - - - - - 六次符合 - - - - - 七次符合 - - - - - 八次符合 - - - - - 九次符合 - - - - - - - - - - + 3 diff --git a/src/ThreeDimensionalConformityAnalysisView/DetectorStatusSummary.cpp b/src/ThreeDimensionalConformityAnalysisView/DetectorStatusSummary.cpp index 2776264..f27d3ba 100644 --- a/src/ThreeDimensionalConformityAnalysisView/DetectorStatusSummary.cpp +++ b/src/ThreeDimensionalConformityAnalysisView/DetectorStatusSummary.cpp @@ -2,11 +2,25 @@ #include "ui_DetectorStatusSummary.h" #include #include + DetectorStatusSummary::DetectorStatusSummary(QWidget *parent) : QWidget(parent), ui(new Ui::DetectorStatusSummary) { ui->setupUi(this); + + // 关键:设置所有子控件背景透明,让父控件背景显示出来 + setAttribute(Qt::WA_StyledBackground, false); + ui->label_name->setAttribute(Qt::WA_TranslucentBackground); + ui->label->setAttribute(Qt::WA_TranslucentBackground); + ui->label_num->setAttribute(Qt::WA_TranslucentBackground); + ui->label_begin_second->setAttribute(Qt::WA_TranslucentBackground); + ui->label_begin_second_num->setAttribute(Qt::WA_TranslucentBackground); + ui->widget->setAttribute(Qt::WA_TranslucentBackground); + ui->widget_dw->setAttribute(Qt::WA_TranslucentBackground); + + // 初始化默认背景色 + m_backgroundColor = QColor("#0E508A"); } DetectorStatusSummary::~DetectorStatusSummary() @@ -14,6 +28,7 @@ DetectorStatusSummary::~DetectorStatusSummary() delete ui; } +// ========== 原有接口(仅修改样式表相关部分) ========== void DetectorStatusSummary::setName(QString name) { ui->label_name->setText(name); @@ -43,7 +58,6 @@ void DetectorStatusSummary::setBeginSecond(QString str) void DetectorStatusSummary::setBeginSecondNum(int num) { ui->label_begin_second_num->setText(QString::number(num)); - } QString DetectorStatusSummary::getBeginSecondNum() @@ -53,16 +67,8 @@ QString DetectorStatusSummary::getBeginSecondNum() void DetectorStatusSummary::setBeginSecondWidget(bool isHide) { - if (isHide) - { - ui->label_begin_second->hide(); - ui->label_begin_second_num->hide(); - } - else - { - ui->label_begin_second->show(); - ui->label_begin_second_num->show(); - } + ui->label_begin_second->setVisible(!isHide); + ui->label_begin_second_num->setVisible(!isHide); } void DetectorStatusSummary::setColorMaxValue(int maxValue) @@ -70,15 +76,10 @@ void DetectorStatusSummary::setColorMaxValue(int maxValue) m_nMaxValue = maxValue; } +// 关键:移除所有硬编码的样式表背景色 void DetectorStatusSummary::setInitWidgetColor() { - QPainter p(this); - QString strqs = "min-height:35px;max-height:35px;"; - p.fillRect(rect(), QColor("#0E508A")); // 仅自己区域 - ui->label_name->setStyleSheet("background-color: #1E79C2;color:#FFFFFF;"+strqs); - ui->label->setStyleSheet("color:#4CA9F9;"); - ui->label_num->setStyleSheet("background-color: #125997;color:#FFFFFF;font-size: 30px; font-family: Microsoft YaHei; "); - ui->widget_dw->setStyleSheet("background-color: #1B66A7;color:#FFFFFF;" + strqs); + m_backgroundColor = QColor("#0E508A"); m_useCustomColor = false; update(); } @@ -92,118 +93,87 @@ void DetectorStatusSummary::setHideBorder() void DetectorStatusSummary::setBackgroundColor(const QColor &color) { m_backgroundColor = color; - //QPainter painter(this); - //painter.setRenderHint(QPainter::Antialiasing); - - ////// 创建渐变背景 - ////QLinearGradient gradient(0, 0, width(), 0); - ////gradient.setColorAt(0.0, m_backgroundColor.lighter(110)); - ////gradient.setColorAt(0.5, m_backgroundColor); - ////gradient.setColorAt(1.0, m_backgroundColor.darker(110)); - - //// 绘制背景 - //painter.fillRect(rect(), m_backgroundColor); - //QString strColor = QString("rgb(%1,%2,%3,1)").arg(m_backgroundColor.red()).arg(m_backgroundColor.green()).arg(m_backgroundColor.blue()); - //ui.widget_dw->setStyleSheet(QString("background-color: %1;color:#FFFFFF;").arg(strColor)); - - //// 绘制边框 - //painter.setPen(QPen(QColor(23, 99, 162), 1)); - //painter.drawRect(rect().adjusted(0, 0, -1, -1)); - update(); // 触发重绘 + m_useCustomColor = true; + update(); } void DetectorStatusSummary::paintEvent(QPaintEvent *event) { Q_UNUSED(event); + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing); -// QPainter painter(this); -// painter.setRenderHint(QPainter::Antialiasing); + painter.fillRect(rect(), m_backgroundColor); -// //// 创建渐变背景 -// //QLinearGradient gradient(0, 0, width(), 0); -// //gradient.setColorAt(0.0, m_backgroundColor.lighter(110)); -// //gradient.setColorAt(0.5, m_backgroundColor); -// //gradient.setColorAt(1.0, m_backgroundColor.darker(110)); - -// // 绘制背景 -// painter.fillRect(rect(), m_backgroundColor); -// QString strColor = QString("rgb(%1,%2,%3,1)").arg(m_backgroundColor.red()).arg(m_backgroundColor.green()).arg(m_backgroundColor.blue()); -// ui->widget_dw->setStyleSheet(QString("background-color: %1;color:#FFFFFF;").arg(strColor)); - -// ui->label->setStyleSheet(QString("background-color: %1;color:#FFFFFF;").arg(strColor)); -// ui->label_num->setStyleSheet(QString("background-color: %1;color:#FFFFFF;font-size: 30px; font-family: Microsoft YaHei; ").arg(strColor)); - -// // 绘制边框 -// painter.setPen(QPen(QColor(23, 99, 162), 1)); -// painter.drawRect(rect().adjusted(0, 0, -1, -1)); + if (flag) { + painter.setPen(QPen(Qt::white, 2)); + painter.drawRect(rect().adjusted(0, 0, -1, -1)); + } } QColor DetectorStatusSummary::calculateGradientColor(double ratio) { - // 四段式渐变:蓝->蓝绿->黄->橙->红 - QColor color; + static const QColor blueScaleColors[5] = { + QColor(179, 212, 255), // 0.00 + QColor(153, 196, 255), // 0.25 + QColor( 92, 160, 255), // 0.50 + QColor( 31, 124, 255), // 0.75 + QColor( 0, 80, 220) // 1.00 + }; - if (ratio < 0.25) { - // 蓝到蓝绿 - double subRatio = ratio / 0.25; - color = QColor( - 14 + (51 - 14) * subRatio, // R: 14->51 - 80 + (102 - 80) * subRatio, // G: 80->102 - 138 + (153 - 138) * subRatio // B: 138->153 - ); - } - else if (ratio < 0.5) { - // 蓝绿到黄 - double subRatio = (ratio - 0.25) / 0.25; - color = QColor( - 51 + (244 - 51) * subRatio, // R: 51->244 - 102 + (196 - 102) * subRatio, // G: 102->196 - 153 + (34 - 153) * subRatio // B: 153->34 - ); - } - else if (ratio < 0.75) { - // 黄到橙 - double subRatio = (ratio - 0.5) / 0.25; - color = QColor( - 244 + (239 - 244) * subRatio, // R: 244->239 - 196 + (155 - 196) * subRatio, // G: 196->155 - 34 + (57 - 34) * subRatio // B: 34->57 - ); - } - else { - // 橙到红 - double subRatio = (ratio - 0.75) / 0.25; - color = QColor( - 239 + (255 - 239) * subRatio, // R: 239->255 - 155 + (51 - 155) * subRatio, // G: 155->51 - 57 + (76 - 57) * subRatio // B: 57->76 - ); - } + ratio = qBound(0.0, ratio, 1.0); + int index = static_cast(ratio * 4); // 0~4 + double subRatio = (ratio - index * 0.25) / 0.25; - return color; + if (index >= 4) + return blueScaleColors[4]; + + const QColor& c1 = blueScaleColors[index]; + const QColor& c2 = blueScaleColors[index + 1]; + return QColor( + c1.red() + static_cast((c2.red() - c1.red()) * subRatio), + c1.green() + static_cast((c2.green() - c1.green()) * subRatio), + c1.blue() + static_cast((c2.blue() - c1.blue()) * subRatio) + ); } void DetectorStatusSummary::mousePressEvent(QMouseEvent *event) { - if (event->button() == Qt::LeftButton && flag == false) - { - if ((ui->label_num->text().toInt() == 0 )&& (ui->label_begin_second_num->text().toInt() == 0)) - return; + if (event->button() == Qt::LeftButton) { + if ((ui->label_num->text().toInt() == 0) && (ui->label_begin_second_num->text().toInt() == 0)) + return; + flag = !flag; + if (flag) { ui->widget->setStyleSheet("QWidget#widget{border: 2px solid #ffffff;}"); - // 可以发出信号 emit oneclicked(objectName()); - flag = true; - } - else - { - if ((ui->label_num->text().toInt() == 0 )&& (ui->label_begin_second_num->text().toInt() == 0)) - return; + } else { ui->widget->setStyleSheet(""); - // 可以发出信号 emit twoClicked(objectName()); - flag = false; } - // 调用基类实现 - QWidget::mousePressEvent(event); + } + QWidget::mousePressEvent(event); } + +void DetectorStatusSummary::setCountColor(int count) +{ + if (m_nMaxValue <= 0) { + m_backgroundColor = QColor("#0E508A"); + update(); + return; + } + + // 防止除零错误和数值溢出 + double ratio = static_cast(count) / static_cast(m_nMaxValue); + ratio = qBound(0.0, ratio, 1.0); + + m_backgroundColor = calculateGradientColor(ratio); + m_useCustomColor = true; + update(); +} + +void DetectorStatusSummary::resetColorMaxValue(int maxValue) +{ + // 确保最大值至少为1,防止除零错误 + m_nMaxValue = qMax(1, maxValue); +} \ No newline at end of file diff --git a/src/ThreeDimensionalConformityAnalysisView/DetectorStatusSummary.h b/src/ThreeDimensionalConformityAnalysisView/DetectorStatusSummary.h index c1615c8..d0fac39 100644 --- a/src/ThreeDimensionalConformityAnalysisView/DetectorStatusSummary.h +++ b/src/ThreeDimensionalConformityAnalysisView/DetectorStatusSummary.h @@ -41,6 +41,12 @@ public: // 直接设置颜色(由ConformanceAnalysis调用) void setBackgroundColor(const QColor& color); + + // ========== 新增:粒子计数色阶专用接口 ========== + // 根据计数值自动计算并设置背景色(使用当前最大值) + void setCountColor(int count); + // 重置色阶最大值(用于自动适配模式) + void resetColorMaxValue(int maxValue); protected: // 添加paintEvent声明 void paintEvent(QPaintEvent *event) override; diff --git a/src/ThreeDimensionalConformityAnalysisView/DetectorStatusSummary.ui b/src/ThreeDimensionalConformityAnalysisView/DetectorStatusSummary.ui index 35cb7ff..4683f4f 100644 --- a/src/ThreeDimensionalConformityAnalysisView/DetectorStatusSummary.ui +++ b/src/ThreeDimensionalConformityAnalysisView/DetectorStatusSummary.ui @@ -52,7 +52,7 @@ - + color: rgb(255, 255, 255); TextLabel @@ -97,8 +97,14 @@ + + 0 + + + color: rgb(255, 255, 255); + 总计数: @@ -110,7 +116,7 @@ - + color: rgb(255, 255, 255); 0 @@ -173,6 +179,9 @@ + + color: rgb(255, 255, 255); + 次级粒子计数: @@ -183,6 +192,9 @@ + + color: rgb(255, 255, 255); + 0 diff --git a/src/ThreeDimensionalConformityAnalysisView/ParticleDataStatistics.cpp b/src/ThreeDimensionalConformityAnalysisView/ParticleDataStatistics.cpp index 4c08186..8fa6c2e 100644 --- a/src/ThreeDimensionalConformityAnalysisView/ParticleDataStatistics.cpp +++ b/src/ThreeDimensionalConformityAnalysisView/ParticleDataStatistics.cpp @@ -86,38 +86,35 @@ void ParticleDataStatistics::_slotClickedTwoBoard(QString board) void ParticleDataStatistics::setWidgetData(int board, int channel, int countNum, int secondNum) { - // 参数合法性检查(防止越界) if (board < 1 || board > MAX_BOARD || channel < 1 || channel > MAX_CHANNEL) { qWarning() << "setWidgetData: invalid board or channel:" << board << channel; return; } + DetectorStatusSummary* widget = m_widgetMap[board - 1][channel - 1]; - if (widget) - { - //当板卡号和通道号都为-1时 设置为为点击状态 - if(board == -1 && channel == -1) - { - widget->setCountName(QStringLiteral(u"总计数")); - widget->setNum(countNum); - widget->setBeginSecond(QStringLiteral(u"初级粒子计数")); - widget->setBeginSecondNum(secondNum); - return ; - } - //当板卡号和点击的板卡号相同时 设置为初级粒子 - if(board == m_bd && channel == m_ch) - { - widget->setCountName(QStringLiteral(u"初级粒子计数")); - widget->setNum(secondNum); - widget->setBeginSecond(QStringLiteral(u"总计数")); - widget->setBeginSecondNum(countNum); - } - else//否则为次级粒子 - { - widget->setCountName(QStringLiteral(u"次级粒子计数")); - widget->setNum(secondNum); - widget->setBeginSecond(QStringLiteral(u"总计数")); - widget->setBeginSecondNum(countNum); - } + if (!widget) return; + + if (board == -1 && channel == -1) { + widget->setCountName(QStringLiteral(u"总计数")); + widget->setNum(countNum); + widget->setBeginSecond(QStringLiteral(u"初级计数")); + widget->setBeginSecondNum(secondNum); + widget->setCountColor(countNum); // 确保调用 + return; + } + + if (board == m_bd && channel == m_ch) { + widget->setCountName(QStringLiteral(u"初级计数")); + widget->setNum(secondNum); + widget->setBeginSecond(QStringLiteral(u"总计数")); + widget->setBeginSecondNum(countNum); + widget->setCountColor(secondNum); // 确保调用 + } else { + widget->setCountName(QStringLiteral(u"次级计数")); + widget->setNum(secondNum); + widget->setBeginSecond(QStringLiteral(u"总计数")); + widget->setBeginSecondNum(countNum); + widget->setCountColor(secondNum); // 确保调用 } } @@ -130,8 +127,18 @@ void ParticleDataStatistics::setInitWidgetData(int board, int channel, int count } widget->setCountName(QStringLiteral(u"总计数")); widget->setNum(countNum); - widget->setBeginSecond(QStringLiteral(u"初级粒子计数")); + widget->setBeginSecond(QStringLiteral(u"初级计数")); widget->setBeginSecondNum(secondNum); - + // 设置初始总计数色阶 + widget->setCountColor(countNum); +} + +void ParticleDataStatistics::setAllWidgetColorMaxValue(int maxValue) +{ + for (int bd = 0; bd < MAX_BOARD; bd++) { + for (int ch = 0; ch < MAX_CHANNEL; ch++) { + m_widgetMap[bd][ch]->resetColorMaxValue(maxValue); + } + } } diff --git a/src/ThreeDimensionalConformityAnalysisView/ParticleDataStatistics.h b/src/ThreeDimensionalConformityAnalysisView/ParticleDataStatistics.h index 4511305..9f58d06 100644 --- a/src/ThreeDimensionalConformityAnalysisView/ParticleDataStatistics.h +++ b/src/ThreeDimensionalConformityAnalysisView/ParticleDataStatistics.h @@ -47,6 +47,7 @@ public: void handleBoard(std::vector eventData); //设置数据 void setCoincidenceEvent(const F2t9Order::CoincidenceEvent &CoincidenceEvent); + void setAllWidgetColorMaxValue(int maxValue); private: // 初始化映射表 diff --git a/src/ThreeDimensionalConformityAnalysisView/ThreeDDisplay.cpp b/src/ThreeDimensionalConformityAnalysisView/ThreeDDisplay.cpp index 02f1335..7975c63 100644 --- a/src/ThreeDimensionalConformityAnalysisView/ThreeDDisplay.cpp +++ b/src/ThreeDimensionalConformityAnalysisView/ThreeDDisplay.cpp @@ -9,6 +9,7 @@ ThreeDDisplay::ThreeDDisplay(QWidget *parent) : _init3DSurface(); // 初始化颜色渐变 initColorGradient(); + connect(ui->comboBox,SIGNAL(currentIndexChanged(int)),this,SLOT(slot_currentIndexChanged(int))); } ThreeDDisplay::~ThreeDDisplay() @@ -310,6 +311,29 @@ void ThreeDDisplay::setComplyWithEvent(double value) ui->lineEdit_count->setText(QString::number(value)); } +void ThreeDDisplay::setBasicParticle(const QString& startStr, const QString& endStr) +{ + ui->lineEdit_begin_start->setText(startStr); + ui->lineEdit_begin_end->setText(endStr); +} + +void ThreeDDisplay::setSecondParticle(const QString& startStr, const QString& endStr) +{ + ui->lineEdit_second_start->setText(startStr); + ui->lineEdit_second_end->setText(endStr); +} + +void ThreeDDisplay::setComplyWithEvent(const QString& countStr) +{ + ui->lineEdit_count->setText(countStr); +} + + +int ThreeDDisplay::getComboBoxIndex() +{ + return ui->comboBox->currentIndex(); +} + /*--------------------设置颜色---------------------------*/ // 初始化颜色渐变 void ThreeDDisplay::initColorGradient() @@ -531,3 +555,8 @@ QColor ThreeDDisplay::getCustomColor(double ratio) return QColor("#FFD4D7"); } } + +void ThreeDDisplay::slot_currentIndexChanged(int index) +{ + emit emitCurrentIndexChanged(index); +} diff --git a/src/ThreeDimensionalConformityAnalysisView/ThreeDDisplay.h b/src/ThreeDimensionalConformityAnalysisView/ThreeDDisplay.h index aec4793..4f5685d 100644 --- a/src/ThreeDimensionalConformityAnalysisView/ThreeDDisplay.h +++ b/src/ThreeDimensionalConformityAnalysisView/ThreeDDisplay.h @@ -34,7 +34,13 @@ public: void setSecondParticle(double startValue,double endValue); //设置符合事件计数 void setComplyWithEvent(double value); - //设置曲面图数据 + + void setBasicParticle(const QString& startStr, const QString& endStr); + void setSecondParticle(const QString& startStr, const QString& endStr); + void setComplyWithEvent(const QString& countStr); + + //获取几次符合 + int getComboBoxIndex(); private: void _init3DSurface(); void _addAnnotations(); @@ -64,7 +70,11 @@ private: // 高亮管理 void highlightSelectedDetector(const QString& selectedName = ""); +private slots: + void slot_currentIndexChanged(int index); +signals: + void emitCurrentIndexChanged(int index); private: Ui::ThreeDDisplay *ui; diff --git a/src/ThreeDimensionalConformityAnalysisView/ThreeDDisplay.ui b/src/ThreeDimensionalConformityAnalysisView/ThreeDDisplay.ui index 430e483..3460110 100644 --- a/src/ThreeDimensionalConformityAnalysisView/ThreeDDisplay.ui +++ b/src/ThreeDimensionalConformityAnalysisView/ThreeDDisplay.ui @@ -51,6 +51,57 @@ + + + + 选择符合事件: + + + + + + + + 二次符合 + + + + + 三次符合 + + + + + 四次符合 + + + + + 五次符合 + + + + + 六次符合 + + + + + 七次符合 + + + + + 八次符合 + + + + + 九次符合 + + + + @@ -91,7 +142,7 @@ - + 3 @@ -107,46 +158,6 @@ 40 - - - - QWidget#widget_6{ - border-bottom: 1px solid #1763a2; -} - - - - 20 - - - 20 - - - - - - - - 选择的符合事件相关信息展示: - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - @@ -166,7 +177,17 @@ - + + + + 106 + 21 + + + + + + @@ -176,7 +197,17 @@ - + + + + 106 + 21 + + + + + + @@ -232,7 +263,17 @@ - + + + + 106 + 21 + + + + + + @@ -242,7 +283,17 @@ - + + + + 106 + 21 + + + + + + @@ -304,7 +355,17 @@ - + + + + 150 + 21 + + + + + +