Compare commits

..

No commits in common. "WeldSeam_1.1.0.3" and "master" have entirely different histories.

11 changed files with 118 additions and 575 deletions

View File

@ -1,33 +0,0 @@
# 点云保存与内存保护
## 自动完整保存
- 扫描开始时同时开启 `enableDebug` 和 `savePointCloud`,基类为每台相机的本次扫描创建一个文件。
- 扫描期间逐帧提交原始点云,后台线程持续写入 TXT;不分段,不等待全部数据进入内存后再保存。
- 此自动采集保存路径固定使用 TXT,不受 `storageType` 的二进制选择影响。手动保存和原有离线保存仍沿用原配置。
- 输出位置沿用 `debugOutputPath`,按应用名和日期建立目录,文件名包含设备和扫描时间。
- 正在写入的文件暂用 `.txt.part` 后缀,内容为 TXT。正常结束时排空待写队列、回填实际扫描线数,再改名为 `.txt`。
- 创建文件失败时不启动该相机的扫描;写入失败、取消或断线时保留 `.part`,不能将它视为完整扫描。异常退出留下的 `.part` 需要人工检查和清理。
- 每台相机的写入队列上限为 8 MiB,包含正在写入的扫描线。队列满时等待写线程,不静默丢帧;超过队列容量的单帧或写入错误会明确标记失败。
- TXT 格式化在后台线程完成,SDK 回调仅复制待写扫描线,不长期保存 SDK 指针。
## WeldSeam 缓存
- 不论调试保存是否开启,WeldSeam 都尝试保留 `m_detectionDataCache`,供右键手动保存使用。
- 每帧复制缓存前检查系统可用物理内存;Windows 使用 `GlobalMemoryStatusEx`,Linux 使用 `/proc/meminfo` 的 `MemAvailable`。
- 预留 1 GiB,并额外考虑下一帧、容器扩容以及 32 MiB 运行余量。继续缓存可能越过保留线时,本次扫描永久停止追加缓存,不因内存稍后恢复而重新追加。
- 内存查询失败也保守停止缓存。内存分配失败同样标记缓存不完整,并安全释放本帧尚未交给缓存的数据。
- 已缓存的前缀数据保留;后续逐帧增量算法处理和自动完整 TXT 写入不受缓存停止影响。
- 扫描/检测完成后仍保留缓存。下一次扫描或加载新数据时,清空旧缓存并重置不完整标记。
- 右键保存时,缓存不完整会提示“内存不足,导致数据不完整”,确认后才能保存部分数据;若一帧都未缓存,直接提示该原因。
- 扫描和检测期间不允许手动导出缓存,避免阻塞采集及将扫描中的前缀误认为完整数据。
- 其他项目的算法缓存策略不变,仅自动点云保存切换为采集期间写入。
## 边界与现场确认
- 1 GiB 是本程序停止增加缓存的保护线,不能保证其他进程或算法的额外分配不会让系统可用内存继续下降。
- 有限队列满时产生背压,是否影响设备帧率取决于 SDK 和硬盘吞吐;需使用实际设备、实际单帧点数确认 500 fps、连续三分钟的吞吐,不能仅凭写入逻辑承诺硬件端不丢帧。
- 正常扫描检查最终 TXT 可复检、扫描线数与接收帧数一致、帧号覆盖整次扫描。
- 低内存扫描检查缓存停止后不再增长,检测正常完成,手动保存明确提示不完整,自动 TXT 仍覆盖后续帧。
- 连续两轮扫描检查文件不覆盖、不串帧,不完整标记在新一轮清零。
- 检查普通项目单相机/多相机扫描、主动停止、取消、断线、无写权限和磁盘写满路径;失败时不能生成看似成功的完整 TXT。

View File

@ -38,7 +38,6 @@ public:
QString StopDynamicScanAndDetect();
void CancelDynamicScan();
ScanState GetScanState() const { return m_scanState.load(); }
bool IsDetectionDataCacheIncomplete() const override { return m_cacheIncomplete.load(); }
int LoadAndDetect(const QString& fileName);
void ReconnectCamera();
@ -59,13 +58,11 @@ public:
protected:
int InitAlgoParams() override;
int ProcessAlgoDetection(std::vector<std::pair<EVzResultDataType, SVzLaserLineData>>& detectionDataCache) override;
bool OnDetectionData(EVzResultDataType dataType, SVzLaserLineData* laserData) override;
int ProcessAlgoDetection(
std::vector<std::pair<EVzResultDataType, SVzLaserLineData>>& detectionDataCache) override;
bool OnDetectionData(EVzResultDataType dataType,
SVzLaserLineData* laserData) override;
bool HasPendingDetectionData() const override;
bool UsesIncrementalDetection() const override { return true; }
void OnDetectionDataCacheAllocationFailed() override;
bool ShouldCacheDetectionData(EVzResultDataType dataType,
const SVzLaserLineData& laserData) override;
void ClearDetectionDataCache() override;
EVzResultDataType GetDetectionDataType() override { return keResultDataType_Position; }
VzNL_OnNotifyStatusCBEx GetCameraStatusCallback() override;
@ -117,7 +114,6 @@ private:
WeldSeamTCPProtocol* m_pTCPServer = nullptr;
DetectPresenter* m_pDetectPresenter = nullptr;
std::atomic<ScanState> m_scanState{ScanState::Ready};
std::atomic<bool> m_cacheIncomplete{false};
std::atomic<bool> m_deinitialized{false};
bool m_bTCPConnected = false;
bool m_modbusRegistersInitialized = false;

View File

@ -12,34 +12,6 @@
#include <QFile>
#include <QMetaObject>
#include <cstring>
#ifdef Q_OS_WIN
#include <windows.h>
#endif
namespace {
quint64 AvailablePhysicalMemory()
{
#ifdef Q_OS_WIN
MEMORYSTATUSEX status{};
status.dwLength = sizeof(status);
return GlobalMemoryStatusEx(&status) ? status.ullAvailPhys : 0;
#elif defined(Q_OS_LINUX)
QFile memoryInfo(QStringLiteral("/proc/meminfo"));
if (memoryInfo.open(QIODevice::ReadOnly)) {
for (const QByteArray& line : memoryInfo.readAll().split('\n')) {
if (line.startsWith("MemAvailable:")) {
return line.mid(sizeof("MemAvailable:") - 1).simplified().split(' ').value(0).toULongLong() * 1024;
}
}
}
return 0;
#else
return 0;
#endif
}
}
WeldSeamPresenter::WeldSeamPresenter(QObject* parent)
: BasePresenter(parent)
@ -137,7 +109,6 @@ void WeldSeamPresenter::DynamicCameraStatusCallback(
auto* presenter = static_cast<WeldSeamPresenter*>(context->presenter);
switch (status) {
case keDeviceWorkStatus_Offline:
presenter->FinishDebugPointCloudStream(context->cameraIndex, false);
presenter->OnCameraStatusChanged(context->cameraIndex, false);
break;
case keDeviceWorkStatus_Eye_Reconnect:
@ -207,8 +178,6 @@ QString WeldSeamPresenter::StopDynamicScanAndDetect()
return QStringLiteral("ERROR_INTERNAL_ERROR");
}
FinishDebugPointCloudStream(m_currentCameraIndex);
// StopDetect 成功后才检查缓存。空扫描仍先返回 STOP_ACK,
// 随后把失败状态写入结果缓存,等待 GET_RET 查询。
if (!HasPendingDetectionData()) {
@ -238,7 +207,6 @@ void WeldSeamPresenter::CancelDynamicScan()
m_vrEyeDeviceList[arrayIndex].second) {
m_vrEyeDeviceList[arrayIndex].second->StopDetect();
}
FinishDebugPointCloudStream(m_currentCameraIndex, false);
ClearDetectionDataCache();
ClearCachedResult(m_currentCameraIndex);
UpdateModbusStatus(m_bCameraConnected ? 0 : 4);
@ -276,6 +244,13 @@ int WeldSeamPresenter::ProcessAlgoDetection(
int code = ERR_CODE(DEV_NOT_FIND);
if (m_pDetectPresenter && m_pConfigManager) {
const ConfigResult config = m_pConfigManager->GetConfigResult();
// Live WeldSeam scans are preprocessed line-by-line. The base cache is
// only populated in debug mode for persistence, so release that copy
// before asking DetectPresenter to consume its incremental data.
if (m_pDetectPresenter->HasIncrementalData() && !detectionDataCache.empty()) {
m_dataLoader.FreeLaserScanData(detectionDataCache);
detectionDataCache.clear();
}
code = m_pDetectPresenter->DetectWeldSeam(
m_currentCameraIndex, detectionDataCache, config.algorithmParams,
GetCameraCalibParam(m_currentCameraIndex), config.debugParam,
@ -324,44 +299,13 @@ bool WeldSeamPresenter::OnDetectionData(EVzResultDataType dataType,
GetCameraCalibParam(m_currentCameraIndex), m_dataLoader);
if (!processed) {
LOG_WARNING("WeldSeam failed to preprocess one point-cloud line\n");
return true;
}
return false;
}
bool WeldSeamPresenter::ShouldCacheDetectionData(EVzResultDataType dataType,
const SVzLaserLineData& laserData)
{
if (m_cacheIncomplete.load()) return false;
const quint64 reserveBytes = 1024ULL * 1024 * 1024;
const quint64 lineBytes = DebugDataSaver::GetLineStorageSize(dataType, laserData);
quint64 growthBytes = 0;
{
std::lock_guard<std::mutex> lock(m_detectionDataMutex);
if (m_detectionDataCache.size() == m_detectionDataCache.capacity()) {
growthBytes = 2 * (m_detectionDataCache.capacity() + 1) * sizeof(m_detectionDataCache.front());
}
}
const quint64 availableBytes = AvailablePhysicalMemory();
if (lineBytes == 0 || availableBytes <= reserveBytes + lineBytes + growthBytes + 32ULL * 1024 * 1024) {
if (!m_cacheIncomplete.exchange(true)) {
LOG_WARNING("WeldSeam cache stopped: available=%llu bytes\n", availableBytes);
OnStatusUpdate("内存不足,导致数据不完整");
}
return false;
}
// 单线处理完成后立即释放 SDK 数据,长距离扫描不再深拷贝完整点云。
return true;
}
void WeldSeamPresenter::OnDetectionDataCacheAllocationFailed()
{
if (!m_cacheIncomplete.exchange(true)) {
LOG_WARNING("WeldSeam cache stopped after allocation failure\n");
OnStatusUpdate("内存不足,导致数据不完整");
}
}
bool WeldSeamPresenter::HasPendingDetectionData() const
{
return m_pDetectPresenter && m_pDetectPresenter->HasIncrementalData();
@ -370,7 +314,6 @@ bool WeldSeamPresenter::HasPendingDetectionData() const
void WeldSeamPresenter::ClearDetectionDataCache()
{
BasePresenter::ClearDetectionDataCache();
m_cacheIncomplete.store(false);
if (m_pDetectPresenter) m_pDetectPresenter->ResetIncrementalData();
}

View File

@ -3,7 +3,7 @@
#define WELDSEAM_APP_NAME "焊缝定位"
#define WELDSEAM_VERSION_STRING "1.1.0"
#define WELDSEAM_BUILD_STRING "3"
#define WELDSEAM_BUILD_STRING "2"
#define WELDSEAM_FULL_VERSION_STRING "V" WELDSEAM_VERSION_STRING "_" WELDSEAM_BUILD_STRING
// 获取版本信息的便捷函数

View File

@ -728,11 +728,6 @@ void MainWindow::showContextMenu(const QPoint& pos, QGraphicsView* view)
}
if (m_presenter->GetDetectionDataCacheSize() == 0) {
if (m_presenter->IsDetectionDataCacheIncomplete()) {
updateStatusLog(tr("内存不足,导致数据不完整"));
QMessageBox::warning(this, tr("检测数据不完整"), tr("内存不足,导致数据不完整"));
return;
}
updateStatusLog(tr("没有检测数据可以保存"));
return;
}
@ -749,20 +744,6 @@ void MainWindow::onSaveDetectionData()
return;
}
if (m_presenter->GetScanState() != WeldSeamPresenter::ScanState::Ready) {
updateStatusLog(tr("请等待扫描和检测结束后再保存检测数据"));
return;
}
if (m_presenter->IsDetectionDataCacheIncomplete()) {
updateStatusLog(tr("内存不足,导致数据不完整"));
if (QMessageBox::warning(this, tr("检测数据不完整"),
tr("内存不足,导致数据不完整\n是否仍然保存已缓存的部分数据?"),
QMessageBox::Yes | QMessageBox::No, QMessageBox::No) != QMessageBox::Yes) {
return;
}
}
// 让用户选择保存目录
QString dirPath = QFileDialog::getExistingDirectory(this,
tr("选择保存目录"),
@ -781,9 +762,7 @@ void MainWindow::onSaveDetectionData()
// 保存数据
if (saveDetectionDataToFile(fullPath, m_presenter->GetDetectIndex())) {
updateStatusLog(m_presenter->IsDetectionDataCacheIncomplete()
? tr("内存不足,导致数据不完整,部分数据已保存到:%1").arg(fileName.c_str())
: tr("检测数据已保存到:%1").arg(fileName.c_str()));
updateStatusLog(tr("检测数据已保存到:%1").arg(fileName.c_str()));
} else {
updateStatusLog(tr("保存检测数据失败"));
}

View File

@ -24,7 +24,7 @@
outRotX="0" outRotY="0" outRotZ="14.56"/>
</HandEyeCalibMatrixs>
<DebugParam enableDebug="true" savePointCloud="true" saveDebugImage="true" printDetailLog="true" crash="false" debugOutputPath="d:/tmp/"/>
<DebugParam debugEnable="1" saveImage="0" saveCloud="0" saveResult="0"/>
<SerialConfig portName="COM6" baudRate="9600" dataBits="8" parity="0" stopBits="1"/>

View File

@ -122,8 +122,6 @@ public:
*/
int GetDetectionDataCacheSize() const;
virtual bool IsDetectionDataCacheIncomplete() const { return false; }
/**
* @brief 保存当前缓存的检测数据到文件(便捷方法)
*
@ -336,19 +334,6 @@ protected:
return GetDetectionDataCacheSize() > 0;
}
virtual bool UsesIncrementalDetection() const { return false; }
virtual void OnDetectionDataCacheAllocationFailed() {
OnStatusUpdate("内存不足,检测数据缓存失败");
}
virtual bool ShouldCacheDetectionData(EVzResultDataType dataType,
const SVzLaserLineData& laserData) {
Q_UNUSED(dataType);
Q_UNUSED(laserData);
return true;
}
/**
* @brief 获取检测数据类型(纯虚函数)
*
@ -506,9 +491,6 @@ protected:
*/
virtual std::string GetSaveDeviceTag() const { return ""; }
void FinishDebugPointCloudStream(int cameraIndex, bool complete = true);
bool HasStreamingPointCloudSave(int cameraIndex) const;
// ============ 公共实现方法 ============
/**
@ -594,8 +576,6 @@ private:
*/
int DetectTask();
bool StartDebugPointCloudStream(int cameraIndex);
// 同步覆盖保存现场点云,保存成功后才能进入算法。
int SaveCrashDataBeforeProcessing(const VrDebugParam& debugParam, const std::vector<std::pair<EVzResultDataType, SVzLaserLineData>>& detectionData);
@ -736,10 +716,6 @@ protected:
// 调试数据异步存储
DebugDataSaver m_debugDataSaver;
mutable std::mutex m_debugStreamMutex;
std::set<int> m_streamedCameras;
std::set<int> m_failedStreamCameras;
std::map<int, DebugDataSaveContext> m_streamSaveContexts;
// ============ 多相机同时扫描相关成员 ============

View File

@ -5,8 +5,6 @@
#include <condition_variable>
#include <cstdint>
#include <map>
#include <memory>
#include <fstream>
#include <mutex>
#include <queue>
#include <string>
@ -30,13 +28,6 @@ public:
void Stop();
bool StartStream(int cameraIndex, const std::string& basePath,
const std::string& timestamp, const std::string& deviceName);
bool AppendStream(int cameraIndex, EVzResultDataType dataType, const SVzLaserLineData& line);
bool FinishStream(int cameraIndex, bool complete = true);
void FinishAllStreams();
static uint64_t GetLineStorageSize(EVzResultDataType dataType, const SVzLaserLineData& line);
void SetMaxStorageSize(int64_t bytes) { m_maxStorageSize = bytes; }
void SetMaxQueueSize(int maxTasks);
void PrepareStorageCache(const std::string& basePath);
@ -45,33 +36,6 @@ public:
static std::string GenerateSavePath(const std::string& basePath, const std::string& timestamp, const std::string& deviceName = "", bool binary = false);
private:
struct StreamLine {
EVzResultDataType dataType;
SVzLaserLineData line{};
std::unique_ptr<unsigned char[]> points3D;
std::unique_ptr<unsigned char[]> points2D;
size_t bytes = 0;
};
struct StreamTask {
std::string basePath;
std::string filePath;
std::ofstream output;
std::mutex appendMutex;
std::mutex queueMutex;
std::condition_variable condition;
std::queue<std::unique_ptr<StreamLine>> lines;
size_t queuedBytes = 0;
uint64_t writtenLines = 0;
bool closing = false;
bool failed = false;
std::thread worker;
};
static void StreamWorker(const std::shared_ptr<StreamTask>& task);
std::mutex m_streamMutex;
std::map<int, std::shared_ptr<StreamTask>> m_streams;
struct SaveTask {
std::string basePath;
std::string filePath;
@ -99,11 +63,10 @@ private:
static void FreeData(std::vector<std::pair<EVzResultDataType, SVzLaserLineData>>& data);
void EnsureStorageLimit(const std::string& basePath);
void EnsureStorageLimitLocked(const std::string& basePath, StorageCache& cache,
const std::string& protectedPath = "");
void EnsureStorageLimitLocked(const std::string& basePath, StorageCache& cache);
void BuildStorageCacheLocked(const std::string& basePath, StorageCache& cache);
void AddFileToCacheLocked(StorageCache& cache, const StorageFileInfo& fileInfo);
void RecordSavedFile(const std::string& basePath, const std::string& filePath, bool protectFile = false);
void RecordSavedFile(const std::string& basePath, const std::string& filePath);
static bool RemoveFileFromCache(StorageCache& cache, const std::string& filePath);
static bool IsOlderFile(const StorageFileInfo& lhs, const StorageFileInfo& rhs);

View File

@ -2,8 +2,6 @@
#include "VrLog.h"
#include "VrError.h"
#include <fstream>
#include <cstring>
#include <new>
#include <QFile>
#include <QFileInfo>
#include <QDir>
@ -18,27 +16,6 @@
namespace {
template<typename Point3D, typename Point2D, typename CacheFunction>
void CopyLineToCache(const SVzLaserLineData& source, CacheFunction&& cache)
{
SVzLaserLineData copy = source;
std::unique_ptr<Point3D[]> points3D;
std::unique_ptr<Point2D[]> points2D;
if (source.nPointCount > 0) {
points3D = std::make_unique<Point3D[]>(source.nPointCount);
points2D = std::make_unique<Point2D[]>(source.nPointCount);
std::memcpy(points3D.get(), source.p3DPoint, sizeof(Point3D) * source.nPointCount);
if (source.p2DPoint) {
std::memcpy(points2D.get(), source.p2DPoint, sizeof(Point2D) * source.nPointCount);
}
}
copy.p3DPoint = points3D.get();
copy.p2DPoint = points2D.get();
cache(copy);
points3D.release();
points2D.release();
}
QString ResolveDebugOutputPath(const VrDebugParam& debugParam)
{
const QString configuredPath = debugParam.debugOutputPath.empty()
@ -194,7 +171,8 @@ BasePresenter::~BasePresenter()
// 处理待处理的 Qt 事件,确保 QueuedConnection 的回调执行时检查到 m_pStatusCallback 为空
QCoreApplication::processEvents();
StopDetection();
// 停止检测线程
StopAlgoDetectThread();
// 停止调试数据异步存储
m_debugDataSaver.Stop();
@ -303,9 +281,7 @@ int BasePresenter::StartDetection(int cameraIndex, bool isAuto)
CameraCallbackContext* ctx = m_cameraContexts[currentCamera];
pDevice->SetStatusCallback(statusCallback, ctx);
if (!StartDebugPointCloudStream(currentCamera)) return ERR_CODE(FILE_ERR_WRITE);
int nRet = pDevice->StartDetect(detectCallback, eDataType, ctx);
if (nRet != SUCCESS) FinishDebugPointCloudStream(currentCamera, false);
LOG_INFO("[BasePresenter] Camera %d start detection result: %d\n", currentCamera, nRet);
if (nRet == SUCCESS) {
@ -346,9 +322,7 @@ int BasePresenter::StartDetection(int cameraIndex, bool isAuto)
CameraCallbackContext* ctx = m_cameraContexts[currentCamera];
pDevice->SetStatusCallback(statusCallback, ctx);
if (!StartDebugPointCloudStream(currentCamera)) return ERR_CODE(FILE_ERR_WRITE);
int nRet = pDevice->StartDetect(detectCallback, eDataType, ctx);
if (nRet != SUCCESS) FinishDebugPointCloudStream(currentCamera, false);
LOG_INFO("[BasePresenter] Camera %d start detection result: %d\n", currentCamera, nRet);
if (nRet == SUCCESS) {
@ -418,7 +392,6 @@ int BasePresenter::StopDetection()
if (pDevice) {
int ret = pDevice->StopDetect();
if (ret == 0) {
FinishDebugPointCloudStream(static_cast<int>(i + 1));
LOG_INFO("[BasePresenter] Camera %zu stop detection successfully\n", i + 1);
} else {
LOG_WARNING("[BasePresenter] Camera %zu stop detection failed, error code: %d\n", i + 1, ret);
@ -426,8 +399,6 @@ int BasePresenter::StopDetection()
}
}
m_debugDataSaver.FinishAllStreams();
// 停止算法检测线程
StopAlgoDetectThread();
@ -477,13 +448,6 @@ int BasePresenter::SaveDetectionDataToFile(const std::string& filePath)
int BasePresenter::LoadDebugDataAndDetect(const std::string& filePath)
{
m_debugDataSaver.FinishAllStreams();
{
std::lock_guard<std::mutex> lock(m_debugStreamMutex);
m_streamedCameras.clear();
m_failedStreamCameras.clear();
m_streamSaveContexts.clear();
}
SetWorkStatus(WorkStatus::Working);
LOG_INFO("[BasePresenter] Loading debug data from file: %s\n", filePath.c_str());
@ -893,71 +857,12 @@ BasePresenter::DebugDataSaveContext BasePresenter::CreateDebugDataSaveContext(co
BasePresenter::DebugDataSaveContext BasePresenter::GetDebugDataSaveContext(const VrDebugParam& debugParam) const
{
{
std::lock_guard<std::mutex> lock(m_debugStreamMutex);
const auto entry = m_streamSaveContexts.find(m_currentCameraIndex);
if (entry != m_streamSaveContexts.end()) return entry->second;
}
return CreateDebugDataSaveContext(debugParam, m_currentCameraIndex);
}
bool BasePresenter::HasStreamingPointCloudSave(int cameraIndex) const
{
std::lock_guard<std::mutex> lock(m_debugStreamMutex);
return m_streamedCameras.count(cameraIndex) != 0;
}
bool BasePresenter::StartDebugPointCloudStream(int cameraIndex)
{
m_debugDataSaver.FinishStream(cameraIndex, false);
const VrDebugParam debugParam = GetDebugParam();
{
std::lock_guard<std::mutex> lock(m_debugStreamMutex);
m_streamedCameras.erase(cameraIndex);
m_failedStreamCameras.erase(cameraIndex);
m_streamSaveContexts.erase(cameraIndex);
if (!debugParam.enableDebug || !debugParam.savePointCloud) return true;
m_streamedCameras.insert(cameraIndex);
}
const DebugDataSaveContext context = cameraIndex == m_currentCameraIndex
? GetDebugDataSaveContext(debugParam) : CreateDebugDataSaveContext(debugParam, cameraIndex);
bool started = false;
try {
started = m_debugDataSaver.StartStream(cameraIndex, context.basePath, context.timestamp, context.deviceName);
} catch (const std::exception& error) {
LOG_ERROR("[BasePresenter] cannot start point-cloud stream: %s\n", error.what());
}
if (!started) {
{
std::lock_guard<std::mutex> lock(m_debugStreamMutex);
m_failedStreamCameras.insert(cameraIndex);
}
OnStatusUpdate("点云实时保存文件创建失败,未启动扫描");
SetWorkStatus(WorkStatus::Error);
return false;
}
{
std::lock_guard<std::mutex> lock(m_debugStreamMutex);
m_streamSaveContexts[cameraIndex] = context;
}
return true;
}
void BasePresenter::FinishDebugPointCloudStream(int cameraIndex, bool complete)
{
if (!m_debugDataSaver.FinishStream(cameraIndex, complete) && complete) {
{
std::lock_guard<std::mutex> lock(m_debugStreamMutex);
m_failedStreamCameras.insert(cameraIndex);
}
OnStatusUpdate("点云实时保存失败,文件数据不完整(保留 .part 文件)");
}
}
int BasePresenter::DetectTask()
{
LOG_INFO("[BasePresenter] DetectTask - 开始执行检测任务\n");
FinishDebugPointCloudStream(m_currentCameraIndex);
// 获取调试参数
VrDebugParam debugParam = GetDebugParam();
@ -997,15 +902,10 @@ int BasePresenter::DetectTask()
// 2. crash 模式同步覆盖保存原始输入,独立于普通点云保存开关和预处理保存策略。
int nRet = SUCCESS;
if (debugParam.enableDebug && debugParam.crash &&
!(UsesIncrementalDetection() && IsDetectionDataCacheIncomplete()))
if (debugParam.enableDebug && debugParam.crash)
{
nRet = SaveCrashDataBeforeProcessing(debugParam, localData);
}
else if (HasStreamingPointCloudSave(m_currentCameraIndex))
{
LOG_INFO("[BasePresenter] point cloud already submitted during acquisition\n");
}
else if (!SavePointCloudBeforeProcessing())
{
LOG_INFO("[BasePresenter] point-cloud debug save delegated to application preprocessing\n");
@ -1150,54 +1050,100 @@ void BasePresenter::_StaticDetectionCallback(EVzResultDataType eDataType, SVzLas
BasePresenter* pThis = ctx->presenter;
int cameraIndex = ctx->cameraIndex;
if (!pThis->m_debugDataSaver.AppendStream(cameraIndex, eDataType, *pLaserLinePoint)) {
bool firstFailure = false;
{
std::lock_guard<std::mutex> lock(pThis->m_debugStreamMutex);
firstFailure = pThis->m_failedStreamCameras.insert(cameraIndex).second;
}
if (firstFailure) pThis->OnStatusUpdate("点云实时保存失败,文件数据不完整(保留 .part 文件)");
}
// crash 模式先缓存完整原始输入,保存完成后再由算法入口处理,避免预处理提前消费现场。
const VrDebugParam debugParam = pThis->GetDebugParam();
if (!(debugParam.enableDebug && debugParam.crash && !pThis->UsesIncrementalDetection()) &&
if (!(debugParam.enableDebug && debugParam.crash) &&
pThis->OnDetectionData(eDataType, pLaserLinePoint))
{
return;
}
if (!pThis->ShouldCacheDetectionData(eDataType, *pLaserLinePoint)) {
return;
// 创建 SVzLaserLineData 副本
SVzLaserLineData lineData;
memset(&lineData, 0, sizeof(SVzLaserLineData));
// 根据数据类型分配和复制点云数据
if (eDataType == keResultDataType_Position) {
// 复制 SVzNL3DPosition 数据
if (pLaserLinePoint->p3DPoint && pLaserLinePoint->nPointCount > 0) {
lineData.p3DPoint = new SVzNL3DPosition[pLaserLinePoint->nPointCount];
if (lineData.p3DPoint) {
if(pLaserLinePoint->p3DPoint){
memcpy(lineData.p3DPoint, pLaserLinePoint->p3DPoint, sizeof(SVzNL3DPosition) * pLaserLinePoint->nPointCount);
} else {
memset(lineData.p3DPoint, 0, sizeof(SVzNL3DPosition) * pLaserLinePoint->nPointCount);
}
}
lineData.p2DPoint = new SVzNL2DPosition[pLaserLinePoint->nPointCount];
if (lineData.p2DPoint){
if(pLaserLinePoint->p2DPoint) {
memcpy(lineData.p2DPoint, pLaserLinePoint->p2DPoint, sizeof(SVzNL2DPosition) * pLaserLinePoint->nPointCount);
} else {
memset(lineData.p2DPoint, 0, sizeof(SVzNL2DPosition) * pLaserLinePoint->nPointCount);
}
}
}
} else if (eDataType == keResultDataType_PositionF) {
if (pLaserLinePoint->p3DPoint && pLaserLinePoint->nPointCount > 0) {
lineData.p3DPoint = new SVzNL3DPosition[pLaserLinePoint->nPointCount];
if (lineData.p3DPoint) {
memcpy(lineData.p3DPoint,
pLaserLinePoint->p3DPoint,
sizeof(SVzNL3DPosition) * pLaserLinePoint->nPointCount);
}
lineData.p2DPoint = new SVzNL2DPositionF[pLaserLinePoint->nPointCount];
if (lineData.p2DPoint) {
if (pLaserLinePoint->p2DPoint) {
memcpy(lineData.p2DPoint,
pLaserLinePoint->p2DPoint,
sizeof(SVzNL2DPositionF) * pLaserLinePoint->nPointCount);
} else {
memset(lineData.p2DPoint,
0,
sizeof(SVzNL2DPositionF) * pLaserLinePoint->nPointCount);
}
}
}
} else if (eDataType == keResultDataType_PointXYZRGBA) {
// 复制 SVzNLPointXYZRGBA 数据
if (pLaserLinePoint->p3DPoint && pLaserLinePoint->nPointCount > 0) {
lineData.p3DPoint = new SVzNLPointXYZRGBA[pLaserLinePoint->nPointCount];
if (lineData.p3DPoint) {
if(pLaserLinePoint->p3DPoint){
memcpy(lineData.p3DPoint, pLaserLinePoint->p3DPoint, sizeof(SVzNLPointXYZRGBA) * pLaserLinePoint->nPointCount);
} else {
memset(lineData.p3DPoint, 0, sizeof(SVzNLPointXYZRGBA) * pLaserLinePoint->nPointCount);
}
}
lineData.p2DPoint = new SVzNL2DLRPoint[pLaserLinePoint->nPointCount];
if (lineData.p2DPoint) {
if(pLaserLinePoint->p2DPoint) {
memcpy(lineData.p2DPoint, pLaserLinePoint->p2DPoint, sizeof(SVzNL2DLRPoint) * pLaserLinePoint->nPointCount);
} else {
memset(lineData.p2DPoint, 0, sizeof(SVzNL2DLRPoint) * pLaserLinePoint->nPointCount);
}
}
}
}
auto cacheLine = [pThis, cameraIndex, eDataType](const SVzLaserLineData& lineData) {
if (pThis->m_batchInProgress.load()) {
pThis->AddDetectionDataToCameraCache(cameraIndex, eDataType, lineData);
} else {
pThis->AddDetectionDataToCache(eDataType, lineData);
}
};
try {
switch (eDataType) {
case keResultDataType_Position:
CopyLineToCache<SVzNL3DPosition, SVzNL2DPosition>(*pLaserLinePoint, cacheLine);
break;
case keResultDataType_PositionF:
CopyLineToCache<SVzNL3DPosition, SVzNL2DPositionF>(*pLaserLinePoint, cacheLine);
break;
case keResultDataType_PointXYZRGBA:
CopyLineToCache<SVzNLPointXYZRGBA, SVzNL2DLRPoint>(*pLaserLinePoint, cacheLine);
break;
case keResultDataType_PointXYZI:
CopyLineToCache<SVzNLPointXYZI, SVzNL2DLRPoint>(*pLaserLinePoint, cacheLine);
break;
default:
LOG_WARNING("[BasePresenter] unsupported cache data type: %d\n", static_cast<int>(eDataType));
break;
}
} catch (const std::bad_alloc&) {
pThis->OnDetectionDataCacheAllocationFailed();
// 复制其他字段
lineData.nPointCount = pLaserLinePoint->nPointCount;
lineData.dTotleOffset = pLaserLinePoint->dTotleOffset;
lineData.dStep = pLaserLinePoint->dStep;
lineData.llTimeStamp = pLaserLinePoint->llTimeStamp;
lineData.llFrameIdx = pLaserLinePoint->llFrameIdx;
lineData.nEncodeNo = pLaserLinePoint->nEncodeNo;
lineData.fSwingAngle = pLaserLinePoint->fSwingAngle;
lineData.bEndOnceScan = pLaserLinePoint->bEndOnceScan;
// 根据扫描模式分流存储
if (pThis->m_batchInProgress.load()) {
// 多相机分批扫描模式:存入该相机的独立缓存
pThis->AddDetectionDataToCameraCache(cameraIndex, eDataType, lineData);
} else {
// 单相机模式:存入共享缓存
pThis->AddDetectionDataToCache(eDataType, lineData);
}
}
@ -1221,7 +1167,6 @@ void BasePresenter::_StaticCameraStatusCallback(EVzDeviceWorkStatus eStatus, voi
LOG_WARNING("[BasePresenter Camera Status Callback] Camera %d offline/disconnected\n", cameraIndex);
pThis->m_bCameraConnected = false;
pThis->FinishDebugPointCloudStream(cameraIndex, false);
pThis->OnCameraStatusChanged(cameraIndex, false);
break;
}
@ -1238,7 +1183,6 @@ void BasePresenter::_StaticCameraStatusCallback(EVzDeviceWorkStatus eStatus, voi
case EVzDeviceWorkStatus::keDeviceWorkStatus_Device_Swing_Finish:
{
LOG_INFO("[BasePresenter Camera Status Callback] Camera %d scan finished\n", cameraIndex);
pThis->FinishDebugPointCloudStream(cameraIndex);
// 分批模式下记录批次完成
if (pThis->m_batchInProgress.load()) {
@ -1511,14 +1455,7 @@ void BasePresenter::StartBatchScan()
CameraCallbackContext* ctx = m_cameraContexts[cameraIndex];
pDevice->SetStatusCallback(statusCallback, ctx);
int nRet = StartDebugPointCloudStream(cameraIndex)
? pDevice->StartDetect(detectCallback, eDataType, ctx) : ERR_CODE(FILE_ERR_WRITE);
if (nRet != SUCCESS) {
FinishDebugPointCloudStream(cameraIndex, false);
OnCameraScanFinished(cameraIndex);
m_algoDetectTaskPending.store(true);
m_algoDetectCondition.notify_one();
}
int nRet = pDevice->StartDetect(detectCallback, eDataType, ctx);
LOG_INFO("[BasePresenter] Camera %d start detect: %d\n", cameraIndex, nRet);
}

View File

@ -14,29 +14,9 @@
#include <algorithm>
#include <cstring>
#include <utility>
#include <chrono>
#include <iomanip>
#include <limits>
#include <stdexcept>
namespace {
std::pair<size_t, size_t> PointStorageSizes(EVzResultDataType dataType)
{
switch (dataType) {
case keResultDataType_Position:
return {sizeof(SVzNL3DPosition), sizeof(SVzNL2DPosition)};
case keResultDataType_PositionF:
return {sizeof(SVzNL3DPosition), sizeof(SVzNL2DPositionF)};
case keResultDataType_PointXYZRGBA:
return {sizeof(SVzNLPointXYZRGBA), sizeof(SVzNL2DLRPoint)};
case keResultDataType_PointXYZI:
return {sizeof(SVzNLPointXYZI), sizeof(SVzNL2DLRPoint)};
default:
return {0, 0};
}
}
QString AppName()
{
QString name = QFileInfo(QCoreApplication::applicationFilePath()).baseName();
@ -111,7 +91,6 @@ DebugDataSaver::~DebugDataSaver()
void DebugDataSaver::Stop()
{
FinishAllStreams();
if (!m_running.exchange(false)) {
return;
}
@ -154,198 +133,6 @@ void DebugDataSaver::PrepareStorageCache(const std::string& basePath)
EnsureStorageLimitLocked(basePath, cache);
}
uint64_t DebugDataSaver::GetLineStorageSize(EVzResultDataType dataType, const SVzLaserLineData& line)
{
if (line.nPointCount < 0) return 0;
const auto sizes = PointStorageSizes(dataType);
if (sizes.first == 0) return 0;
return sizeof(StreamLine) + static_cast<uint64_t>(sizes.first + sizes.second) * line.nPointCount;
}
bool DebugDataSaver::StartStream(int cameraIndex, const std::string& basePath,
const std::string& timestamp, const std::string& deviceName)
{
FinishStream(cameraIndex, false);
if (!m_running.load()) return false;
PrepareStorageCache(basePath);
auto task = std::make_shared<StreamTask>();
task->basePath = basePath;
const QString requested = QString::fromStdString(GenerateSavePath(basePath, timestamp, deviceName));
const QFileInfo info(requested);
if (!info.absoluteDir().mkpath(QStringLiteral("."))) return false;
QString path = requested;
int suffix = 0;
while (QFileInfo::exists(path) || QFileInfo::exists(path + QStringLiteral(".part"))) {
path = info.absoluteDir().filePath(info.completeBaseName() + QStringLiteral("_%1.txt").arg(++suffix));
}
task->filePath = path.toStdString();
task->output.open(task->filePath + ".part", std::ios::binary | std::ios::trunc);
task->output << "LineNum:" << std::left << std::setw(20) << 0
<< "\nDataType:0\nScanSpeed:0\nPointAdjust: 1\nMaxTimeStamp:0_0\n";
task->output.flush();
if (!task->output.good()) {
LOG_ERROR("[DebugDataSaver] stream open failed: %s\n", task->filePath.c_str());
return false;
}
{
std::lock_guard<std::mutex> lock(m_streamMutex);
m_streams[cameraIndex] = task;
try {
task->worker = std::thread(&DebugDataSaver::StreamWorker, task);
} catch (const std::exception& error) {
m_streams.erase(cameraIndex);
LOG_ERROR("[DebugDataSaver] stream worker failed: %s\n", error.what());
return false;
}
}
LOG_INFO("[DebugDataSaver] streaming TXT started: %s\n", task->filePath.c_str());
return true;
}
bool DebugDataSaver::AppendStream(int cameraIndex, EVzResultDataType dataType, const SVzLaserLineData& line)
{
std::shared_ptr<StreamTask> task;
std::unique_lock<std::mutex> appendLock;
{
std::lock_guard<std::mutex> lock(m_streamMutex);
const auto entry = m_streams.find(cameraIndex);
if (entry == m_streams.end()) return true;
task = entry->second;
appendLock = std::unique_lock<std::mutex>(task->appendMutex);
}
try {
const uint64_t bytes = GetLineStorageSize(dataType, line);
const size_t maxQueuedBytes = 8 * 1024 * 1024;
if (bytes == 0 || bytes > maxQueuedBytes || (line.nPointCount > 0 && !line.p3DPoint)) {
throw std::runtime_error("Invalid point-cloud line");
}
std::unique_lock<std::mutex> lock(task->queueMutex);
task->condition.wait(lock, [&]() {
return task->closing || task->failed || task->queuedBytes + bytes <= maxQueuedBytes;
});
if (task->closing || task->failed) return false;
lock.unlock();
auto copy = std::make_unique<StreamLine>();
copy->dataType = dataType;
copy->line = line;
copy->line.p3DPoint = nullptr;
copy->line.p2DPoint = nullptr;
copy->bytes = static_cast<size_t>(bytes);
const auto sizes = PointStorageSizes(dataType);
if (line.nPointCount > 0) {
const size_t bytes3D = sizes.first * line.nPointCount;
const size_t bytes2D = sizes.second * line.nPointCount;
copy->points3D = std::make_unique<unsigned char[]>(bytes3D);
std::memcpy(copy->points3D.get(), line.p3DPoint, bytes3D);
copy->points2D = std::make_unique<unsigned char[]>(bytes2D);
if (line.p2DPoint) std::memcpy(copy->points2D.get(), line.p2DPoint, bytes2D);
copy->line.p3DPoint = copy->points3D.get();
copy->line.p2DPoint = copy->points2D.get();
}
lock.lock();
if (task->closing || task->failed) return false;
task->lines.push(std::move(copy));
task->queuedBytes += bytes;
task->condition.notify_all();
return true;
} catch (const std::exception& error) {
std::lock_guard<std::mutex> lock(task->queueMutex);
task->failed = true;
task->condition.notify_all();
LOG_ERROR("[DebugDataSaver] stream enqueue failed: %s\n", error.what());
return false;
}
}
void DebugDataSaver::StreamWorker(const std::shared_ptr<StreamTask>& task)
{
try {
auto lastFlush = std::chrono::steady_clock::now();
while (true) {
std::unique_ptr<StreamLine> line;
{
std::unique_lock<std::mutex> lock(task->queueMutex);
task->condition.wait_for(lock, std::chrono::milliseconds(500), [&]() {
return task->closing || task->failed || !task->lines.empty();
});
if (task->lines.empty() && (task->closing || task->failed)) break;
if (!task->lines.empty()) {
line = std::move(task->lines.front());
task->lines.pop();
}
}
if (line && !LaserDataLoader::WriteLaserScanLine(task->output, line->dataType, line->line)) {
throw std::runtime_error("Point-cloud TXT write failed");
}
const auto now = std::chrono::steady_clock::now();
if (now - lastFlush >= std::chrono::milliseconds(500)) {
task->output.flush();
lastFlush = now;
}
std::lock_guard<std::mutex> lock(task->queueMutex);
if (line) task->queuedBytes -= line->bytes;
if (!task->output.good()) {
task->failed = true;
task->condition.notify_all();
LOG_ERROR("[DebugDataSaver] stream write failed: %s\n", task->filePath.c_str());
break;
}
if (line) ++task->writtenLines;
task->condition.notify_all();
}
} catch (const std::exception& error) {
std::lock_guard<std::mutex> lock(task->queueMutex);
task->failed = true;
task->condition.notify_all();
LOG_ERROR("[DebugDataSaver] stream worker failed: %s\n", error.what());
}
}
bool DebugDataSaver::FinishStream(int cameraIndex, bool complete)
{
std::shared_ptr<StreamTask> task;
{
std::lock_guard<std::mutex> lock(m_streamMutex);
const auto entry = m_streams.find(cameraIndex);
if (entry == m_streams.end()) return true;
task = entry->second;
m_streams.erase(entry);
}
std::lock_guard<std::mutex> appendLock(task->appendMutex);
{
std::lock_guard<std::mutex> lock(task->queueMutex);
task->closing = true;
task->condition.notify_all();
}
if (task->worker.joinable()) task->worker.join();
task->output.seekp(sizeof("LineNum:") - 1);
task->output << std::left << std::setw(20) << task->writtenLines;
task->output.flush();
const bool written = task->output.good() && !task->failed &&
task->writtenLines <= static_cast<uint64_t>((std::numeric_limits<int>::max)());
task->output.close();
bool success = written && task->output.good() && complete;
std::string savedPath = task->filePath + ".part";
if (success) {
success = QFile::rename(QString::fromStdString(savedPath), QString::fromStdString(task->filePath));
if (success) savedPath = task->filePath;
}
LOG_INFO("[DebugDataSaver] stream finished: complete=%d lines=%llu path=%s\n",
success ? 1 : 0, static_cast<unsigned long long>(task->writtenLines), savedPath.c_str());
RecordSavedFile(task->basePath, savedPath, true);
return success;
}
void DebugDataSaver::FinishAllStreams()
{
std::vector<int> cameras;
{
std::lock_guard<std::mutex> lock(m_streamMutex);
for (const auto& entry : m_streams) cameras.push_back(entry.first);
}
for (int cameraIndex : cameras) FinishStream(cameraIndex, false);
}
void DebugDataSaver::SaveAsync(const std::string& basePath,
const std::string& timestamp,
const std::vector<std::pair<EVzResultDataType, SVzLaserLineData>>& data,
@ -538,8 +325,7 @@ void DebugDataSaver::EnsureStorageLimit(const std::string& basePath)
EnsureStorageLimitLocked(basePath, cache);
}
void DebugDataSaver::EnsureStorageLimitLocked(const std::string& basePath, StorageCache& cache,
const std::string& protectedPath)
void DebugDataSaver::EnsureStorageLimitLocked(const std::string& basePath, StorageCache& cache)
{
Q_UNUSED(basePath);
@ -549,12 +335,8 @@ void DebugDataSaver::EnsureStorageLimitLocked(const std::string& basePath, Stora
}
while (cache.totalSize > limit && !cache.files.empty()) {
auto candidate = std::find_if(cache.files.begin(), cache.files.end(), [&](const StorageFileInfo& file) {
return file.path != protectedPath;
});
if (candidate == cache.files.end()) break;
StorageFileInfo oldest = *candidate;
cache.files.erase(candidate);
StorageFileInfo oldest = cache.files.front();
cache.files.erase(cache.files.begin());
QString filePath = QString::fromStdString(oldest.path);
QFileInfo info(filePath);
@ -625,7 +407,7 @@ void DebugDataSaver::AddFileToCacheLocked(StorageCache& cache, const StorageFile
cache.totalSize += fileInfo.size;
}
void DebugDataSaver::RecordSavedFile(const std::string& basePath, const std::string& filePath, bool protectFile)
void DebugDataSaver::RecordSavedFile(const std::string& basePath, const std::string& filePath)
{
std::lock_guard<std::mutex> lock(m_diskSpaceMutex);
StorageCache& cache = m_storageCaches[NormalizeBasePath(basePath)];
@ -643,7 +425,7 @@ void DebugDataSaver::RecordSavedFile(const std::string& basePath, const std::str
AddFileToCacheLocked(cache, fileInfo);
}
EnsureStorageLimitLocked(basePath, cache, protectFile ? normalizedFilePath : std::string());
EnsureStorageLimitLocked(basePath, cache);
}
bool DebugDataSaver::RemoveFileFromCache(StorageCache& cache, const std::string& filePath)

View File

@ -386,7 +386,7 @@ void MainWindow::readConfig()
}
QJsonObject request;
request[QStringLiteral("msgType")] = QStringLiteral("GET_CONFIG");
request[QStringLiteral("protocolVersion")] = QStringLiteral("2.1");
request[QStringLiteral("protocolVersion")] = QStringLiteral("1.6");
request[QStringLiteral("requestId")] = QStringLiteral("cfg-%1")
.arg(QDateTime::currentMSecsSinceEpoch());
sendJsonMessage(request);
@ -430,7 +430,7 @@ void MainWindow::setConfig()
config[QStringLiteral("queueProcessParam")] = queue;
config[QStringLiteral("pointCloudSendParam")] = pointCloud;
request[QStringLiteral("msgType")] = QStringLiteral("SET_CONFIG");
request[QStringLiteral("protocolVersion")] = QStringLiteral("2.1");
request[QStringLiteral("protocolVersion")] = QStringLiteral("1.6");
request[QStringLiteral("requestId")] = QStringLiteral("cfg-%1")
.arg(QDateTime::currentMSecsSinceEpoch());
request[QStringLiteral("config")] = config;
@ -447,7 +447,7 @@ void MainWindow::setRealtime()
QJsonObject request;
request[QStringLiteral("msgType")] = QStringLiteral("SET_REALTIME");
request[QStringLiteral("protocolVersion")] = QStringLiteral("2.1");
request[QStringLiteral("protocolVersion")] = QStringLiteral("1.6");
request[QStringLiteral("timestamp")] = QDateTime::currentMSecsSinceEpoch();
request[QStringLiteral("enable")] = m_realtimeCheck->isChecked();
sendJsonMessage(request);
@ -464,7 +464,7 @@ void MainWindow::resetAlarm()
QJsonObject request;
request[QStringLiteral("msgType")] = QStringLiteral("RESET_ALARM");
request[QStringLiteral("protocolVersion")] = QStringLiteral("2.1");
request[QStringLiteral("protocolVersion")] = QStringLiteral("1.6");
request[QStringLiteral("timestamp")] = QDateTime::currentMSecsSinceEpoch();
sendJsonMessage(request);
logMessage(QStringLiteral("已发送 RESET_ALARM"));
@ -479,7 +479,7 @@ void MainWindow::restDetect()
QJsonObject request;
request[QStringLiteral("msgType")] = QStringLiteral("REST_DETECT");
request[QStringLiteral("protocolVersion")] = QStringLiteral("2.1");
request[QStringLiteral("protocolVersion")] = QStringLiteral("1.6");
request[QStringLiteral("timestamp")] = QDateTime::currentMSecsSinceEpoch();
sendJsonMessage(request);
logMessage(QStringLiteral("已发送 REST_DETECT"));
@ -574,7 +574,7 @@ void MainWindow::requestProtocolInfo()
QJsonObject request;
request[QStringLiteral("msgType")] = QStringLiteral("GET_PROTOCOL_INFO");
request[QStringLiteral("protocolVersion")] = QStringLiteral("2.1");
request[QStringLiteral("protocolVersion")] = QStringLiteral("1.6");
sendJsonMessage(request);
logMessage(QStringLiteral("已发送新协议 GET_PROTOCOL_INFO 请求"));
}