42#ifndef UFO_CLOUD_IO_PCD_HPP
43#define UFO_CLOUD_IO_PCD_HPP
46#include <ufo/cloud/point_cloud.hpp>
47#include <ufo/core/intensity.hpp>
48#include <ufo/core/normal.hpp>
49#include <ufo/io/cloud_properties.hpp>
50#include <ufo/io/file_handler.hpp>
51#include <ufo/numeric/transform3.hpp>
52#include <ufo/numeric/vec.hpp>
53#include <ufo/vision/color.hpp>
67void pcdSplit(std::vector<std::string>& result, std::string
const& in,
68 char const*
const delimiters);
71[[nodiscard]] CloudProperties cloudPropertiesPCD(std::filesystem::path
const& file);
73template <std::size_t Dim,
class T,
class... Ts>
74bool readPCD(std::filesystem::path
const& file, PointCloud<Dim, T, Ts...>& pc,
77 FileHandler fp(file.c_str(),
"rb");
80 std::println(stderr,
"[UFO | Read PCD] Failed to open file: {}", file.string());
88 std::vector<std::string> fields;
89 std::vector<int> field_size;
90 std::vector<char> field_type;
91 std::vector<int> field_count;
92 std::vector<int> field_offset;
100 std::vector<std::string> st;
101 for (
char* buf = fp.readline();
nullptr != buf; buf = fp.readline()) {
102 std::string line(buf);
108 detail::pcdSplit(st, line,
"\t\r ");
110 std::stringstream sstream(line);
111 sstream.imbue(std::locale::classic());
113 std::string line_type;
114 sstream >> line_type;
116 if (
"#" == line_type.substr(0, 1)) {
120 if (
"VERSION" == line_type.substr(0, 7)) {
122 }
else if ((
"FIELDS" == line_type.substr(0, 6)) ||
123 (
"COLUMNS" == line_type.substr(0, 7))) {
124 int specified_channel_count =
static_cast<int>(st.size() - 1);
126 fields.resize(specified_channel_count);
127 for (
int i = 0; i < specified_channel_count; ++i) {
128 fields[i] = st.at(i + 1);
130 }
else if (
"SIZE" == line_type.substr(0, 4)) {
131 if (fields.empty()) {
133 "[UFO | Read PCD] FIELDS must be specified before SIZE in header");
137 int specified_channel_count =
static_cast<int>(st.size() - 1);
139 field_size.resize(specified_channel_count);
142 for (
int i = 0; i < specified_channel_count; ++i) {
143 sstream >> field_size[i];
145 }
else if (
"TYPE" == line_type.substr(0, 4)) {
146 if (fields.empty() || field_size.empty()) {
148 "[UFO | Read PCD] FIELDS and SIZE must be specified before "
153 int specified_channel_count =
static_cast<int>(st.size() - 1);
155 field_type.resize(specified_channel_count);
157 for (
int i{}; specified_channel_count > i; ++i) {
158 field_type[i] = st.at(i + 1).c_str()[0];
160 }
else if (
"COUNT" == line_type.substr(0, 5)) {
161 if (fields.empty() || field_size.empty() || field_type.empty()) {
163 "[UFO | Read PCD] FIELDS, SIZE, and TYPE must be specified before "
168 int specified_channel_count =
static_cast<int>(st.size() - 1);
170 field_count.resize(specified_channel_count);
173 for (
int i{}; specified_channel_count > i; ++i) {
174 field_offset[i] = offset;
175 sstream >> field_count[i];
176 offset += field_count[i] * field_size[i];
178 }
else if (
"WIDTH" == line_type.substr(0, 5)) {
180 if (sstream.fail()) {
181 std::println(stderr,
"[UFO | Read PCD] Invalid WIDTH value specified");
184 }
else if (
"HEIGHT" == line_type.substr(0, 6)) {
186 if (sstream.fail()) {
187 std::println(stderr,
"[UFO | Read PCD] Invalid HEIGHT value specified");
190 }
else if (
"VIEWPOINT" == line_type.substr(0, 9)) {
191 if (
nullptr == viewpoint) {
197 "[UFO | Read PCD] Not enough number of elements in VIEWPOINT. Need "
198 "7 values (tx ty tz qw qx qy qz)");
202 sstream >> viewpoint->translation.x >> viewpoint->translation.y >>
203 viewpoint->translation.z;
205 sstream >> q.w >> q.x >> q.y >> q.z;
206 viewpoint->rotation =
static_cast<Mat3x3f
>(q);
207 }
else if (
"POINTS" == line_type.substr(0, 6)) {
208 if (field_count.empty()) {
210 stderr,
"[UFO | Read PCD] Number of POINTS specified before COUNT in header");
214 }
else if (
"DATA" == line_type.substr(0, 4)) {
215 if (st.at(1).substr(0, 17) ==
"binary_compressed") {
217 }
else if (st.at(1).substr(0, 6) ==
"binary") {
219 }
else if (st.at(1).substr(0, 5) ==
"ascii") {
222 std::println(stderr,
"[UFO | Read PCD] Unknown DATA format: {}", line);
229 if (fields.empty() || fields.size() != field_size.size() ||
230 fields.size() != field_type.size() || fields.size() != field_count.size() ||
231 fields.size() != field_offset.size()) {
233 "[UFO | Read PCD] FIELDS, SIZE, TYPE, and COUNT must be specified in "
234 "header and have the same number of elements");
238 if (0 > width || 0 > height || 0 > points) {
240 "[UFO | Read PCD] WIDTH, HEIGHT, and POINTS must be specified in header "
241 "and be positive integers");
247 "[UFO | Read PCD] DATA must be specified in header and has to be either "
248 "ascii, binary, or binary_compressed");
254 if (0 == data_type) {
258 }
else if (1 == data_type) {
262 }
else if (2 == data_type) {
265 "[UFO | Read PCD] Does not support binary compressed PCD files yet");
268 std::println(stderr,
"[UFO | Read PCD] Unknown DATA type");
275template <std::size_t Dim,
class T,
class... Ts>
276bool writePCD(std::filesystem::path
const& file, PointCloud<Dim, T, Ts...>
const& pc,
279 FileHandler fp(file.c_str(),
"wb");
282 std::println(stderr,
"[UFO | Write PCD] Failed to open file: {}", file.string());
286 std::string version =
".7";
288 std::vector<std::string> fields;
289 std::vector<int> field_size;
290 std::vector<char> field_type;
291 std::vector<int> field_count;
293 fields.push_back(
"x");
294 field_size.push_back(4);
295 field_type.push_back(
'F');
296 field_count.push_back(1);
297 fields.push_back(
"y");
298 field_size.push_back(4);
299 field_type.push_back(
'F');
300 field_count.push_back(1);
301 fields.push_back(
"z");
302 field_size.push_back(4);
303 field_type.push_back(
'F');
304 field_count.push_back(1);
306 if constexpr ((is_color_v<Ts> || ...)) {
307 fields.push_back(has_alpha_v<first_color_t<Ts...>> ?
"rgba" :
"rgb");
308 field_size.push_back(4);
309 field_type.push_back(
'U');
310 field_count.push_back(1);
313 if constexpr ((is_intensity_v<Ts> || ...)) {
314 fields.push_back(
"intensity");
315 field_size.push_back(4);
316 field_type.push_back(
'F');
317 field_count.push_back(1);
320 if constexpr ((is_normal_v<Ts> || ...)) {
321 fields.push_back(
"normal_x");
322 field_size.push_back(4);
323 field_type.push_back(
'F');
324 field_count.push_back(1);
325 fields.push_back(
"normal_y");
326 field_size.push_back(4);
327 field_type.push_back(
'F');
328 field_count.push_back(1);
329 fields.push_back(
"normal_z");
330 field_size.push_back(4);
331 field_type.push_back(
'F');
332 field_count.push_back(1);
335 int width = pc.size();
337 int points = width * height;
339 std::println(fp.get(),
"# .PCD v{} - Point Cloud Data file format", version);
340 std::println(fp.get(),
"VERSION {}", version);
342 std::print(fp.get(),
"FIELDS");
343 for (
auto const& field : fields) {
344 std::print(fp.get(),
" {}", field);
346 std::print(fp.get(),
"\n");
348 std::print(fp.get(),
"SIZE");
349 for (
auto const& size : field_size) {
350 std::print(fp.get(),
" {}", size);
352 std::print(fp.get(),
"\n");
354 std::print(fp.get(),
"TYPE");
355 for (
auto const& type : field_type) {
356 std::print(fp.get(),
" {}", type);
358 std::print(fp.get(),
"\n");
360 std::print(fp.get(),
"COUNT");
361 for (
auto const& count : field_count) {
362 std::print(fp.get(),
" {}", count);
364 std::print(fp.get(),
"\n");
366 std::println(fp.get(),
"WIDTH {}", width);
367 std::println(fp.get(),
"HEIGHT {}", height);
369 Vec3f t(viewpoint.translation);
370 Quatf q(viewpoint.rotation);
371 std::println(fp.get(),
"VIEWPOINT {:.6} {:.6} {:.6} {:.6} {:.6} {:.6} {:.6}", t.x, t.y,
372 t.z, q.w, q.x, q.y, q.z);
374 std::println(fp.get(),
"POINTS {}", points);
377 std::println(fp.get(),
"DATA ascii");
379 for (std::size_t i{}; points > i; ++i) {
380 Vec3f p = convert<Vec3f>(view<Vec<Dim, T>>(pc)[i]);
381 std::print(fp.get(),
"{:.6} {:.6} {:.6}", p.x, p.y, p.z);
383 if constexpr ((is_color_v<Ts> || ...)) {
384 std::array<std::uint8_t, 4> rgba;
385 if constexpr (has_alpha_v<first_color_t<Ts...>>) {
386 SmallRGBA c = convert<SmallRGBA>(view<first_color_t<Ts...>>(pc)[i]);
387 rgba = {c.blue, c.green, c.red, c.alpha};
389 SmallRGB c = convert<SmallRGB>(view<first_color_t<Ts...>>(pc)[i]);
390 rgba = {c.blue, c.green, c.red, 0};
393 std::memcpy(&data, rgba.data(), 4);
394 std::print(fp.get(),
" {}", data);
397 if constexpr ((is_intensity_v<Ts> || ...)) {
398 float intensity =
static_cast<float>(view<Intensity>(pc)[i].intensity);
399 std::print(fp.get(),
" {:.6}", intensity);
402 if constexpr ((is_normal_v<Ts> || ...)) {
403 Normal n = view<Normal>(pc)[i];
404 std::print(fp.get(),
" {:.6} {:.6} {:.6}", n.x, n.y, n.z);
407 std::println(fp.get(),
"");
412 std::println(fp.get(),
"DATA binary");
414 std::unique_ptr<float[]> data(
new float[fields.size()]);
415 for (std::size_t i{}; points > i; ++i) {
418 Vec3f p = convert<Vec3f>(view<Vec<Dim, T>>(pc)[i]);
423 if constexpr ((is_color_v<Ts> || ...)) {
424 std::array<std::uint8_t, 4> rgba;
425 if constexpr (has_alpha_v<first_color_t<Ts...>>) {
426 SmallRGBA c = convert<SmallRGBA>(view<first_color_t<Ts...>>(pc)[i]);
427 rgba = {c.blue, c.green, c.red, c.alpha};
429 SmallRGB c = convert<SmallRGB>(view<first_color_t<Ts...>>(pc)[i]);
430 rgba = {c.blue, c.green, c.red, 0};
432 std::memcpy(&data[j++], rgba.data(), 4);
435 if constexpr ((is_intensity_v<Ts> || ...)) {
436 data[j++] =
static_cast<float>(view<Intensity>(pc)[i]);
439 if constexpr ((is_normal_v<Ts> || ...)) {
440 Normal n = view<Normal>(pc)[i];
446 std::fwrite(data.get(),
sizeof(
float), fields.size(), fp.get());
All vision-related classes and functions.