UFO 1.0.0
An Efficient Probabilistic 3D Mapping Framework That Embraces the Unknown
Loading...
Searching...
No Matches
pcd.hpp
1
42#ifndef UFO_CLOUD_IO_PCD_HPP
43#define UFO_CLOUD_IO_PCD_HPP
44
45// UFO
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>
54
55// STL
56#include <cstdio>
57#include <cstring>
58#include <filesystem>
59#include <memory>
60#include <print>
61#include <sstream>
62
63namespace ufo
64{
65namespace detail
66{
67void pcdSplit(std::vector<std::string>& result, std::string const& in,
68 char const* const delimiters);
69}
70
71[[nodiscard]] CloudProperties cloudPropertiesPCD(std::filesystem::path const& file);
72
73template <std::size_t Dim, class T, class... Ts>
74bool readPCD(std::filesystem::path const& file, PointCloud<Dim, T, Ts...>& pc,
75 ufo::Transform3f* viewpoint = nullptr)
76{
77 FileHandler fp(file.c_str(), "rb");
78
79 if (!fp) {
80 std::println(stderr, "[UFO | Read PCD] Failed to open file: {}", file.string());
81 return false;
82 }
83
84 // Read header
85
86 std::string version;
87
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;
93
94 int width = -1;
95 int height = -1;
96 int points = -1;
97
98 int data_type = -1;
99
100 std::vector<std::string> st;
101 for (char* buf = fp.readline(); nullptr != buf; buf = fp.readline()) {
102 std::string line(buf);
103
104 if (line.empty()) {
105 continue;
106 }
107
108 detail::pcdSplit(st, line, "\t\r ");
109
110 std::stringstream sstream(line);
111 sstream.imbue(std::locale::classic());
112
113 std::string line_type;
114 sstream >> line_type;
115
116 if ("#" == line_type.substr(0, 1)) {
117 continue;
118 }
119
120 if ("VERSION" == line_type.substr(0, 7)) {
121 version = st.at(1);
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);
125
126 fields.resize(specified_channel_count);
127 for (int i = 0; i < specified_channel_count; ++i) {
128 fields[i] = st.at(i + 1);
129 }
130 } else if ("SIZE" == line_type.substr(0, 4)) {
131 if (fields.empty()) {
132 std::println(stderr,
133 "[UFO | Read PCD] FIELDS must be specified before SIZE in header");
134 return false;
135 }
136
137 int specified_channel_count = static_cast<int>(st.size() - 1);
138
139 field_size.resize(specified_channel_count);
140
141 int offset = 0;
142 for (int i = 0; i < specified_channel_count; ++i) {
143 sstream >> field_size[i];
144 }
145 } else if ("TYPE" == line_type.substr(0, 4)) {
146 if (fields.empty() || field_size.empty()) {
147 std::println(stderr,
148 "[UFO | Read PCD] FIELDS and SIZE must be specified before "
149 "TYPE in header");
150 return false;
151 }
152
153 int specified_channel_count = static_cast<int>(st.size() - 1);
154
155 field_type.resize(specified_channel_count);
156
157 for (int i{}; specified_channel_count > i; ++i) {
158 field_type[i] = st.at(i + 1).c_str()[0];
159 }
160 } else if ("COUNT" == line_type.substr(0, 5)) {
161 if (fields.empty() || field_size.empty() || field_type.empty()) {
162 std::println(stderr,
163 "[UFO | Read PCD] FIELDS, SIZE, and TYPE must be specified before "
164 "COUNT in header");
165 return false;
166 }
167
168 int specified_channel_count = static_cast<int>(st.size() - 1);
169
170 field_count.resize(specified_channel_count);
171
172 int offset = 0;
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];
177 }
178 } else if ("WIDTH" == line_type.substr(0, 5)) {
179 sstream >> width;
180 if (sstream.fail()) {
181 std::println(stderr, "[UFO | Read PCD] Invalid WIDTH value specified");
182 return false;
183 }
184 } else if ("HEIGHT" == line_type.substr(0, 6)) {
185 sstream >> height;
186 if (sstream.fail()) {
187 std::println(stderr, "[UFO | Read PCD] Invalid HEIGHT value specified");
188 return false;
189 }
190 } else if ("VIEWPOINT" == line_type.substr(0, 9)) {
191 if (nullptr == viewpoint) {
192 continue;
193 }
194
195 if (8 > st.size()) {
196 std::println(stderr,
197 "[UFO | Read PCD] Not enough number of elements in VIEWPOINT. Need "
198 "7 values (tx ty tz qw qx qy qz)");
199 return false;
200 }
201
202 sstream >> viewpoint->translation.x >> viewpoint->translation.y >>
203 viewpoint->translation.z;
204 Quatf q;
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()) {
209 std::println(
210 stderr, "[UFO | Read PCD] Number of POINTS specified before COUNT in header");
211 return false;
212 }
213 sstream >> points;
214 } else if ("DATA" == line_type.substr(0, 4)) {
215 if (st.at(1).substr(0, 17) == "binary_compressed") {
216 data_type = 2;
217 } else if (st.at(1).substr(0, 6) == "binary") {
218 data_type = 1;
219 } else if (st.at(1).substr(0, 5) == "ascii") {
220 data_type = 0;
221 } else {
222 std::println(stderr, "[UFO | Read PCD] Unknown DATA format: {}", line);
223 return false;
224 }
225 break;
226 }
227 }
228
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()) {
232 std::println(stderr,
233 "[UFO | Read PCD] FIELDS, SIZE, TYPE, and COUNT must be specified in "
234 "header and have the same number of elements");
235 return false;
236 }
237
238 if (0 > width || 0 > height || 0 > points) {
239 std::println(stderr,
240 "[UFO | Read PCD] WIDTH, HEIGHT, and POINTS must be specified in header "
241 "and be positive integers");
242 return false;
243 }
244
245 if (0 > data_type) {
246 std::println(stderr,
247 "[UFO | Read PCD] DATA must be specified in header and has to be either "
248 "ascii, binary, or binary_compressed");
249 return false;
250 }
251
252 // Read data
253
254 if (0 == data_type) {
255 // ASCII
256
257 // TODO: Implement
258 } else if (1 == data_type) {
259 // Binary
260
261 // TODO: Implement
262 } else if (2 == data_type) {
263 // Binary compressed
264 std::println(stderr,
265 "[UFO | Read PCD] Does not support binary compressed PCD files yet");
266 return false;
267 } else {
268 std::println(stderr, "[UFO | Read PCD] Unknown DATA type");
269 return false;
270 }
271
272 return false;
273}
274
275template <std::size_t Dim, class T, class... Ts>
276bool writePCD(std::filesystem::path const& file, PointCloud<Dim, T, Ts...> const& pc,
277 bool ascii = false, ufo::Transform3f viewpoint = ufo::Transform3f())
278{
279 FileHandler fp(file.c_str(), "wb");
280
281 if (!fp) {
282 std::println(stderr, "[UFO | Write PCD] Failed to open file: {}", file.string());
283 return false;
284 }
285
286 std::string version = ".7";
287
288 std::vector<std::string> fields;
289 std::vector<int> field_size;
290 std::vector<char> field_type;
291 std::vector<int> field_count;
292
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);
305
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);
311 }
312
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);
318 }
319
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);
333 }
334
335 int width = pc.size();
336 int height = 1;
337 int points = width * height;
338
339 std::println(fp.get(), "# .PCD v{} - Point Cloud Data file format", version);
340 std::println(fp.get(), "VERSION {}", version);
341
342 std::print(fp.get(), "FIELDS");
343 for (auto const& field : fields) {
344 std::print(fp.get(), " {}", field);
345 }
346 std::print(fp.get(), "\n");
347
348 std::print(fp.get(), "SIZE");
349 for (auto const& size : field_size) {
350 std::print(fp.get(), " {}", size);
351 }
352 std::print(fp.get(), "\n");
353
354 std::print(fp.get(), "TYPE");
355 for (auto const& type : field_type) {
356 std::print(fp.get(), " {}", type);
357 }
358 std::print(fp.get(), "\n");
359
360 std::print(fp.get(), "COUNT");
361 for (auto const& count : field_count) {
362 std::print(fp.get(), " {}", count);
363 }
364 std::print(fp.get(), "\n");
365
366 std::println(fp.get(), "WIDTH {}", width);
367 std::println(fp.get(), "HEIGHT {}", height);
368
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);
373
374 std::println(fp.get(), "POINTS {}", points);
375
376 if (ascii) {
377 std::println(fp.get(), "DATA ascii");
378
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);
382
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};
388 } else {
389 SmallRGB c = convert<SmallRGB>(view<first_color_t<Ts...>>(pc)[i]);
390 rgba = {c.blue, c.green, c.red, 0};
391 }
392 std::uint32_t data;
393 std::memcpy(&data, rgba.data(), 4);
394 std::print(fp.get(), " {}", data);
395 }
396
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);
400 }
401
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);
405 }
406
407 std::println(fp.get(), "");
408 }
409
410 return true;
411 } else {
412 std::println(fp.get(), "DATA binary");
413
414 std::unique_ptr<float[]> data(new float[fields.size()]);
415 for (std::size_t i{}; points > i; ++i) {
416 std::size_t j{};
417
418 Vec3f p = convert<Vec3f>(view<Vec<Dim, T>>(pc)[i]);
419 data[j++] = p.x;
420 data[j++] = p.y;
421 data[j++] = p.z;
422
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};
428 } else {
429 SmallRGB c = convert<SmallRGB>(view<first_color_t<Ts...>>(pc)[i]);
430 rgba = {c.blue, c.green, c.red, 0};
431 }
432 std::memcpy(&data[j++], rgba.data(), 4);
433 }
434
435 if constexpr ((is_intensity_v<Ts> || ...)) {
436 data[j++] = static_cast<float>(view<Intensity>(pc)[i]);
437 }
438
439 if constexpr ((is_normal_v<Ts> || ...)) {
440 Normal n = view<Normal>(pc)[i];
441 data[j++] = n.x;
442 data[j++] = n.y;
443 data[j++] = n.z;
444 }
445
446 std::fwrite(data.get(), sizeof(float), fields.size(), fp.get());
447 }
448 }
449
450 return true;
451}
452} // namespace ufo
453
454#endif // UFO_CLOUD_IO_PCD_HPP
All vision-related classes and functions.
Definition cloud.hpp:49
Rigid-body transform: a rotation matrix plus a translation vector.
Definition transform.hpp:81