369 lines
12 KiB
C++
369 lines
12 KiB
C++
#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);
|
||
} |