239 lines
5.7 KiB
C++
239 lines
5.7 KiB
C++
|
|
#include "livoxPcloudFrameDumper.h"
|
||
|
|
|
||
|
|
#include <chrono>
|
||
|
|
#include <cmath>
|
||
|
|
#include <ctime>
|
||
|
|
#include <fstream>
|
||
|
|
#include <iomanip>
|
||
|
|
#include <limits>
|
||
|
|
#include <sstream>
|
||
|
|
#include <stdexcept>
|
||
|
|
|
||
|
|
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<float>::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<device::DeviceAttachmentSpec>& 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<int>(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<std::pair<std::string, std::string>>& 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<const std::uint8_t*>(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<const float*>(
|
||
|
|
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<const char*>(&x), sizeof(float));
|
||
|
|
output.write(reinterpret_cast<const char*>(&y), sizeof(float));
|
||
|
|
output.write(reinterpret_cast<const char*>(&z), sizeof(float));
|
||
|
|
}
|
||
|
|
|
||
|
|
(void)rowNBytes;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!output)
|
||
|
|
{
|
||
|
|
throw std::runtime_error(
|
||
|
|
std::string(__func__) + ": failed while writing "
|
||
|
|
+ outputPath.string());
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
} // namespace stim_buff
|
||
|
|
} // namespace smo
|