#include "livoxPcloudFrameDumper.h" #include #include #include #include #include #include #include #include namespace smo { namespace stim_buff { namespace { constexpr const char* kDumpDirParamName = "pcloud-dump-dir"; constexpr const char* kPcdExtension = ".pcd"; constexpr std::size_t kXyzComponentsPerPoint = 3; std::string makePcdHeader(std::size_t width, std::size_t height) { std::ostringstream oss; oss << "# .PCD v0.7 - Point Cloud Data file format\n"; oss << "VERSION 0.7\n"; oss << "FIELDS x y z\n"; oss << "SIZE 4 4 4\n"; oss << "TYPE F F F\n"; oss << "COUNT 1 1 1\n"; oss << "WIDTH " << width << "\n"; oss << "HEIGHT " << height << "\n"; oss << "VIEWPOINT 0 0 0 1 0 0 0\n"; oss << "POINTS " << (width * height) << "\n"; oss << "DATA binary\n"; return oss.str(); } float sanitizeCoordinate(float coordinate) { if (coordinate == 0.0f) { return std::numeric_limits::quiet_NaN(); } return coordinate; } bool pointIsZero(float x, float y, float z) { return x == 0.0f && y == 0.0f && z == 0.0f; } } // namespace LivoxPcloudFrameDumper::LivoxPcloudFrameDumper( const std::shared_ptr& deviceAttachmentSpec) : enabled(false), deviceSelector(deviceAttachmentSpec ? deviceAttachmentSpec->deviceSelector : ""), dumpDirectory() { if (!deviceAttachmentSpec) { throw std::runtime_error( std::string(__func__) + ": deviceAttachmentSpec is null"); } std::string dumpDirParam = parseDumpDirParam( deviceAttachmentSpec->stimBuffApiParams); if (dumpDirParam.empty()) { return; } enabled = true; dumpDirectory = resolveDumpDirectory(dumpDirParam); } void LivoxPcloudFrameDumper::prepareForRun() const { if (!enabled) { return; } std::filesystem::create_directories(dumpDirectory); } void LivoxPcloudFrameDumper::dumpProducedFrame( const livoxProto1::Device& device, const StagingBuffer& collationBuffer, const sscl::AsynchronousLoop& frameAssemblyResult) const { if (!enabled) { return; } std::size_t nSucceeded = frameAssemblyResult.nSucceeded.load(); if (nSucceeded == 0) { return; } std::size_t pointsPerDgram = livoxProto1::Device::getNPointsPerDgram( static_cast(device.currentReturnMode)); if (pointsPerDgram == 0) { throw std::runtime_error( std::string(__func__) + ": pointsPerDgram resolved to 0"); } std::filesystem::path outputPath = makeUniqueFramePath( dumpDirectory, deviceSelector); writeBinaryPcdFile( outputPath, collationBuffer, nSucceeded, pointsPerDgram); } std::string LivoxPcloudFrameDumper::parseDumpDirParam( const std::vector>& params) { for (auto it = params.rbegin(); it != params.rend(); ++it) { if (it->first == kDumpDirParamName) { return it->second; } } return ""; } std::filesystem::path LivoxPcloudFrameDumper::resolveDumpDirectory( const std::string& dumpDirParam) { std::filesystem::path dumpDirPath(dumpDirParam); if (dumpDirPath.is_absolute()) { return dumpDirPath; } return std::filesystem::current_path() / dumpDirPath; } std::string LivoxPcloudFrameDumper::makeTimestampStem( const std::string& deviceSelector) { auto now = std::chrono::system_clock::now(); std::time_t nowTime = std::chrono::system_clock::to_time_t(now); std::tm nowTm = *std::localtime(&nowTime); std::ostringstream oss; oss << deviceSelector << "-" << std::put_time(&nowTm, "%Y-%m-%d--%H:%M:%S"); return oss.str(); } std::filesystem::path LivoxPcloudFrameDumper::makeUniqueFramePath( const std::filesystem::path& dumpDir, const std::string& deviceSelector) { std::string stem = makeTimestampStem(deviceSelector); std::filesystem::path candidate = dumpDir / (stem + kPcdExtension); if (!std::filesystem::exists(candidate)) { return candidate; } for (std::size_t suffix = 1; ; ++suffix) { std::ostringstream oss; oss << stem << "--" << std::setw(3) << std::setfill('0') << suffix << kPcdExtension; candidate = dumpDir / oss.str(); if (!std::filesystem::exists(candidate)) { return candidate; } } } void LivoxPcloudFrameDumper::writeBinaryPcdFile( const std::filesystem::path& outputPath, const StagingBuffer& collationBuffer, std::size_t nSucceeded, std::size_t pointsPerDgram) { std::ofstream output(outputPath, std::ios::binary); if (!output) { throw std::runtime_error( std::string(__func__) + ": failed to open " + outputPath.string()); } output << makePcdHeader(pointsPerDgram, nSucceeded); struct iovec collationIov = collationBuffer.getClEngineIovec(); const auto* bufferBase = static_cast(collationIov.iov_base); const std::size_t rowNBytes = pointsPerDgram * kXyzComponentsPerPoint * sizeof(float); const std::size_t slotStrideNBytes = collationBuffer.slotStrideNBytes; for (std::size_t rowIndex = 0; rowIndex < nSucceeded; ++rowIndex) { const auto* rowBase = reinterpret_cast( bufferBase + (rowIndex * slotStrideNBytes)); for (std::size_t pointIndex = 0; pointIndex < pointsPerDgram; ++pointIndex) { const std::size_t pointOffset = pointIndex * kXyzComponentsPerPoint; float x = rowBase[pointOffset + 0]; float y = rowBase[pointOffset + 1]; float z = rowBase[pointOffset + 2]; if (pointIsZero(x, y, z)) { x = sanitizeCoordinate(x); y = sanitizeCoordinate(y); z = sanitizeCoordinate(z); } output.write(reinterpret_cast(&x), sizeof(float)); output.write(reinterpret_cast(&y), sizeof(float)); output.write(reinterpret_cast(&z), sizeof(float)); } (void)rowNBytes; } if (!output) { throw std::runtime_error( std::string(__func__) + ": failed while writing " + outputPath.string()); } } } // namespace stim_buff } // namespace smo