diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 84c0b3f..674ef04 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -20,7 +20,6 @@ #include "DataProcessWorkPool.h" #include "EnergyScaleForm.h" #include "MeasureAnalysisHistoryForm.h" -#include "MeasureAnalysisProjectModel.h" #include "NewMeasureAnalysisDlg.h" #include "MeasureAnalysisView.h" #include "MeasureAnalysisTreeView.h" @@ -33,6 +32,8 @@ #include #include "MeasureClient.h" #include "MeasureAnalysisDataTableView.h" +#include "DataCalcProcess/GaussPolyCoe.h" +#include "EnergyScaleDataModel.h" #include "csv.h" #include @@ -388,6 +389,7 @@ void MainWindow::closeProject(const QString& project_name) + void MainWindow::showEvent(QShowEvent *event) { QMainWindow::showEvent(event); @@ -468,8 +470,8 @@ void MainWindow::on_action_start_measure_triggered() } MeasureAnalysisProjectModel* models = ProjectList::Instance()->GetCurrentProjectModel(); QMap > project_node_items = ProjectList::Instance()->getProjectNodeItems(); - QMap node = project_node_items[models->GetProjectName()]; - QStandardItem *nodeItem = node[models->GetProjectName()]; + nodeMap = project_node_items[models->GetProjectName()]; + QStandardItem *nodeItem = nodeMap[models->GetProjectName()]; ProjectList::Instance()->SetNodeStatus(nodeItem,"测量中",true); QString dir = ProjectList::Instance()->GetCurrentProjectModel()->GetProjectDir(); @@ -479,7 +481,7 @@ void MainWindow::on_action_start_measure_triggered() QString status = status_ok ? QStringLiteral(u"有效") : QStringLiteral(u"无效"); QVariant analys_type = QVariant::fromValue(AnalysisType::ParticleData); QString item_name = QStringLiteral(u"测量粒子数据"); - QStandardItem * particleData = node[item_name]; + QStandardItem * particleData = nodeMap[item_name]; ProjectList::Instance()->SetNodeStatus(particleData,status,true); //创建通道道址计数文件夹 const QString& all_channel_particle_data_filename = models->GetAllChannelParticleDataFilename(); @@ -601,7 +603,20 @@ void MainWindow::onRunningInfo(const QString &run_info) void MainWindow::onGvfData(const QByteArray &data) { QList particles = _gvfToCsv->parseParticleFrames(data); - if (particles.isEmpty()) { + //处理粒子数据 + changeParticleData(particles); + //处理道址计数 + changeChannelParticleCount(particles); + //处理能谱数据 + changeParticleEnergyData(particles); + //处理能量计数 + changeEnergyCountData(particles); +} + +//处理粒子数据 +void MainWindow::changeParticleData(QList &dataList) +{ + if (dataList.isEmpty()) { LOG_INFO(QStringLiteral(u"本次GVF数据未解析到有效粒子,跳过写入CSV")); return; } @@ -625,9 +640,9 @@ void MainWindow::onGvfData(const QByteArray &data) } QString csvBuffer; - csvBuffer.reserve(particles.size() * 64); // 单条数据约64字节,预分配足够内存 + csvBuffer.reserve(dataList.size() * 64); // 单条数据约64字节,预分配足够内存 - for (const auto &p : particles) { + for (const auto &p : dataList) { csvBuffer += QString("%1,%2,%3,%4\n") .arg(p.boardId) .arg(p.channelId) @@ -638,126 +653,283 @@ void MainWindow::onGvfData(const QByteArray &data) out << csvBuffer; out.flush(); // 确保数据立即写入磁盘,避免程序崩溃丢失数据 outFile.close(); - //处理粒子数据 - changeUpdata(particles); - //处理道址计数 - changeChannelParticleCount(particles); + updataTable(); } -void MainWindow::changeUpdata(QList &data) +void MainWindow::updataTable() { - QString dir = ProjectList::Instance()->GetCurrentProjectModel()->GetProjectDir(); - QString csvPath = QStringLiteral(u"%1/%2").arg(dir).arg("粒子数据.csv"); auto dockList = _dock_manager->dockWidgetsMap().values(); - int i = 0; for(auto dock : dockList) { MeasureAnalysisView* view = dynamic_cast(dock->widget()); if(!view) continue; - if(view->GetAnalyzeType() == AnalysisType::ParticleData - && view->GetViewType() == MeasureAnalysisView::DataTable) + if(view->GetViewType() == MeasureAnalysisView::DataTable) { - MeasureAnalysisDataTableView* table = dynamic_cast(view); - QString boardId = QString::number(data.at(i).boardId); - QString channelId = QString::number(data.at(i).channelId); - QString address = QString::number(data.at(i).address); - QString timestampCount = QString::number(data.at(i).timestampCount); - QStringList dataList; - dataList << boardId << channelId << address << timestampCount; - - // dataList << QString("%1,%2,%3,%4").arg(boardId).arg(channelId).arg(address).arg(timestampCount); - table->AppendRow(dataList,false); - i++; + switch(view->GetAnalyzeType()) + { + case AnalysisType::ParticleData:{ + MeasureAnalysisDataTableView* table = dynamic_cast(view); + table->AppendRow(); + };break; + // case AnalysisType::AddressCountData: + // { + // MeasureAnalysisDataTableView* table = dynamic_cast(view); + // table->AppendRow(); + // };break; + case AnalysisType::ParticleEnergyData: + { + MeasureAnalysisDataTableView* table = dynamic_cast(view); + table->AppendRow(); + };break; + case AnalysisType::EnergyCountData: + { + MeasureAnalysisDataTableView* table = dynamic_cast(view); + table->AppendRow(); + };break; + } } } } void MainWindow::changeChannelParticleCount(QList &data) { - bool ret_ok = true; + if (data.isEmpty()) { + return; + } - // 通道号 -> 地址 -> 计数 - for(auto info : data) - { - // 板卡和通道号计算,通道号 = 板卡号 * 4 + 通道号 + QHash> deltaCounts; // 通道号 -> 道址 -> 增量 + int totalParticles = 0; + + // 计算本次数据的增量计数 + for (const auto &info : data) { int channel_num = (info.boardId) * 4 + (info.channelId + 1); - // 统计每个通道的粒子计数 - if (!channel_address_counts.contains(channel_num)) { - channel_address_counts[channel_num] = QMap(); - } - channel_address_counts[channel_num][info.address]++; + deltaCounts[channel_num][info.address]++; + totalParticles++; + } + + if (deltaCounts.isEmpty()) { + return; } MeasureAnalysisProjectModel* models = ProjectList::Instance()->GetCurrentProjectModel(); + if (!models) { + LOG_ERROR(QStringLiteral(u"当前没有打开的测量项目,无法写入道址计数数据")); + return; + } + + const QString& projectName = models->GetProjectName(); const QString& every_ch_count_dir = QDir(models->GetProjectDir()).filePath(QStringLiteral(u"通道道址计数")); QDir every_ch_count_output_dir(every_ch_count_dir); - // 写入每个通道的粒子计数数据(优化:使用一次打开文件,批量写入) - QMap> channel_file_streams; - // 预创建所有通道的文件流 - for (auto channel_it = channel_address_counts.begin(); channel_it != channel_address_counts.end(); ++channel_it) { - uint channel_num = channel_it.key(); - QString count_data_filename = every_ch_count_output_dir.filePath(QStringLiteral(u"通道%1粒子计数.csv").arg(channel_num)); - particle_count_filename_list.insert(channel_num, count_data_filename); - // 创建文件流 - std::shared_ptr out(new std::ofstream(QStrToSysPath(count_data_filename))); - channel_file_streams[channel_num] = out; - *out << QString(QStringLiteral(u"道址")).toStdString() << "," << QString(QStringLiteral(u"计数")).toStdString() << std::endl; - } - // 批量写入数据 - for (auto channel_it = channel_address_counts.begin(); channel_it != channel_address_counts.end(); ++channel_it) { - uint channel_num = channel_it.key(); - const QMap& address_counts = channel_it.value(); - auto out_stream = channel_file_streams[channel_num]; - for (auto address_it = address_counts.begin(); address_it != address_counts.end(); ++address_it) { - uint address = address_it.key(); - unsigned long long count = address_it.value(); - *out_stream << address << "," << count << std::endl; + if (!every_ch_count_output_dir.exists()) { + if (!every_ch_count_output_dir.mkpath(every_ch_count_dir)) { + LOG_ERROR(QStringLiteral(u"无法创建通道道址计数目录: %1").arg(every_ch_count_dir)); + return; } } - channel_file_streams.clear(); - const QString& project_name = models->GetProjectName(); - MeasureAnalysisProjectModel* project_model = ProjectList::Instance()->GetProjectModel(project_name); - if (project_model == nullptr) { - ret_ok = false; - } else { - // 更新项目模型中的通道粒子计数数据文件名 - for (auto it = particle_count_filename_list.begin(); it != particle_count_filename_list.end(); ++it) { - project_model->SetChannelAddressCountDataFilename(it.key(), it.value()); + + QMutexLocker locker(&m_channelCountMutex); + + bool hasError = false; + QHash newChannelFiles; // 本次新增的通道文件 + + // 处理每个通道的计数更新 + for (auto channelIt = deltaCounts.constBegin(); channelIt != deltaCounts.constEnd(); ++channelIt) { + uint channel_num = channelIt.key(); + const auto& addressDeltas = channelIt.value(); + + QString count_data_filename; + if (particle_count_filename_list.contains(channel_num)) { + count_data_filename = particle_count_filename_list[channel_num]; + } else { + count_data_filename = every_ch_count_output_dir.filePath(QStringLiteral(u"通道%1粒子计数.csv").arg(channel_num)); + models->SetChannelAddressCountDataFilename(channel_num, count_data_filename); + particle_count_filename_list.insert(channel_num, count_data_filename); + newChannelFiles.insert(channel_num, count_data_filename); } - QMap > project_node_items = ProjectList::Instance()->getProjectNodeItems(); - QMap node_map = project_node_items[models->GetProjectName()]; + for (auto addressIt = addressDeltas.constBegin(); addressIt != addressDeltas.constEnd(); ++addressIt) { + uint address = addressIt.key(); + unsigned long long delta = addressIt.value(); + channel_address_counts[channel_num][address] += delta; + } + + QFile outFile(count_data_filename); + if (!outFile.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) { + LOG_ERROR(QStringLiteral(u"无法打开通道%1的计数文件: %2,错误: %3") + .arg(channel_num) + .arg(count_data_filename) + .arg(outFile.errorString())); + hasError = true; + continue; + } + + QTextStream out(&outFile); + out.setCodec("UTF-8"); + + // 写入表头 + out << QStringLiteral(u"道址,计数\n"); + + // 写入所有道址的最新总计数(按道址排序) + QString csvBuffer; + csvBuffer.reserve(channel_address_counts[channel_num].size() * 32); // 预分配内存 + + // 获取排序后的道址列表 + QList sortedAddresses = channel_address_counts[channel_num].keys(); + std::sort(sortedAddresses.begin(), sortedAddresses.end()); + + for (uint address : sortedAddresses) { + unsigned long long total = channel_address_counts[channel_num][address]; + csvBuffer += QString("%1,%2\n").arg(address).arg(total); + } + + out << csvBuffer; + out.flush(); // 确保数据立即写入磁盘 + outFile.close(); + } + + // 更新项目树节点状态 + if (!newChannelFiles.isEmpty() || !hasError) { + for (auto it = newChannelFiles.constBegin(); it != newChannelFiles.constEnd(); ++it) { + models->SetChannelAddressCountDataFilename(it.key(), it.value()); + } const QString& adrr_count_item_name = QStringLiteral(u"道址计数"); - const QMap& filename_list = models->GetChannelAddressCountDataFilenameList(); - bool status_ok = false; - QString status = QStringLiteral(u"无效"); - if (!filename_list.isEmpty()) { - status_ok = true; - status = QStringLiteral(u"有效"); - } - - if (node_map.contains(adrr_count_item_name)) { - auto adrr_count_item = node_map[adrr_count_item_name]; + if (nodeMap.contains(adrr_count_item_name)) { + auto adrr_count_item = nodeMap[adrr_count_item_name]; + bool status_ok = !particle_count_filename_list.isEmpty(); + QString status = status_ok ? QStringLiteral(u"有效") : QStringLiteral(u"无效"); ProjectList::Instance()->SetNodeStatus(adrr_count_item, status, status_ok); - for (auto it = filename_list.begin(); it != filename_list.end(); ++it) { + + for (auto it = newChannelFiles.constBegin(); it != newChannelFiles.constEnd(); ++it) { uint ch_num = it.key(); QString item_name = QStringLiteral(u"通道%1道址计数").arg(ch_num); - if(node_map.contains(item_name)) - { - return; + if (nodeMap.contains(item_name)) { + continue; // 节点已存在,跳过 } + models->SaveProjectModel(); const QVariant& analys_type = QVariant::fromValue(AnalysisType::AddressCountData); - QStandardItem* node_item = ProjectList::Instance()->AddChildNode(adrr_count_item, item_name, status, analys_type, true, status_ok); - node_item->setData(project_name, Qt::UserRole + 2); + QStandardItem* node_item = ProjectList::Instance()->AddChildNode( + adrr_count_item, item_name, status, analys_type, true, status_ok); + node_item->setData(projectName, Qt::UserRole + 2); node_item->setData(ch_num, Qt::UserRole + 3); - node_map[item_name] = node_item; + nodeMap[item_name] = node_item; } } } + // updataTable(); + + if (hasError) { + LOG_WARN(QStringLiteral(u"部分通道的道址计数数据写入失败,请检查磁盘空间和文件权限")); + } +} + +//处理粒子能量数据 +void MainWindow::changeParticleEnergyData(QList &dataList) +{ + if (dataList.isEmpty()) { + return; + } + + MeasureAnalysisProjectModel* project_model = ProjectList::Instance()->GetCurrentProjectModel(); + if (!project_model) { + LOG_ERROR(QStringLiteral(u"当前没有打开的测量项目,无法处理能谱数据")); + return; + } + + const QString& project_name = project_model->GetProjectName(); + + EnergyScaleDataModel energy_scale_data_model(project_model->GetEnergyScaleFilename()); + if (!energy_scale_data_model.LoadData()) { + LOG_WARN(QStringLiteral(u"[%1]加载能量刻度文件失败,跳过本次能谱处理").arg(project_name)); + return; + } + + if (!energy_scale_data_model.IsValid()) { + LOG_WARN(QStringLiteral(u"[%1]能量刻度数据无效,请检查刻度配置").arg(project_name)); + return; + } + + QMutexLocker locker(&m_energyDataMutex); + + QString energy_spectrum_filename = QDir(project_model->GetProjectDir()).filePath(QStringLiteral(u"能谱数据.csv")); + + QFile outFile(energy_spectrum_filename); + if (!outFile.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Append)) { + LOG_ERROR(QStringLiteral(u"[%1]无法打开能谱文件: %2,错误: %3") + .arg(project_name) + .arg(energy_spectrum_filename) + .arg(outFile.errorString())); + return; + } + + QTextStream out(&outFile); + out.setCodec("UTF-8"); + + if ( outFile.size() == 0) { + out << QStringLiteral(u"板卡号,通道号,道址,能量(KeV),时间计数\n"); + LOG_DEBUG(QStringLiteral(u"[%1]能谱文件为空,已写入标准表头").arg(project_name)); + } + + QString csvBuffer; + csvBuffer.reserve(dataList.size() * 80); // 单条数据约80字节,预分配足够内存 + + int totalProcessed = dataList.size(); + int successCount = 0; + int skipCount = 0; + + for (const auto& particle : dataList) { + int channel_num = (particle.boardId) * 4 + (particle.channelId + 1); + const QString& channel_name = QStringLiteral(u"通道%1").arg(channel_num); + + std::vector coeffs = energy_scale_data_model.GetEnergyFitResultCoeffs(channel_name); + if (coeffs.empty()) { + skipCount++; + continue; // 该通道未配置能量刻度,跳过 + } + + double energy = GaussPolyCoe::Predict(coeffs, particle.address); + + csvBuffer += QString("%1,%2,%3,%4,%5\n") + .arg(particle.boardId) + .arg(particle.channelId) + .arg(particle.address) + .arg(energy, 0, 'f', 4) + .arg(particle.timestampCount); + + successCount++; + } + + if (!csvBuffer.isEmpty()) { + out << csvBuffer; + out.flush(); // 强制写入磁盘,防止程序崩溃丢失数据 + } + + outFile.close(); + + if (successCount > 0) { + project_model->SetParticleEnergyDataFilename(energy_spectrum_filename); + + if (!project_model->SaveProjectModel()) { + LOG_WARN(QStringLiteral(u"[%1]保存项目模型失败,能谱文件路径未持久化").arg(project_name)); + } + + const QString& energy_node_name = QStringLiteral(u"粒子能量数据"); + QMap> all_nodes = ProjectList::Instance()->getProjectNodeItems(); + QMap project_nodes = all_nodes[project_name]; + + if (project_nodes.contains(energy_node_name)) { + QStandardItem* energy_node = project_nodes[energy_node_name]; + ProjectList::Instance()->SetNodeStatus(energy_node, QStringLiteral(u"有效"), true); + } + } + updataTable(); +} +//处理能量计数 +void MainWindow::changeEnergyCountData(QList &dataList) +{ } diff --git a/src/MainWindow.h b/src/MainWindow.h index 5e2d31e..ab23733 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -4,6 +4,7 @@ #include #include #include "GvfToCsv/GvfToCsv.h" +#include "MeasureAnalysisProjectModel.h" QT_BEGIN_NAMESPACE namespace Ui { @@ -52,10 +53,19 @@ private: void initStatusBar(); void applyStyleSheet(); void closeProject(const QString &project_name); + + //更新表格 + void updataTable(); + //处理粒子数据 - void changeUpdata(QList &data); + void changeParticleData(QList &dataList); //处理道址计数 void changeChannelParticleCount(QList &data); + //处理粒子能量数据 + void changeParticleEnergyData(QList &dataList); + //处理能量计数 + void changeEnergyCountData(QList &dataList); + signals: void newProject(const QString &project_name); @@ -107,8 +117,16 @@ private: QMap> channel_address_counts; // 通道号 -> 地址 -> 计数 QMap particle_count_filename_list; + //2026-06-11 + QMap nodeMap; + QMutex m_channelCountMutex;// + QMutex m_energyDataMutex;//能量谱锁 - - + //能量计数 + QHash m_channelEnergyCountFiles; // 通道号 -> 能量计数文件路径 + QHash> m_channelEnergyStats; // 通道号 -> 能量bin -> 计数 + QMap m_allChannelEnergyStats; // 全通道能量bin -> 计数 + QMutex m_energyCountMutex; // 保护能量计数数据的互斥锁 + QHash> m_energyScaleCoeffCache;// 能量刻度系数缓存 }; #endif // MAINWINDOW_H diff --git a/src/MeasureAnalysisDataTableView/MeasureAnalysisDataTableView.cpp b/src/MeasureAnalysisDataTableView/MeasureAnalysisDataTableView.cpp index dd8a336..192c53c 100644 --- a/src/MeasureAnalysisDataTableView/MeasureAnalysisDataTableView.cpp +++ b/src/MeasureAnalysisDataTableView/MeasureAnalysisDataTableView.cpp @@ -95,64 +95,21 @@ void MeasureAnalysisDataTableView::RefreshTableData(const QString &csvFilePath) _tableView->setBufferSize(_buffer_size); } -void MeasureAnalysisDataTableView::AppendRow(const QVariantList &rowData, bool writeToFile) +void MeasureAnalysisDataTableView::AppendRow(/*const QVariantList &rowData, bool writeToFile*/) { - // 1. 前置校验 - if (!_tableModel || !_tableModel->dataSource()) { - LOG_WARN(QStringLiteral(u"追加行失败:表格模型或数据源未初始化")); - return; - } auto dataSource = std::dynamic_pointer_cast(_tableModel->dataSource()); - if (!dataSource || !dataSource->isValid()) { - LOG_WARN(QStringLiteral(u"追加行失败:CSV数据源无效")); - return; - } - // 2. 列数匹配 - const int expectedColumns = _tableModel->columnCount(); - if (rowData.size() != expectedColumns) { - LOG_WARN(QStringLiteral(u"追加行失败:列数不匹配,期望%1列,实际%2列") - .arg(expectedColumns).arg(rowData.size())); - return; - } - //需要写入文件的 - if (writeToFile) { - QFile file(dataSource->filePath()); - QTextStream out(&file); - out.setCodec("UTF-8"); // 与读取时的 QString::fromUtf8 保持一致 - - QStringList escapedFields; - for (const QVariant& field : rowData) { - escapedFields.append(escapeCsvField(field.toString())); - } - - out << escapedFields.join(',') << "\n"; - file.close(); - LOG_DEBUG(QStringLiteral(u"已成功将新行写入CSV文件:%1").arg(dataSource->filePath())); - RefreshTableData(dataSource->filePath()); - return; - } - - QFile file(dataSource->filePath()); - if (!file.open(QIODevice::Append | QIODevice::Text)) { - LOG_ERROR(QStringLiteral(u"追加行失败:无法打开文件 %1,错误:%2") - .arg(file.fileName()).arg(file.errorString())); - return; - } - - // 4. 重新加载整个表格,确保模型与文件同步 RefreshTableData(dataSource->filePath()); - // 5. 自动滚动到底部 _tableView->scrollToBottom(); } -void MeasureAnalysisDataTableView::AppendRow(const QStringList &rowData, bool writeToFile) -{ - QVariantList varList; - for (const QString& field : rowData) { - varList.append(QVariant(field)); - } - AppendRow(varList, writeToFile); -} +// void MeasureAnalysisDataTableView::AppendRow(/*const QStringList &rowData, bool writeToFile*/) +// { +// // QVariantList varList; +// // for (const QString& field : rowData) { +// // varList.append(QVariant(field)); +// // } +// AppendRow(varList, writeToFile); +// } diff --git a/src/MeasureAnalysisDataTableView/MeasureAnalysisDataTableView.h b/src/MeasureAnalysisDataTableView/MeasureAnalysisDataTableView.h index 729f630..b496a9b 100644 --- a/src/MeasureAnalysisDataTableView/MeasureAnalysisDataTableView.h +++ b/src/MeasureAnalysisDataTableView/MeasureAnalysisDataTableView.h @@ -23,8 +23,8 @@ public: // 2026-06-10 表尾插入数据函数(支持两种参数类型,默认同时写入CSV文件) - void AppendRow(const QVariantList& rowData, bool writeToFile = true); - void AppendRow(const QStringList& rowData, bool writeToFile = true); + void AppendRow(/*const QVariantList& rowData, bool writeToFile = true*/); + // void AppendRow(const QStringList& rowData, bool writeToFile = true); private: // 私有成员变量 VirtualTableView *_tableView;