修改三维显示初级粒子计数和次级粒子计数问题,修复能量刻度加载时问题,修复软件标题栏问题

This commit is contained in:
anxinglong 2026-06-01 17:45:49 +08:00
parent a72dbeebbe
commit 4291065053
14 changed files with 501 additions and 113 deletions

View File

@ -69,6 +69,7 @@ void EnergyScaleForm::SetAnalyzeDataFilename(const QMap<QString, QVariant> &data
QString baseName =QString("[%1]%2").arg(dir.dirName()).arg(fileInfo.baseName());
ui->lineEdit_name->setText(baseName);
ui->lineEdit_name->setReadOnly(true);
ui->groupBox_2->hide();
ui->pBtn_SaveAs->setText(QStringLiteral(u"保存到系统"));
m_currentFilePath = data_filename;
QString errorMsg;
@ -970,7 +971,7 @@ void EnergyScaleForm::on_pBtn_SaveAs_clicked()
systemEnble = false;
// 选择保存路径
QString defaultDir = QDir(qApp->applicationDirPath()).filePath("configure/EnergyScale");
QString targetFilePath = QFileDialog::getSaveFileName(this,
targetFilePath = QFileDialog::getSaveFileName(this,
"另存为能量刻度文件",
defaultDir,
"JSON文件 (*.json);;所有文件 (*.*)");

View File

@ -11,6 +11,7 @@
#include <QMessageBox>
#include <QDoubleSpinBox>
#include <QDir>
#include <QList>
#include "DeviceParamsSaveToSysDlg.h"
static QStringList s_addr_count_list { "256", "512", "1024", "2048", "4096", "8192", "16384" };
@ -386,12 +387,17 @@ void DeviceParamsTableForm::onCfgChannelSelectBtnClicked()
int num_columns = std::sqrt(32);
if (num_columns == 0)
num_columns = 1;
QList<QCheckBox*> all_checkboxes;
QVBoxLayout* checkbox_layout = new QVBoxLayout();
QHBoxLayout* checkbox_column_layout = new QHBoxLayout();
for (int row = 0; row < 32; ++row) {
QCheckBox* check_box = new QCheckBox(QStringLiteral(u"通道%1").arg(row + 1));
check_box->setChecked(!ui->params_cfg_table->isRowHidden(row));
checkbox_column_layout->addWidget(check_box);
all_checkboxes.append(check_box);
connect(check_box, &QCheckBox::stateChanged, [this, row](int state) {
ui->params_cfg_table->setRowHidden(row, state == Qt::Unchecked);
});
@ -407,16 +413,18 @@ void DeviceParamsTableForm::onCfgChannelSelectBtnClicked()
// 全选和反选
QHBoxLayout* button_layout = new QHBoxLayout();
QPushButton* btn_all_select = new QPushButton(QString(QStringLiteral(u"全选")));
connect(btn_all_select, &QPushButton::clicked, [this]() {
connect(btn_all_select, &QPushButton::clicked, [this, all_checkboxes]() {
for (int row = 0; row < 32; ++row) {
ui->params_cfg_table->setRowHidden(row, true);
ui->params_cfg_table->setRowHidden(row, false);
all_checkboxes[row]->setChecked(true);
}
});
button_layout->addWidget(btn_all_select);
QPushButton* btn_reserve_select = new QPushButton(QString(QStringLiteral(u"反选")));
connect(btn_reserve_select, &QPushButton::clicked, [this]() {
connect(btn_reserve_select, &QPushButton::clicked, [this, all_checkboxes]() {
for (int row = 0; row < 32; ++row) {
ui->params_cfg_table->setRowHidden(row, !ui->params_cfg_table->isRowHidden(row));
all_checkboxes[row]->setChecked(!all_checkboxes[row]->isChecked());
}
});
button_layout->addWidget(btn_reserve_select);

View File

@ -6,6 +6,9 @@
#include <QFileDialog>
#include <QFileInfo>
#include <QTimer>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonValue>
#include "DataProcessWorkPool.h"
#include "GlobalDefine.h"
NewMeasureAnalysisDlg::NewMeasureAnalysisDlg(QWidget *parent)
@ -33,7 +36,9 @@ NewMeasureAnalysisDlg::~NewMeasureAnalysisDlg()
void NewMeasureAnalysisDlg::initialization()
{
ui->progressBar->setVisible(false);
ui->comboBox_measure_param->addItem(QStringLiteral(u""));
ui->comboBox_energy_scale->addItem(QStringLiteral(u""));
ui->comboBox_efficiency_scale->addItem(QStringLiteral(u""));
QRegExp rx(R"(^[^\\/:*?"<>|]+$)");
QValidator *validator = new QRegExpValidator(rx, this);
ui->lineEdit_name->setValidator(validator);
@ -173,7 +178,8 @@ void NewMeasureAnalysisDlg::newProject(const QString& particle_data_filename)
QString project_energy_scale_filename = QDir(project_dir_path).filePath(QStringLiteral(u"能量刻度.json"));
QFileInfo energy_scale_file_info(energy_scale_filename);
if (energy_scale_file_info.exists()) {
QFile::copy(energy_scale_filename, project_energy_scale_filename);
readJsonAsSave(energy_scale_filename,project_energy_scale_filename);
// QFile::copy(energy_scale_filename, project_energy_scale_filename);
}
bool is_measure_complete = !particle_data_filename.isEmpty();
@ -346,3 +352,39 @@ void NewMeasureAnalysisDlg::startParticleSortTask(const QString& data_file_path,
separate_task->SetFinishedNotifier(this, "onNewProjectFromFileFinished", project_name);
separate_task->StartTask();
}
void NewMeasureAnalysisDlg::readJsonAsSave(QString sourceFile, QString targetFile)
{
QFile file(sourceFile);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
qDebug() << "无法打开文件:" << sourceFile;
return ;
}
QByteArray jsonData = file.readAll();
file.close();
QJsonParseError parseError;
QJsonDocument doc = QJsonDocument::fromJson(jsonData, &parseError);
if (parseError.error != QJsonParseError::NoError) {
return ;
}
QJsonObject rootObj = doc.object();
if (rootObj.contains("Remark")) {
rootObj.remove("Remark");
}
QJsonDocument newDoc(rootObj);
QFile saveFile(targetFile);
if (saveFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
// 格式化输出,方便阅读
saveFile.write(newDoc.toJson(QJsonDocument::Indented));
saveFile.close();
}
}

View File

@ -23,6 +23,7 @@ private:
void startParticleSortTask(const QString& data_file_path,
const QString& project_name,
const QString& project_dir_path);
void readJsonAsSave(QString sourceFile,QString targetFile);
private slots:
void onNewProjectFromFileFinished(bool ok, const QString& project_name, const QVariant &data);
void on_btn_ok_clicked();

View File

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>550</width>
<height>278</height>
<width>631</width>
<height>314</height>
</rect>
</property>
<property name="windowTitle">

View File

@ -0,0 +1,14 @@
#include "RegionOfInterest.h"
#include "ui_RegionOfInterest.h"
RegionOfInterest::RegionOfInterest(QWidget *parent)
: QWidget(parent)
, ui(new Ui::RegionOfInterest)
{
ui->setupUi(this);
}
RegionOfInterest::~RegionOfInterest()
{
delete ui;
}

View File

@ -0,0 +1,22 @@
#ifndef REGIONOFINTEREST_H
#define REGIONOFINTEREST_H
#include <QWidget>
namespace Ui {
class RegionOfInterest;
}
class RegionOfInterest : public QWidget
{
Q_OBJECT
public:
explicit RegionOfInterest(QWidget *parent = nullptr);
~RegionOfInterest();
private:
Ui::RegionOfInterest *ui;
};
#endif // REGIONOFINTEREST_H

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>RegionOfInterest</class>
<widget class="QWidget" name="RegionOfInterest">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>711</width>
<height>505</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -38,7 +38,6 @@ QJsonObject ConformityHistoryItem::toJson() const
obj["secondaryEnergyStart"] = secondaryEnergyStart;
obj["secondaryEnergyEnd"] = secondaryEnergyEnd;
// ✅ 修复致命BUG不再无限递归嵌套
QJsonArray boardChannelJson;
for (int board = 0; board < MAX_BOARD; ++board) {
QJsonArray channelJson;
@ -55,6 +54,24 @@ QJsonObject ConformityHistoryItem::toJson() const
}
obj["firstParticleData"] = firstParticleJson;
QJsonArray matrixJson;
for (int pBoard = 0; pBoard < MAX_BOARD; ++pBoard) {
QJsonArray pChannelArray;
for (int pChannel = 0; pChannel < MAX_CHANNEL; ++pChannel) {
QJsonArray sBoardArray;
for (int sBoard = 0; sBoard < MAX_BOARD; ++sBoard) {
QJsonArray sChannelArray;
for (int sChannel = 0; sChannel < MAX_CHANNEL; ++sChannel) {
sChannelArray.append(coincidenceMatrix[pBoard][pChannel][sBoard][sChannel]);
}
sBoardArray.append(sChannelArray);
}
pChannelArray.append(sBoardArray);
}
matrixJson.append(pChannelArray);
}
obj["coincidenceMatrix"] = matrixJson;
return obj;
}
@ -70,6 +87,7 @@ ConformityHistoryItem ConformityHistoryItem::fromJson(const QJsonObject& obj)
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) {
@ -79,12 +97,30 @@ ConformityHistoryItem ConformityHistoryItem::fromJson(const QJsonObject& obj)
}
}
// 初级粒子数据
QJsonObject firstParticleJson = obj["firstParticleData"].toObject();
for (const QString& key : firstParticleJson.keys()) {
item.firstParticleData[key] = firstParticleJson[key].toInt();
}
// 新增:符合矩阵数据
memset(item.coincidenceMatrix, 0, sizeof(item.coincidenceMatrix));
QJsonArray matrixJson = obj["coincidenceMatrix"].toArray();
for (int pBoard = 0; pBoard < MAX_BOARD && pBoard < matrixJson.size(); ++pBoard) {
QJsonArray pChannelArray = matrixJson[pBoard].toArray();
for (int pChannel = 0; pChannel < MAX_CHANNEL && pChannel < pChannelArray.size(); ++pChannel) {
QJsonArray sBoardArray = pChannelArray[pChannel].toArray();
for (int sBoard = 0; sBoard < MAX_BOARD && sBoard < sBoardArray.size(); ++sBoard) {
QJsonArray sChannelArray = sBoardArray[sBoard].toArray();
for (int sChannel = 0; sChannel < MAX_CHANNEL && sChannel < sChannelArray.size(); ++sChannel) {
item.coincidenceMatrix[pBoard][pChannel][sBoard][sChannel] = sChannelArray[sChannel].toInt();
}
}
}
}
item.surfaceData.clear();
item.channelSurfaceData.clear();
return item;
}
@ -99,7 +135,9 @@ ConformityCalculatedResult ConformityHistoryItem::toCalculatedResult() const
result.secondaryEnergyStart = secondaryEnergyStart;
result.secondaryEnergyEnd = secondaryEnergyEnd;
memcpy(result.boardChannelData, boardChannelData, sizeof(boardChannelData));
memcpy(result.coincidenceMatrix, coincidenceMatrix, sizeof(coincidenceMatrix));
result.firstParticleData = firstParticleData;
result.channelSurfaceData = channelSurfaceData;
result.surfaceData = surfaceData;
return result;
}
@ -116,7 +154,9 @@ ConformityHistoryItem ConformityHistoryItem::fromCalculatedResult(const Conformi
item.secondaryEnergyStart = result.secondaryEnergyStart;
item.secondaryEnergyEnd = result.secondaryEnergyEnd;
memcpy(item.boardChannelData, result.boardChannelData, sizeof(result.boardChannelData));
memcpy(item.coincidenceMatrix, result.coincidenceMatrix, sizeof(result.coincidenceMatrix));
item.firstParticleData = result.firstParticleData;
item.channelSurfaceData = result.channelSurfaceData;
item.surfaceData = result.surfaceData;
return item;
}
@ -171,12 +211,47 @@ void ConformityAnalysis::InitViewWorkspace(const QString &project_name)
void ConformityAnalysis::SetAnalyzeDataFilename(const QMap<QString, QVariant> &data_files_set)
{
if (data_files_set.isEmpty()) return;
// if (data_files_set.isEmpty()) return;
// clearAllCachedData();
// freeEventVector(_currentSpectrumData);
// m_conformFileMap.clear();
// 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();
// }
// }
// if (m_conformFileMap.isEmpty()) {
// qWarning() << "[ConformityAnalysis] 未找到任何有效的符合数数据文件";
// return;
// }
// if (m_conformFileMap.contains(2)) {
// ConformityCalculatedResult result = streamParseAndCalculate(2, m_conformFileMap[2]);
// QMutexLocker cacheLocker(&m_cacheMutex);
// m_calculatedCache[2] = result;
// cacheLocker.unlock();
// displayConformData(2);
// QMetaObject::invokeMethod(this, [=]() {
// saveResultToHistory(result);
// }, Qt::QueuedConnection);
// }
// m_isParsing = true;
// parseAllConformDataInBackground();
if (data_files_set.isEmpty()) return;
clearAllCachedData();
freeEventVector(_currentSpectrumData);
m_conformFileMap.clear();
// 构建符合数->文件路径映射
for (const QString& key : data_files_set.keys()) {
bool ok;
int cnt = key.toInt(&ok);
@ -190,22 +265,108 @@ void ConformityAnalysis::SetAnalyzeDataFilename(const QMap<QString, QVariant> &d
return;
}
if (m_conformFileMap.contains(2)) {
// 重新加载最新的历史数据
loadHistoryFromFile();
ConformityCalculatedResult result = streamParseAndCalculate(2, m_conformFileMap[2]);
// 检查每个文件是否需要重新解析
QList<int> needParseConformCounts;
QList<int> canUseHistoryConformCounts;
QMutexLocker cacheLocker(&m_cacheMutex);
m_calculatedCache[2] = result;
cacheLocker.unlock();
for (int conformCount : m_conformFileMap.keys()) {
QString fileName = m_conformFileMap[conformCount];
QFileInfo fileInfo(fileName);
displayConformData(2);
// 查找对应的历史记录
int historyIdx = findHistoryIndex(fileName, conformCount);
QMetaObject::invokeMethod(this, [=]() {
saveResultToHistory(result);
}, Qt::QueuedConnection);
if (historyIdx >= 0 && fileInfo.exists()) {
const ConformityHistoryItem& historyItem = _conformityHistoryList[historyIdx];
// 比较文件最后修改时间和历史记录时间戳
// 如果文件修改时间早于历史记录保存时间,说明文件未变,可以使用历史数据
if (fileInfo.lastModified() <= historyItem.timestamp) {
// 直接将历史数据转换为预计算结果存入缓存
ConformityCalculatedResult result = historyItem.toCalculatedResult();
QMutexLocker cacheLocker(&m_cacheMutex);
m_calculatedCache[conformCount] = result;
canUseHistoryConformCounts.append(conformCount);
qDebug() << "[ConformityAnalysis] " << conformCount << "重符合数据使用历史缓存,跳过解析";
continue;
}
}
// 文件不存在或已修改,需要重新解析
needParseConformCounts.append(conformCount);
qDebug() << "[ConformityAnalysis] " << conformCount << "重符合数据需要重新解析";
}
m_isParsing = true;
parseAllConformDataInBackground();
// 优先显示2重符合数据如果有历史缓存
if (canUseHistoryConformCounts.contains(2)) {
displayConformData(2);
}
// 如果有需要解析的文件,启动后台解析
if (!needParseConformCounts.isEmpty()) {
m_isParsing = true;
// 先解析2重符合如果需要保证界面快速显示
if (needParseConformCounts.contains(2)) {
ConformityCalculatedResult result = streamParseAndCalculate(2, m_conformFileMap[2]);
QMutexLocker cacheLocker(&m_cacheMutex);
m_calculatedCache[2] = result;
cacheLocker.unlock();
displayConformData(2);
QMetaObject::invokeMethod(this, [=]() {
saveResultToHistory(result);
}, Qt::QueuedConnection);
needParseConformCounts.removeOne(2);
}
// 后台解析剩余的符合数
parseRemainingConformDataInBackground(needParseConformCounts);
} else {
m_isParsing = false;
qDebug() << "[ConformityAnalysis] 所有数据均使用历史缓存,无需解析";
}
}
void ConformityAnalysis::parseRemainingConformDataInBackground(const QList<int>& conformCounts)
{
if (conformCounts.isEmpty()) {
m_isParsing = false;
return;
}
QList<int> sortedCounts = conformCounts;
std::sort(sortedCounts.begin(), sortedCounts.end());
auto parseTask = [this, sortedCounts]() {
for (int conformCount : sortedCounts) {
if (m_parseWatcher && m_parseWatcher->isCanceled())
break;
QString fileName = m_conformFileMap[conformCount];
ConformityCalculatedResult result = streamParseAndCalculate(conformCount, fileName);
QMutexLocker cacheLocker(&m_cacheMutex);
m_calculatedCache[conformCount] = result;
cacheLocker.unlock();
if (conformCount == m_currentConformCount) {
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));
}
};
if (m_parseWatcher) {
m_parseWatcher->cancel();
m_parseWatcher->waitForFinished();
delete m_parseWatcher;
}
m_parseWatcher = new QFutureWatcher<void>(this);
connect(m_parseWatcher, &QFutureWatcher<void>::finished, this, &ConformityAnalysis::onAllParseFinished);
m_parseWatcher->setFuture(QtConcurrent::run(parseTask));
}
void ConformityAnalysis::parseAllConformDataInBackground()
@ -219,21 +380,15 @@ void ConformityAnalysis::parseAllConformDataInBackground()
for (int conformCount : conformCounts) {
if (m_parseWatcher && m_parseWatcher->isCanceled())
break;
QString fileName = m_conformFileMap[conformCount];
ConformityCalculatedResult result = streamParseAndCalculate(conformCount, fileName);
QMutexLocker cacheLocker(&m_cacheMutex);
m_calculatedCache[conformCount] = result;
cacheLocker.unlock();
if (conformCount == m_currentConformCount) {
QMetaObject::invokeMethod(this, "displayConformData", Qt::QueuedConnection,
Q_ARG(int, conformCount));
}
QMetaObject::invokeMethod(this, [=]() {
saveResultToHistory(result);
}, Qt::QueuedConnection);
@ -252,12 +407,14 @@ ConformityCalculatedResult ConformityAnalysis::streamParseAndCalculate(int confo
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) {
memset(result.coincidenceMatrix, 0, sizeof(result.coincidenceMatrix));
for (int board = 1; board <= MAX_BOARD; ++board) {
for (int channel = 1; channel <= MAX_CHANNEL; ++channel) {
QString key = QStringLiteral(u"widget_%1_%2").arg(board).arg(channel);
result.firstParticleData[key] = 0;
result.channelSurfaceData[key] = QVector<SurfacePoint>();
}
}
@ -266,13 +423,11 @@ ConformityCalculatedResult ConformityAnalysis::streamParseAndCalculate(int confo
double minSecondVal = std::numeric_limits<double>::max();
double maxSecondVal = 0.0;
QVector<particleCoincidenceEvent> currentEventParticles;
int currentEventId = -1;
float primaryEnergy = 0.0f;
float secondaryEnergySum = 0.0f;
int eventCount = 0;
if (!QFileInfo::exists(fileName)) {
qCritical() << "[ConformityAnalysis] CSV文件不存在" << fileName;
return result;
}
@ -294,25 +449,49 @@ ConformityCalculatedResult ConformityAnalysis::streamParseAndCalculate(int confo
particleCoincidenceEvent event;
while (reader.read_row(event.eventId, event.board, event.channel, event.energy, event.timeCounter)) {
eventCount++;
result.boardChannelData[event.board][event.channel]++;
if (event.eventId != currentEventId) {
if (currentEventId != -1) {
SurfacePoint point;
point.primaryEnergy = primaryEnergy;
point.secondaryEnergySum = secondaryEnergySum;
point.count = 1;
result.surfaceData.append(point);
if (currentEventId != -1 && !currentEventParticles.isEmpty()) {
SurfacePoint globalPoint;
const auto& primaryParticle = currentEventParticles.first();
globalPoint.primaryEnergy = static_cast<float>(primaryParticle.energy);
globalPoint.secondaryEnergySum = 0.0f;
for (int i = 1; i < currentEventParticles.size(); ++i) {
globalPoint.secondaryEnergySum += static_cast<float>(currentEventParticles[i].energy);
}
globalPoint.count = 1;
result.surfaceData.append(globalPoint);
int primaryBoard = primaryParticle.board;
int primaryChannel = primaryParticle.channel;
QString primaryKey = QStringLiteral(u"widget_%1_%2").arg(primaryBoard + 1).arg(primaryChannel + 1);
SurfacePoint channelPoint;
channelPoint.primaryEnergy = static_cast<float>(primaryParticle.energy);
channelPoint.secondaryEnergySum = 0.0f;
for (int secondaryIdx = 1; secondaryIdx < currentEventParticles.size(); ++secondaryIdx) {
const auto& secondaryParticle = currentEventParticles[secondaryIdx];
int secondaryBoard = secondaryParticle.board;
int secondaryChannel = secondaryParticle.channel;
result.coincidenceMatrix[primaryBoard][primaryChannel][secondaryBoard][secondaryChannel]++;
channelPoint.secondaryEnergySum += static_cast<float>(secondaryParticle.energy);
}
channelPoint.count = 1;
result.channelSurfaceData[primaryKey].append(channelPoint);
}
currentEventId = event.eventId;
primaryEnergy = static_cast<float>(event.energy);
secondaryEnergySum = 0.0f;
currentEventParticles.clear();
currentEventParticles.append(event);
int boardId = event.board + 1;
int channelId = event.channel + 1;
if (boardId >= 1 && boardId <= 8 && channelId >= 1 && channelId <= 4) {
if (boardId >= 1 && boardId <= MAX_BOARD && channelId >= 1 && channelId <= MAX_CHANNEL) {
QString key = QStringLiteral(u"widget_%1_%2").arg(boardId).arg(channelId);
result.firstParticleData[key]++;
}
@ -320,23 +499,48 @@ ConformityCalculatedResult ConformityAnalysis::streamParseAndCalculate(int confo
if (event.energy < minFirstVal) minFirstVal = event.energy;
if (event.energy > maxFirstVal) maxFirstVal = event.energy;
} else {
secondaryEnergySum += static_cast<float>(event.energy);
currentEventParticles.append(event);
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);
if (currentEventId != -1 && !currentEventParticles.isEmpty()) {
SurfacePoint globalPoint;
const auto& primaryParticle = currentEventParticles.first();
globalPoint.primaryEnergy = static_cast<float>(primaryParticle.energy);
globalPoint.secondaryEnergySum = 0.0f;
for (int i = 1; i < currentEventParticles.size(); ++i) {
globalPoint.secondaryEnergySum += static_cast<float>(currentEventParticles[i].energy);
}
globalPoint.count = 1;
result.surfaceData.append(globalPoint);
int primaryBoard = primaryParticle.board;
int primaryChannel = primaryParticle.channel;
QString primaryKey = QStringLiteral(u"widget_%1_%2").arg(primaryBoard + 1).arg(primaryChannel + 1);
SurfacePoint channelPoint;
channelPoint.primaryEnergy = static_cast<float>(primaryParticle.energy);
channelPoint.secondaryEnergySum = 0.0f;
for (int secondaryIdx = 1; secondaryIdx < currentEventParticles.size(); ++secondaryIdx) {
const auto& secondaryParticle = currentEventParticles[secondaryIdx];
int secondaryBoard = secondaryParticle.board;
int secondaryChannel = secondaryParticle.channel;
result.coincidenceMatrix[primaryBoard][primaryChannel][secondaryBoard][secondaryChannel]++;
channelPoint.secondaryEnergySum += static_cast<float>(secondaryParticle.energy);
}
channelPoint.count = 1;
result.channelSurfaceData[primaryKey].append(channelPoint);
}
result.surfaceData.squeeze();
for (auto& vec : result.channelSurfaceData) {
vec.squeeze();
}
} catch (const std::exception& e) {
qCritical() << "[ConformityAnalysis] CSV解析异常" << e.what();
return result;
@ -347,19 +551,18 @@ ConformityCalculatedResult ConformityAnalysis::streamParseAndCalculate(int confo
result.primaryEnergyEnd = maxFirstVal;
result.secondaryEnergyStart = (minSecondVal == std::numeric_limits<double>::max()) ? 0.0 : minSecondVal;
result.secondaryEnergyEnd = maxSecondVal;
return result;
}
void ConformityAnalysis::saveResultToHistory(const ConformityCalculatedResult& result)
{
QMutexLocker locker(&m_historyMutex);
ConformityHistoryItem item = ConformityHistoryItem::fromCalculatedResult(result);
// 1. 流式写入JSON零大小限制
// 保存JSON元数据
saveJsonStream(result.conformCount, item);
// 2. 二进制保存超大曲面数据
saveSurfaceDataToBinary(result.conformCount, item.surfaceData);
// 保存二进制曲面数据(全局+所有通道)
saveSurfaceDataToBinary(result.conformCount, item.surfaceData, item.channelSurfaceData);
}
void ConformityAnalysis::saveJsonStream(int conformCount, const ConformityHistoryItem& item)
@ -367,13 +570,11 @@ void ConformityAnalysis::saveJsonStream(int conformCount, const ConformityHistor
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");
// 强制浮点数用固定小数格式,避免科学计数法
@ -406,7 +607,6 @@ void ConformityAnalysis::saveJsonStream(int conformCount, const ConformityHistor
};
stream << "{" << '\n';
stream << " \"timestamp\": \"" << escapeJson(item.timestamp.toString(Qt::ISODate)) << "\"," << '\n';
stream << " \"dataFileName\": \"" << escapeJson(item.dataFileName) << "\"," << '\n';
stream << " \"conformCount\": " << item.conformCount << "," << '\n';
@ -416,6 +616,7 @@ void ConformityAnalysis::saveJsonStream(int conformCount, const ConformityHistor
stream << " \"secondaryEnergyStart\": " << item.secondaryEnergyStart << "," << '\n';
stream << " \"secondaryEnergyEnd\": " << item.secondaryEnergyEnd << "," << '\n';
// 板卡通道数据
stream << " \"boardChannelData\": [" << '\n';
for (int board = 0; board < MAX_BOARD; ++board) {
stream << " [";
@ -429,6 +630,7 @@ void ConformityAnalysis::saveJsonStream(int conformCount, const ConformityHistor
}
stream << " ]," << '\n';
// 初级粒子数据
stream << " \"firstParticleData\": {" << '\n';
QList<QString> keys = item.firstParticleData.keys();
for (int i = 0; i < keys.size(); ++i) {
@ -436,26 +638,55 @@ void ConformityAnalysis::saveJsonStream(int conformCount, const ConformityHistor
if (i < keys.size() - 1) stream << ",";
stream << '\n';
}
stream << " }" << '\n';
stream << " }," << '\n';
// 符合矩阵数据
stream << " \"coincidenceMatrix\": [" << '\n';
for (int pBoard = 0; pBoard < MAX_BOARD; ++pBoard) {
stream << " [" << '\n';
for (int pChannel = 0; pChannel < MAX_CHANNEL; ++pChannel) {
stream << " [";
for (int sBoard = 0; sBoard < MAX_BOARD; ++sBoard) {
stream << "[";
for (int sChannel = 0; sChannel < MAX_CHANNEL; ++sChannel) {
stream << item.coincidenceMatrix[pBoard][pChannel][sBoard][sChannel];
if (sChannel < MAX_CHANNEL - 1) stream << ", ";
}
stream << "]";
if (sBoard < MAX_BOARD - 1) stream << ", ";
}
stream << "]";
if (pChannel < MAX_CHANNEL - 1) stream << ",";
stream << '\n';
}
stream << " ]";
if (pBoard < MAX_BOARD - 1) stream << ",";
stream << '\n';
}
stream << " ]" << '\n';
stream << "}" << '\n';
file.close();
}
void ConformityAnalysis::saveSurfaceDataToBinary(int conformCount, const QVector<SurfacePoint>& data)
void ConformityAnalysis::saveSurfaceDataToBinary(int conformCount, const QVector<SurfacePoint> &globalData, const QMap<QString, QVector<SurfacePoint> > &channelData)
{
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;
// 先保存全局曲面数据
stream << globalData;
// 再保存通道曲面数据数量和内容
stream << channelData.size();
for (auto it = channelData.begin(); it != channelData.end(); ++it) {
stream << it.key() << it.value();
}
file.close();
}
@ -465,12 +696,9 @@ void ConformityAnalysis::loadHistoryFromFile()
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;
}
QFile f(jsonPath);
if (!f.open(QIODevice::ReadOnly)) {
qWarning() << "[ConformityAnalysis] 无法打开JSON文件" << jsonPath;
@ -478,7 +706,6 @@ void ConformityAnalysis::loadHistoryFromFile()
}
QJsonDocument doc = QJsonDocument::fromJson(f.readAll());
f.close();
ConformityHistoryItem item = ConformityHistoryItem::fromJson(doc.object());
if (QFile::exists(binPath)) {
@ -486,18 +713,23 @@ void ConformityAnalysis::loadHistoryFromFile()
if (binFile.open(QIODevice::ReadOnly)) {
QDataStream stream(&binFile);
stream >> item.surfaceData;
int channelCount;
stream >> channelCount;
for (int i = 0; i < channelCount; ++i) {
QString key;
QVector<SurfacePoint> data;
stream >> key >> data;
item.channelSurfaceData[key] = data;
}
binFile.close();
}
}
_conformityHistoryList.append(item);
}
}
// ==================== 单个符合数解析完成 ====================
void ConformityAnalysis::onSingleParseFinished(int conformCount)
{
qDebug() << "[ConformityAnalysis] " << conformCount << "重符合数据处理完成";
}
void ConformityAnalysis::onAllParseFinished()
@ -535,7 +767,6 @@ void ConformityAnalysis::displayConformData(int conformCount)
setAllBoardData();
setThreeUiData();
ui->widget_3D->setSurfaceData(m_surfaceData);
}
void ConformityAnalysis::slot_ConformCountChanged(int index)
@ -552,7 +783,6 @@ QVector<particleCoincidenceEvent*> ConformityAnalysis::readCsv(const QString& fi
QVector<particleCoincidenceEvent*> dataList;
if (!QFileInfo::exists(fileName)) {
qWarning() << "[ConformityAnalysis] 文件不存在:" << fileName;
return dataList;
}
@ -590,7 +820,6 @@ QVector<particleCoincidenceEvent*> ConformityAnalysis::readCsv(const QString& fi
return dataList;
}
// 释放事件数组内存
void ConformityAnalysis::freeEventVector(QVector<particleCoincidenceEvent*>& vec)
{
qDeleteAll(vec);
@ -752,46 +981,65 @@ void ConformityAnalysis::slot_ClickedBoard(int board,int channel)
if (!m_calculatedCache.contains(m_currentConformCount)) {
return;
}
QString fileName = m_calculatedCache[m_currentConformCount].dataFileName;
const ConformityCalculatedResult& result = m_calculatedCache[m_currentConformCount];
locker.unlock();
if (_currentSpectrumData.isEmpty()) {
_currentSpectrumData = readCsv(fileName);
QString primaryKey = QStringLiteral(u"widget_%1_%2").arg(board).arg(channel);
if (!result.channelSurfaceData.contains(primaryKey)) {
qWarning() << "[ConformityAnalysis] 通道数据不存在:" << primaryKey;
return;
}
QVector<particleCoincidenceEvent*> data = handleBasicSubordinate(board, channel, _currentSpectrumData);
if(data.size() <= 0) return;
const QVector<SurfacePoint>& channelSurface = result.channelSurfaceData[primaryKey];
ui->widget_3D->setSurfaceData(channelSurface);
QVector<SurfacePoint> 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;
double localFirstStart = std::numeric_limits<double>::max();
double localFirstEnd = 0.0;
double localSecondStart = std::numeric_limits<double>::max();
double localSecondEnd = 0.0;
for (const auto& point : channelSurface) {
if (point.primaryEnergy < localFirstStart) localFirstStart = point.primaryEnergy;
if (point.primaryEnergy > localFirstEnd) localFirstEnd = point.primaryEnergy;
if (point.secondaryEnergySum < localSecondStart) localSecondStart = point.secondaryEnergySum;
if (point.secondaryEnergySum > localSecondEnd) localSecondEnd = point.secondaryEnergySum;
}
m_dFirstStart = (localFirstStart == std::numeric_limits<double>::max()) ? 0.0 : localFirstStart;
m_dFirstEnd = localFirstEnd;
m_dSecondStart = localSecondStart;
m_dSecondStart = (localSecondStart == std::numeric_limits<double>::max()) ? 0.0 : localSecondStart;
m_dSecondEnd = localSecondEnd;
m_iComply = channelSurface.size();
setThreeUiData();
m_subordinate = handleSubordinate(data, board, channel);
m_subordinate.clear();
int maxSubordinate = 0;
for (int val : m_subordinate.values()) {
if (val > maxSubordinate) maxSubordinate = val;
int primaryBoardIdx = board - 1;
int primaryChannelIdx = channel - 1;
for (int sBoard = 0; sBoard < MAX_BOARD; ++sBoard) {
for (int sChannel = 0; sChannel < MAX_CHANNEL; ++sChannel) {
QString key = QStringLiteral(u"widget_%1_%2").arg(sBoard + 1).arg(sChannel + 1);
int count = result.coincidenceMatrix[primaryBoardIdx][primaryChannelIdx][sBoard][sChannel];
m_subordinate[key] = count;
if (count > maxSubordinate) maxSubordinate = count;
}
}
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();
int ch = parts[2].toInt();
ui->widget->setWidgetData(bd,ch,m_boardChannel[bd - 1][ch - 1],m_subordinate[objectName]);
for (int sBoard = 1; sBoard <= MAX_BOARD; ++sBoard) {
for (int sChannel = 1; sChannel <= MAX_CHANNEL; ++sChannel) {
if (sBoard == board && sChannel == channel) {
continue;
}
QString key = QStringLiteral(u"widget_%1_%2").arg(sBoard).arg(sChannel);
int totalCount = m_boardChannel[sBoard - 1][sChannel - 1];
int secondCount = m_subordinate[key];
ui->widget->setWidgetData(sBoard, sChannel, totalCount, secondCount);
}
}
ui->widget->setWidgetData(board,channel,m_boardChannel[board - 1][channel - 1],data.size());
int primaryTotalCount = m_boardChannel[primaryBoardIdx][primaryChannelIdx];
int primaryCount = channelSurface.size();
ui->widget->setWidgetData(board, channel, primaryTotalCount, primaryCount);
}
QMap<QString, int> ConformityAnalysis::handleSubordinate(QVector<particleCoincidenceEvent*> &eventData,int Board, int Channel)
@ -851,7 +1099,9 @@ void ConformityAnalysis::saveHistoryToFile()
int idx = findHistoryIndex(m_conformFileMap.value(conformCount), conformCount);
if (idx >= 0) {
saveJsonStream(conformCount, _conformityHistoryList[idx]);
saveSurfaceDataToBinary(conformCount, _conformityHistoryList[idx].surfaceData);
saveSurfaceDataToBinary(conformCount,
_conformityHistoryList[idx].surfaceData,
_conformityHistoryList[idx].channelSurfaceData);
}
}
}
@ -897,9 +1147,19 @@ int ConformityAnalysis::saveCurrentAnalysisToHistory(int conformCount)
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;
QString absoluteFileName = QFileInfo(fileName).absoluteFilePath();
for (int i = 0; i < _conformityHistoryList.size(); ++i) {
const ConformityHistoryItem& item = _conformityHistoryList[i];
if (item.dataFileName == fileName && item.conformCount == conformCount) {
if (QFileInfo(item.dataFileName).absoluteFilePath() == absoluteFileName
&& item.conformCount == conformCount) {
return i;
}
}

View File

@ -37,6 +37,7 @@ struct ConformityCalculatedResult {
int conformCount; // 符合粒子数
QString dataFileName; // 原始CSV文件名
int totalEvents; // 符合事件总计数
int totalParticles; // 总粒子数
double primaryEnergyStart; // 初级粒子能量范围-起始
double primaryEnergyEnd; // 初级粒子能量范围-结束
double secondaryEnergyStart; // 次级粒子能量范围-起始
@ -44,14 +45,23 @@ struct ConformityCalculatedResult {
// 板卡通道计数数据8板×4通道
int boardChannelData[MAX_BOARD][MAX_CHANNEL];
// 初级粒子计数数据
QMap<QString, int> firstParticleData;
// 三维曲面数据
// ✅ 新增:次级粒子符合矩阵 [初级板卡][初级通道][次级板卡][次级通道] = 符合次数
int coincidenceMatrix[MAX_BOARD][MAX_CHANNEL][MAX_BOARD][MAX_CHANNEL];
// ✅ 新增:每个板卡通道作为初级粒子的曲面数据
QMap<QString, QVector<SurfacePoint>> channelSurfaceData;
// 三维曲面数据(全局)
QVector<SurfacePoint> surfaceData;
// 构造函数
ConformityCalculatedResult() {
memset(boardChannelData, 0, sizeof(boardChannelData));
memset(coincidenceMatrix, 0, sizeof(coincidenceMatrix)); // 初始化符合矩阵
totalEvents = 0;
primaryEnergyStart = primaryEnergyEnd = 0.0;
secondaryEnergyStart = secondaryEnergyEnd = 0.0;
@ -75,6 +85,13 @@ struct ConformityHistoryItem {
int boardChannelData[MAX_BOARD][MAX_CHANNEL];
// 初级粒子计数数据
QMap<QString, int> firstParticleData;
// ✅ 新增:符合矩阵
int coincidenceMatrix[MAX_BOARD][MAX_CHANNEL][MAX_BOARD][MAX_CHANNEL];
// ✅ 新增:每个通道的曲面数据
QMap<QString, QVector<SurfacePoint>> channelSurfaceData;
// 三维曲面数据
QVector<SurfacePoint> surfaceData;
@ -148,7 +165,8 @@ private:
// 流式写入JSON零大小限制
void saveJsonStream(int conformCount, const ConformityHistoryItem& item);
// 二进制保存超大曲面数据
void saveSurfaceDataToBinary(int conformCount, const QVector<SurfacePoint>& data);
void saveSurfaceDataToBinary(int conformCount, const QVector<SurfacePoint>& globalData, const QMap<QString, QVector<SurfacePoint>>& channelData);
// 仅保存指定符合数的历史记录到对应文件
void saveSingleHistoryToFile(int conformCount, const ConformityHistoryItem& item);
int saveCurrentAnalysisToHistory(int conformCount);
@ -167,6 +185,9 @@ private:
QVector<particleCoincidenceEvent*> readCsv(const QString& fileName);
// 释放事件数组内存
void freeEventVector(QVector<particleCoincidenceEvent*>& vec);
// 后台解析剩余的符合数数据
void parseRemainingConformDataInBackground(const QList<int>& conformCounts);
private:
Ui::ConformityAnalysis *ui;

View File

@ -28,7 +28,6 @@ DetectorStatusSummary::~DetectorStatusSummary()
delete ui;
}
// ========== 原有接口(仅修改样式表相关部分) ==========
void DetectorStatusSummary::setName(QString name)
{
ui->label_name->setText(name);
@ -76,7 +75,6 @@ void DetectorStatusSummary::setColorMaxValue(int maxValue)
m_nMaxValue = maxValue;
}
// 关键:移除所有硬编码的样式表背景色
void DetectorStatusSummary::setInitWidgetColor()
{
m_backgroundColor = QColor("#0E508A");

View File

@ -209,12 +209,10 @@ void ThreeDDisplay::_updateSurfaceData()
// m_surface->axisZ()->setRange(minSecondary, maxSecondary);
if (flag)
{
qDebug()<< maxPrimary << maxSecondary;
m_surface->axisX()->setRange(0, maxPrimary);
m_surface->axisZ()->setRange(0, maxSecondary);
flag = false;
}
qDebug()<<getMaxCount(dataMatrix);
m_surface->axisY()->setRange(0, getMaxCount(dataMatrix));
}

View File

@ -44,7 +44,7 @@ int main(int argc, char *argv[])
// 设置应用信息
app.setApplicationName("EnergySpectrumAnalyer");
app.setApplicationVersion(QVersionNumber(1, 0, 0).toString());
app.setApplicationDisplayName(QStringLiteral(u"符合能谱测量析软件"));
app.setApplicationDisplayName(QStringLiteral(u"符合能谱测量析软件"));
app.setOrganizationName(QStringLiteral("NINT,4,4"));
QString app_desc = QStringLiteral(u"\
\

View File

@ -106,6 +106,7 @@ SOURCES += \
MeasureAnalysisProjectModel.cpp \
ParticleInjectTimeView/ParticleInjectTimeAnalysisView.cpp \
ParticleTimeDifferenceView/ParticleTimeDifferenceView.cpp \
RegionOfInterest/RegionOfInterest.cpp \
VirtualTable/CsvDataSource.cpp \
VirtualTable/SampleDataSource.cpp \
VirtualTable/VirtualTableModel.cpp \
@ -161,6 +162,7 @@ HEADERS += \
MeasureAnalysisProjectModel.h \
ParticleInjectTimeView/ParticleInjectTimeAnalysisView.h \
ParticleTimeDifferenceView/ParticleTimeDifferenceView.h \
RegionOfInterest/RegionOfInterest.h \
VirtualTable/CsvDataSource.h \
VirtualTable/DataSource.h \
VirtualTable/SampleDataSource.h \
@ -187,6 +189,7 @@ FORMS += \
MeasureDeviceParamsConfigView/DeviceParamsTableForm.ui \
ParticleCountPlotView/BatchEnergyScaleDialog.ui \
NewMeasureAnalysisDlg.ui \
RegionOfInterest/RegionOfInterest.ui \
ThreeDimensionalConformityAnalysisView/DetectorStatusSummary.ui \
ThreeDimensionalConformityAnalysisView/ParticleDataStatistics.ui \
ThreeDimensionalConformityAnalysisView/ThreeDDisplay.ui \
@ -203,3 +206,4 @@ contains(DEFINES, ENABLE_DEBUG) {
}
}