EnergySpectrumAnalyer/src/GvfToCsv/GvfToCsv.cpp

369 lines
12 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

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

#include "GvfToCsv.h"
#include <QFile>
#include <QFileInfo>
#include <QTextStream>
#include <QEventLoop>
#include <QTimer>
#include <QDir>
#include <QJsonDocument>
#include <QJsonObject>
#include "GlobalDefine.h"
GvfToCsv::GvfToCsv(QObject *parent)
: QObject(parent)
{
connect(m_sqliteWorker.get(), &SQLiteReadWrite::operationCompleted,
this, &GvfToCsv::onSqliteOperationCompleted);
connect(m_sqliteWorker.get(), &SQLiteReadWrite::progressUpdated,
this, &GvfToCsv::conversionProgress);
connect(m_sqliteWorker.get(), &SQLiteReadWrite::logMessage,
this, [](const QString &msg) { qDebug() << "[GVF转换日志]" << msg; });
}
GvfToCsv::~GvfToCsv()
{
cleanUp();
if (m_syncEventLoop) {
delete m_syncEventLoop;
}
}
QList<ParticleData> GvfToCsv::parseParticleFrames(const QByteArray &data)
{
QList<ParticleData> particles;
const int minDataSize = GvfConst::HEADER_SIZE + GvfConst::PARTICLE_BLOCK_SIZE;
if (data.size() < minDataSize) {
qDebug() << "粒子数据长度不足,跳过解析";
return particles;
}
const quint8* rawData = reinterpret_cast<const quint8*>(data.constData());
if (rawData[0] != GvfConst::HEADER_BYTE1 || rawData[1] != GvfConst::HEADER_BYTE2) {
qDebug() << "粒子数据头部校验失败,跳过解析";
return particles;
}
const quint8* ptr = rawData + GvfConst::HEADER_SIZE;
int remaining = data.size() - GvfConst::HEADER_SIZE;
particles.reserve(remaining / GvfConst::PARTICLE_BLOCK_SIZE);
while (remaining >= GvfConst::PARTICLE_BLOCK_SIZE)
{
quint16 alignWord = qFromLittleEndian<quint16>(static_cast<const void*>(ptr));
quint8 lowByte = alignWord & 0xFF;
quint8 highByte = (alignWord >> 8) & 0xFF;
if (highByte != GvfConst::ALIGN_HIGH_BYTE) {
ptr += GvfConst::PARTICLE_BLOCK_SIZE;
remaining -= GvfConst::PARTICLE_BLOCK_SIZE;
continue;
}
const quint8 board = (lowByte >> 4) & 0x0F;
const quint8 channel = lowByte & 0x0F;
const int channelIndex = board * 4 + channel;
if (channelIndex >= GvfConst::MAX_CHANNEL_COUNT) {
ptr += GvfConst::PARTICLE_BLOCK_SIZE;
remaining -= GvfConst::PARTICLE_BLOCK_SIZE;
continue;
}
quint64 timestamp = 0;
const quint8* timestampPtr = ptr + 2;
for (int i = 0; i < 6; ++i) {
timestamp |= static_cast<quint64>(timestampPtr[i]) << (8 * i);
}
const quint16 amplitude = qFromLittleEndian<quint16>(static_cast<const void*>(ptr + 8));
const quint32 icr = qFromLittleEndian<quint32>(static_cast<const void*>(ptr + 10));
const quint16 riseTime = qFromLittleEndian<quint16>(static_cast<const void*>(ptr + 14));
const quint16 fallTime = qFromLittleEndian<quint16>(static_cast<const void*>(ptr + 16));
const int address = static_cast<int>(
static_cast<double>(amplitude) / GvfConst::AMPLITUDE_MAX * GvfConst::MAX_ADDRESS
);
ParticleData p(
board,
channel,
timestamp,
timestamp * GvfConst::TIME_PER_COUNT,
amplitude,
address,
icr,
riseTime,
fallTime
);
particles.append(p);
ptr += GvfConst::PARTICLE_BLOCK_SIZE;
remaining -= GvfConst::PARTICLE_BLOCK_SIZE;
}
return particles;
}
void GvfToCsv::cleanUp()
{
if (m_sqliteWorker) {
// 先断开所有信号连接
disconnect(m_sqliteWorker.get(), nullptr, this, nullptr);
// 停止操作
m_sqliteWorker->stopOperation();
// 等待操作完成
QEventLoop loop;
QTimer timeoutTimer;
timeoutTimer.setSingleShot(true);
connect(m_sqliteWorker.get(), &SQLiteReadWrite::operationCompleted, &loop, &QEventLoop::quit);
connect(&timeoutTimer, &QTimer::timeout, &loop, &QEventLoop::quit);
timeoutTimer.start(3000);
loop.exec(QEventLoop::ExcludeUserInputEvents);
m_sqliteWorker.reset();
}
m_gvfPath.clear();
m_csvPath.clear();
m_lastError.clear();
}
bool GvfToCsv::convertGVF2CSVSync(const QString &gvfPath, const QString &csvPath)
{
if (!m_syncEventLoop) {
m_syncEventLoop = new QEventLoop();
}
bool result = false;
QObject signalReceiver;
connect(this, &GvfToCsv::conversionFinished, &signalReceiver, [&](bool success) {
result = success;
m_syncEventLoop->quit();
});
connect(this, &GvfToCsv::errorOccurred, &signalReceiver, [](const QString &msg) {
LOG_ERROR(QStringLiteral(u"GVF转换错误: %1").arg(msg).toUtf8().constData());
});
convertGVF2CSVAsync(gvfPath, csvPath);
m_syncEventLoop->exec();
return result;
}
void GvfToCsv::convertGVF2CSVAsync(const QString &gvfPath, const QString &csvPath)
{
m_lastError.clear();
cleanUp();
if (gvfPath.isEmpty() || csvPath.isEmpty()) {
setLastError("GVF/CSV路径不能为空");
emit conversionFinished(false);
return;
}
QFileInfo gvfFileInfo(gvfPath);
if (!gvfFileInfo.exists() || !gvfFileInfo.isFile()) {
setLastError(QString("GVF文件不存在: %1").arg(gvfPath));
emit conversionFinished(false);
return;
}
QFileInfo csvFileInfo(csvPath);
QDir csvDir = csvFileInfo.absoluteDir();
if (!csvDir.exists() && !csvDir.mkpath(".")) {
setLastError(QString("无法创建CSV目录: %1").arg(csvDir.path()));
emit conversionFinished(false);
return;
}
m_gvfPath = gvfPath;
m_csvPath = csvPath;
m_sqliteWorker = std::make_unique<SQLiteReadWrite>(this);
connect(m_sqliteWorker.get(), &SQLiteReadWrite::operationCompleted,
this, &GvfToCsv::onSqliteOperationCompleted, Qt::QueuedConnection);
connect(m_sqliteWorker.get(), &SQLiteReadWrite::progressUpdated,
this, &GvfToCsv::conversionProgress, Qt::QueuedConnection);
if (!m_sqliteWorker->openDatabase(m_gvfPath)) {
setLastError(QString("打开GVF数据库失败: %1").arg(m_gvfPath));
emit conversionFinished(false);
return;
}
m_sqliteWorker->startReadWriteOperation();
}
void GvfToCsv::onSqliteOperationCompleted(bool success, const QString &msg)
{
if (!m_sqliteWorker) {
setLastError("SQLite 工作对象已被销毁");
emit conversionFinished(false);
return;
}
if (!success) {
setLastError(QString("GVF数据读取失败: %1").arg(msg));
cleanUp();
emit conversionFinished(false);
return;
}
try {
processDBData();
emit conversionFinished(true);
} catch (const std::exception &e) {
setLastError(QString("CSV写入异常: %1").arg(e.what()));
emit conversionFinished(false);
} catch (...) {
setLastError("CSV写入未知异常");
emit conversionFinished(false);
}
cleanUp();
}
void GvfToCsv::processDBData()
{
if (!m_sqliteWorker) {
throw std::runtime_error("SQLite 工作对象已销毁,无法读取数据");
}
QVector<DataBaseStruct> dbList = m_sqliteWorker->DataBaseList();
if (dbList.isEmpty()) {
throw std::runtime_error("GVF数据库中无任何记录");
return;
}
// 从lmstatisticinfov2读取软件记录的测量启动时间精度最高
QString measureStartAbsTime = m_sqliteWorker->getMeasureStartTime();
QFile outFile(m_csvPath);
if (!outFile.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) {
throw std::runtime_error(QString("无法创建CSV文件: %1错误: %2")
.arg(m_csvPath)
.arg(outFile.errorString())
.toStdString());
}
QTextStream out(&outFile);
out.setCodec("UTF-8");
out << QStringLiteral(u"板卡号,通道号,道址,时间计数\n");
QString csvBuffer;
csvBuffer.reserve(4 * 1024 * 1024); // 4MB缓冲区
quint64 totalParticles = 0;
quint64 totalIcrSum = 0; // 所有粒子ICR输入触发总计数 Cicr
quint64 tsMin = ULLONG_MAX; // 全部粒子最小时间戳计数(用于计算总时长)
quint64 tsMax = 0; // 全部粒子最大时间戳计数
int emptyFrameCount = 0;
int processedFrames = 0;
const int totalFrames = dbList.size();
for (const auto &data : dbList) {
QList<ParticleData> particles = parseParticleFrames(data.data);
if (particles.isEmpty()) {
emptyFrameCount++;
} else {
totalParticles += particles.size();
for (const auto &p : particles) {
csvBuffer += QString("%1,%2,%3,%4\n")
.arg(p.boardId)
.arg(p.channelId)
.arg(p.address)
.arg(p.timestampCount);
//硬件输入触发ICR总和累加
totalIcrSum += p.icr;
//更新全局最小时间戳
if(p.timestampCount < tsMin) tsMin = p.timestampCount;
//更新全局最大时间戳
if(p.timestampCount > tsMax) tsMax = p.timestampCount;
}
}
if (csvBuffer.size() >= 4 * 1024 * 1024) {
out << csvBuffer;
csvBuffer.clear();
}
processedFrames++;
if (processedFrames % 1000 == 0) {
int remainingFrames = totalFrames - processedFrames;
int progress = (processedFrames * 100) / totalFrames;
emit conversionProgress(progress);
}
}
if (!csvBuffer.isEmpty()) {
out << csvBuffer;
}
double realTime = 0.0; // 实时间RealTime Tr物理测量总时长单位s
double liveTime = 0.0; // 活时间LiveTime Tl探测器有效采集时长单位s
double deadTime = 0.0; // 死时间DeadTime Td硬件阻塞无法采集时长单位s
// 存在有效粒子且时间戳有差值,才具备计算时间的条件
if(totalParticles > 0 && tsMax > tsMin)
{
// 时间戳差值 = 最大计数 - 最小计数
quint64 tsDelta = tsMax - tsMin;
// 计算公式:实时间 = 总计数差 / 时钟频率(200MHz)
realTime = static_cast<double>(tsDelta) / (GvfConst::CLOCK_FREQ_MHZ * 1e6);
// 防止ICR总和为0除零崩溃
if(totalIcrSum > 0)
{
// 有效粒子占输入触发总计数比例
double countRatio = static_cast<double>(totalParticles) / totalIcrSum;
// 活时间 = 实时间 × 有效粒子占比
liveTime = realTime * countRatio;
// 死时间 = 实时间 - 活时间
deadTime = realTime - liveTime;
}
}
QFileInfo csvFileInfo(m_csvPath);
QString jsonFilePath = csvFileInfo.absoluteDir().filePath("report_params.json");
QFile jsonFile(jsonFilePath);
QJsonObject rootObj;
rootObj["real_time_s"] = realTime; // 真时间,单位秒
rootObj["live_time_s"] = liveTime; // 活时间,单位秒
rootObj["dead_time_s"] = deadTime; // 死时间,单位秒
rootObj["start_absolute_time"] = measureStartAbsTime;
QFileInfo gvfInfo(m_gvfPath);
rootObj["gvf_file_name"] = gvfInfo.fileName();
// 生成带缩进格式化JSON文本便于阅读
QJsonDocument jsonDoc(rootObj);
QByteArray jsonContent = jsonDoc.toJson(QJsonDocument::Indented);
// 写入JSON文件覆盖原有文件
if (!jsonFile.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text))
{
throw std::runtime_error(QString("Generate report_params.json failed, path:%1 error:%2")
.arg(jsonFilePath)
.arg(jsonFile.errorString())
.toStdString());
}
jsonFile.write(jsonContent);
jsonFile.flush();
jsonFile.close();
out.flush();
outFile.close();
if (outFile.error() != QFile::NoError) {
throw std::runtime_error(QString("CSV文件写入失败: %1错误: %2")
.arg(m_csvPath)
.arg(outFile.errorString())
.toStdString());
}
if (totalParticles == 0) {
QFile::remove(m_csvPath);
throw std::runtime_error(QString("GVF文件中未解析到任何有效粒子数据空帧数: %1")
.arg(emptyFrameCount)
.toStdString());
}
emit conversionProgress(100);
}