cpp-toolbox  0.0.1
A toolbox library for C++
Loading...
Searching...
No Matches
pcd_impl.hpp
Go to the documentation of this file.
1#pragma once
2
10#include <cpp-toolbox/io/formats/pcd.hpp> // Include the header declaring the class
11
12// Add necessary includes for the implementations below
13#include <algorithm> // std::clamp
14#include <cmath> // std::is_floating_point_v, std::is_integral_v
15#include <cstdint> // INT64_C
16#include <cstring> // memcpy
17#include <fstream> // ofstream (for writing), istream
18#include <iomanip> // setprecision, fixed
19#include <limits> // numeric_limits
20#include <map>
21#include <memory>
22#include <sstream> // stringstream
23#include <stdexcept> // exceptions for parsing
24#include <type_traits> // is_same_v, decay_t
25#include <variant> // variant for ascii parsing
26#include <vector>
27
28#include <cpp-toolbox/file/memory_mapped_file.hpp> // For reading binary
29#include <cpp-toolbox/logger/thread_logger.hpp> // For LOG_*
30// #include <cpp-toolbox/types/point.hpp> // Should be included via pcd.hpp
31
32namespace toolbox::io
33{
34
35// Assuming pcd.hpp includes this file *after* the full class definition.
36
61template<typename T>
62bool pcd_format_t::write_internal(const std::string& path,
63 const point_cloud_t<T>& cloud,
64 bool binary) const
65{
66 LOG_DEBUG_S << "pcd_format_t::write_internal processing " << cloud.size()
67 << " points for " << path;
68
69 std::ofstream file;
70 std::ios_base::openmode mode = std::ios::trunc;
71 if (binary) {
72 mode |= std::ios::binary;
73 }
74 file.open(path, mode);
75
76 if (!file.is_open()) {
77 LOG_ERROR_S << "pcd_format_t: Failed to open file for writing: " << path;
78 return false;
79 }
80
81 // Determine fields, types, sizes, counts based on T and cloud content
82 std::vector<std::string> fields;
83 std::vector<char> types;
84 std::vector<size_t> sizes;
85 std::vector<size_t> counts;
86
87 // XYZ are always present if cloud is not empty (XYZ在非空点云中始终存在/XYZ
88 // are always present if cloud is not empty)
89 fields.push_back("x");
90 fields.push_back("y");
91 fields.push_back("z");
92 char point_field_type = 'F';
93 size_t point_field_size = 4;
94 if constexpr (std::is_same_v<T, double>) {
95 point_field_size = 8;
96 } else if constexpr (!std::is_same_v<T, float>) {
97 LOG_WARN_S << "pcd_format_t: Writing non-float/double type "
98 << typeid(T).name()
99 << " might require specific TYPE/SIZE adjustments.";
100 }
101 for (int i = 0; i < 3; ++i) {
102 types.push_back(point_field_type);
103 sizes.push_back(point_field_size);
104 counts.push_back(1);
105 }
106
107 bool has_normals =
108 !cloud.normals.empty() && cloud.normals.size() == cloud.points.size();
109 if (has_normals) {
110 fields.push_back("normal_x");
111 fields.push_back("normal_y");
112 fields.push_back("normal_z");
113 for (int i = 0; i < 3; ++i) {
114 types.push_back(
115 point_field_type); // Assume same type as points
116 // (假设与点相同类型/Assume same type as points)
117 sizes.push_back(point_field_size);
118 counts.push_back(1);
119 }
120 }
121
122 bool has_colors =
123 !cloud.colors.empty() && cloud.colors.size() == cloud.points.size();
124 if (has_colors) {
125 fields.push_back("r");
126 fields.push_back("g");
127 fields.push_back("b");
128 for (int i = 0; i < 3; ++i) {
129 types.push_back('U');
130 sizes.push_back(1);
131 counts.push_back(1); // Standard uint8 for colors
132 // (颜色使用标准uint8/Standard uint8 for colors)
133 }
134 }
135
136 // Write Header (写入头部/Write Header)
137 file << "VERSION .7\n";
138 file << "FIELDS";
139 for (const auto& f : fields) {
140 file << " " << f;
141 }
142 file << "\n";
143 file << "SIZE";
144 for (auto s : sizes) {
145 file << " " << s;
146 }
147 file << "\n";
148 file << "TYPE";
149 for (auto t : types) {
150 file << " " << t;
151 }
152 file << "\n";
153 file << "COUNT";
154 for (auto c : counts) {
155 file << " " << c;
156 }
157 file << "\n";
158 file << "WIDTH " << cloud.size() << "\n";
159 file << "HEIGHT 1\n"; // Assume unorganized cloud (假设为非组织化点云/Assume
160 // unorganized cloud)
161 file << "VIEWPOINT 0 0 0 1 0 0 0\n"; // Default viewpoint (默认视点/Default
162 // viewpoint)
163 file << "POINTS " << cloud.size() << "\n";
164 file << "DATA " << (binary ? "binary" : "ascii") << "\n";
165
166 // Write Data (写入数据/Write Data)
167 if (cloud.empty()) {
168 LOG_WARN_S << "pcd_format_t: Writing empty point cloud header to " << path;
169 return file.good(); // Wrote header successfully (成功写入头部/Wrote header
170 // successfully)
171 }
172
173 if (binary) {
174 size_t point_step = 0;
175 for (size_t i = 0; i < fields.size(); ++i) {
176 point_step += sizes[i] * counts[i];
177 }
178 std::vector<unsigned char> point_buffer(point_step);
179
180 for (size_t i = 0; i < cloud.size(); ++i) {
181 unsigned char* current_ptr = point_buffer.data();
182 // Write XYZ (写入XYZ坐标/Write XYZ)
183 std::memcpy(current_ptr, &cloud.points[i].x, point_field_size);
184 current_ptr += point_field_size;
185 std::memcpy(current_ptr, &cloud.points[i].y, point_field_size);
186 current_ptr += point_field_size;
187 std::memcpy(current_ptr, &cloud.points[i].z, point_field_size);
188 current_ptr += point_field_size;
189 // Write Normals (写入法线/Write Normals)
190 if (has_normals) {
191 std::memcpy(current_ptr, &cloud.normals[i].x, point_field_size);
192 current_ptr += point_field_size;
193 std::memcpy(current_ptr, &cloud.normals[i].y, point_field_size);
194 current_ptr += point_field_size;
195 std::memcpy(current_ptr, &cloud.normals[i].z, point_field_size);
196 current_ptr += point_field_size;
197 }
198 // Write Colors (convert to uint8) (写入颜色(转换为uint8)/Write Colors
199 // (convert to uint8))
200 if (has_colors) {
201 uint8_t r_val = 0, g_val = 0, b_val = 0;
202 // Clamp and convert T -> uint8 (限制范围并转换T到uint8/Clamp and
203 // convert T -> uint8)
204 if constexpr (std::is_floating_point_v<T>) {
205 r_val = static_cast<uint8_t>(
206 std::clamp(cloud.colors[i].x * static_cast<T>(255.0),
207 static_cast<T>(0.0),
208 static_cast<T>(255.0)));
209 g_val = static_cast<uint8_t>(
210 std::clamp(cloud.colors[i].y * static_cast<T>(255.0),
211 static_cast<T>(0.0),
212 static_cast<T>(255.0)));
213 b_val = static_cast<uint8_t>(
214 std::clamp(cloud.colors[i].z * static_cast<T>(255.0),
215 static_cast<T>(0.0),
216 static_cast<T>(255.0)));
217 } else { // Assume integer type already in range or needs clamping
218 // (假设整数类型已在范围内或需要限制/Assume integer type
219 // already in range or needs clamping)
220 r_val = static_cast<uint8_t>(
221 std::clamp(static_cast<int64_t>(cloud.colors[i].x),
222 INT64_C(0),
223 INT64_C(255)));
224 g_val = static_cast<uint8_t>(
225 std::clamp(static_cast<int64_t>(cloud.colors[i].y),
226 INT64_C(0),
227 INT64_C(255)));
228 b_val = static_cast<uint8_t>(
229 std::clamp(static_cast<int64_t>(cloud.colors[i].z),
230 INT64_C(0),
231 INT64_C(255)));
232 }
233 std::memcpy(current_ptr, &r_val, 1);
234 current_ptr += 1;
235 std::memcpy(current_ptr, &g_val, 1);
236 current_ptr += 1;
237 std::memcpy(current_ptr, &b_val, 1);
238 current_ptr += 1;
239 }
240 file.write(reinterpret_cast<const char*>(point_buffer.data()),
241 point_buffer.size());
242 if (!file) {
243 LOG_ERROR_S << "pcd_format_t: Error writing binary data point " << i
244 << " to " << path;
245 return false;
246 }
247 }
248 } else { // ASCII
249 file << std::fixed
250 << std::setprecision(std::numeric_limits<T>::max_digits10);
251 for (size_t i = 0; i < cloud.size(); ++i) {
252 file << cloud.points[i].x << " " << cloud.points[i].y << " "
253 << cloud.points[i].z;
254 if (has_normals) {
255 file << " " << cloud.normals[i].x << " " << cloud.normals[i].y << " "
256 << cloud.normals[i].z;
257 }
258 if (has_colors) {
259 uint8_t r_val = 0, g_val = 0, b_val = 0;
260 if constexpr (std::is_floating_point_v<T>) {
261 r_val = static_cast<uint8_t>(
262 std::clamp(cloud.colors[i].x * static_cast<T>(255.0),
263 static_cast<T>(0.0),
264 static_cast<T>(255.0)));
265 g_val = static_cast<uint8_t>(
266 std::clamp(cloud.colors[i].y * static_cast<T>(255.0),
267 static_cast<T>(0.0),
268 static_cast<T>(255.0)));
269 b_val = static_cast<uint8_t>(
270 std::clamp(cloud.colors[i].z * static_cast<T>(255.0),
271 static_cast<T>(0.0),
272 static_cast<T>(255.0)));
273 } else {
274 r_val = static_cast<uint8_t>(
275 std::clamp(static_cast<int64_t>(cloud.colors[i].x),
276 INT64_C(0),
277 INT64_C(255)));
278 g_val = static_cast<uint8_t>(
279 std::clamp(static_cast<int64_t>(cloud.colors[i].y),
280 INT64_C(0),
281 INT64_C(255)));
282 b_val = static_cast<uint8_t>(
283 std::clamp(static_cast<int64_t>(cloud.colors[i].z),
284 INT64_C(0),
285 INT64_C(255)));
286 }
287 file << " " << static_cast<int>(r_val) << " " << static_cast<int>(g_val)
288 << " " << static_cast<int>(b_val);
289 }
290 file << "\n";
291 if (!file) {
292 LOG_ERROR_S << "pcd_format_t: Error writing ASCII data point " << i
293 << " to " << path;
294 return false;
295 }
296 }
297 }
298 return file.good();
299}
300
321template<typename T>
322bool pcd_format_t::read_ascii_data(std::istream& stream,
323 const pcd_header_t& header,
324 point_cloud_t<T>& cloud)
325{
326 cloud.points.reserve(header.points);
327 bool has_normals = header.m_field_indices.count("normal_x")
328 && header.m_field_indices.count("normal_y")
329 && header.m_field_indices.count("normal_z");
330 bool has_colors = header.m_field_indices.count("rgb")
331 || header.m_field_indices.count("rgba")
332 || (header.m_field_indices.count("r") && header.m_field_indices.count("g")
333 && header.m_field_indices.count("b"));
334
335 if (has_normals)
336 cloud.normals.reserve(header.points);
337 if (has_colors)
338 cloud.colors.reserve(header.points);
339
340 std::string line;
341 size_t points_read = 0;
342 while (points_read < header.points && std::getline(stream, line)) {
343 if (line.empty() || line[0] == '#')
344 continue; // Skip empty/comment lines (跳过空行和注释行/Skip
345 // empty/comment lines)
346
347 point_t<T> current_point;
348 point_t<T> current_normal;
349 point_t<T> current_color;
350 point_t<T>* normal_ptr = has_normals ? &current_normal : nullptr;
351 point_t<T>* color_ptr = has_colors ? &current_color : nullptr;
352
353 if (parse_ascii_point_line(
354 line, header, current_point, normal_ptr, color_ptr))
355 {
356 cloud.points.push_back(current_point);
357 if (has_normals)
358 cloud.normals.push_back(current_normal);
359 if (has_colors)
360 cloud.colors.push_back(current_color);
361 points_read++;
362 } else {
363 LOG_WARN_S << "pcd_format_t: Skipping invalid ASCII line ("
364 << points_read + 1 << ") in file.";
365 }
366 }
367 if (points_read != header.points) {
368 LOG_WARN_S << "pcd_format_t: Read " << points_read
369 << " points, but header expected " << header.points
370 << " (ASCII).";
371 return points_read
372 == header.points; // Require full read (要求完整读取/Require full read)
373 }
374 return true;
375}
376
396template<typename T>
397bool pcd_format_t::read_binary_data(const std::string& path,
398 const pcd_header_t& header,
399 point_cloud_t<T>& cloud)
400{
402 if (!mapped_file.open(path)) {
403 LOG_ERROR_S << "pcd_format_t: Failed to memory map file: " << path;
404 return false;
405 }
406
407 const unsigned char* buffer_start = mapped_file.data();
408 if (!buffer_start) {
409 LOG_ERROR_S << "pcd_format_t: Memory mapped buffer is null for: " << path;
410 return false;
411 }
412 const unsigned char* data_ptr = buffer_start + header.header_length;
413 const unsigned char* buffer_end = buffer_start + mapped_file.size();
414
415 // Pre-fetch field info (预先获取字段信息/Pre-fetch field info)
416 auto x_info = header.get_field_info("x");
417 auto y_info = header.get_field_info("y");
418 auto z_info = header.get_field_info("z");
419 if (!x_info || !y_info || !z_info) {
421 << "pcd_format_t: Essential fields (x, y, z) info not found in header.";
422 return false;
423 }
424
425 bool has_normals = false;
426 std::optional<pcd_header_t::field_info_t> nx_info, ny_info, nz_info;
427 if (header.m_field_indices.count("normal_x")
428 && header.m_field_indices.count("normal_y")
429 && header.m_field_indices.count("normal_z"))
430 {
431 nx_info = header.get_field_info("normal_x");
432 ny_info = header.get_field_info("normal_y");
433 nz_info = header.get_field_info("normal_z");
434 has_normals = nx_info && ny_info && nz_info;
435 if (!has_normals)
436 LOG_WARN_S << "pcd_format_t: Normals expected but info couldn't be "
437 "retrieved. Skipping normals.";
438 }
439
440 bool has_colors = false;
441 std::optional<pcd_header_t::field_info_t> r_info, g_info, b_info;
442 if (header.m_field_indices.count("r") && header.m_field_indices.count("g")
443 && header.m_field_indices.count("b"))
444 {
445 r_info = header.get_field_info("r");
446 g_info = header.get_field_info("g");
447 b_info = header.get_field_info("b");
448 has_colors = r_info && g_info && b_info;
449 if (!has_colors)
450 LOG_WARN_S << "pcd_format_t: R,G,B expected but info couldn't be "
451 "retrieved. Skipping colors.";
452 else if (!(r_info->type == 'U' && r_info->size == 1 && g_info->type == 'U'
453 && g_info->size == 1 && b_info->type == 'U'
454 && b_info->size == 1))
455 {
456 LOG_WARN_S << "pcd_format_t: R,G,B fields found but are not TYPE=U, "
457 "SIZE=1. Skipping colors.";
458 has_colors = false;
459 }
460 }
461
462 cloud.points.reserve(header.points);
463 if (has_normals)
464 cloud.normals.reserve(header.points);
465 if (has_colors)
466 cloud.colors.reserve(header.points);
467
468 auto read_typed_field = [&](const pcd_header_t::field_info_t& info,
469 const unsigned char* point_ptr,
470 T& out_val) -> bool
471 {
472#define READ_BINARY_CASE(PCDType) \
473 case sizeof(PCDType): \
474 if (!read_binary_field_value<T, PCDType>( \
475 point_ptr, info.offset, out_val)) { \
476 LOG_ERROR_S << "pcd_format_t: Failed binary read (offset " \
477 << info.offset << ", size " << sizeof(PCDType) << ")"; \
478 return false; \
479 } \
480 break
481 switch (info.type) {
482 case 'F':
483 switch (info.size) {
484 READ_BINARY_CASE(float);
485 READ_BINARY_CASE(double);
486 default:
487 LOG_ERROR_S << "Unsupported float size " << info.size;
488 return false;
489 }
490 break;
491 case 'I':
492 switch (info.size) {
493 READ_BINARY_CASE(int8_t);
494 READ_BINARY_CASE(int16_t);
495 READ_BINARY_CASE(int32_t);
496 READ_BINARY_CASE(int64_t);
497 default:
498 LOG_ERROR_S << "Unsupported int size " << info.size;
499 return false;
500 }
501 break;
502 case 'U':
503 switch (info.size) {
504 READ_BINARY_CASE(uint8_t);
505 READ_BINARY_CASE(uint16_t);
506 READ_BINARY_CASE(uint32_t);
507 READ_BINARY_CASE(uint64_t);
508 default:
509 LOG_ERROR_S << "Unsupported uint size " << info.size;
510 return false;
511 }
512 break;
513 default:
514 LOG_ERROR_S << "Unsupported field type " << info.type;
515 return false;
516 }
517 return true;
518#undef READ_BINARY_CASE
519 };
520
521 auto read_color_comp = [&](const pcd_header_t::field_info_t& info,
522 const unsigned char* point_ptr,
523 T& out_val) -> bool
524 {
525 uint8_t pcd_val;
526 if (!read_binary_field_value<uint8_t, uint8_t>(
527 point_ptr, info.offset, pcd_val))
528 {
529 LOG_ERROR_S << "pcd_format_t: Failed binary color read (offset "
530 << info.offset << ")";
531 return false;
532 }
533 if constexpr (std::is_floating_point_v<T>) {
534 out_val = static_cast<T>(pcd_val) / static_cast<T>(255.0);
535 } else {
536 out_val = static_cast<T>(pcd_val);
537 }
538 return true;
539 };
540
541 for (size_t i = 0; i < header.points; ++i) {
542 const unsigned char* current_point_ptr = data_ptr + i * header.point_step;
543 if (current_point_ptr < data_ptr
544 || current_point_ptr + header.point_step > buffer_end)
545 {
546 LOG_ERROR_S << "pcd_format_t: Attempt to read past mapped buffer "
547 "boundary at point index "
548 << i;
549 return false;
550 }
551
552 point_t<T> current_point;
553 if (!read_typed_field(*x_info, current_point_ptr, current_point.x)
554 || !read_typed_field(*y_info, current_point_ptr, current_point.y)
555 || !read_typed_field(*z_info, current_point_ptr, current_point.z))
556 {
557 LOG_ERROR_S << "pcd_format_t: Failed reading XYZ for point " << i;
558 return false;
559 }
560 cloud.points.push_back(current_point);
561
562 if (has_normals) {
563 point_t<T> current_normal;
564 if (!read_typed_field(*nx_info, current_point_ptr, current_normal.x)
565 || !read_typed_field(*ny_info, current_point_ptr, current_normal.y)
566 || !read_typed_field(*nz_info, current_point_ptr, current_normal.z))
567 {
568 LOG_ERROR_S << "pcd_format_t: Failed reading normal for point " << i;
569 return false;
570 }
571 cloud.normals.push_back(current_normal);
572 }
573
574 if (has_colors) {
575 point_t<T> current_color;
576 if (!read_color_comp(*r_info, current_point_ptr, current_color.x)
577 || !read_color_comp(*g_info, current_point_ptr, current_color.y)
578 || !read_color_comp(*b_info, current_point_ptr, current_color.z))
579 {
580 LOG_ERROR_S << "pcd_format_t: Failed reading color for point " << i;
581 return false;
582 }
583 cloud.colors.push_back(current_color);
584 }
585 }
586 return true;
587}
588
612template<typename PointStorageT>
613bool pcd_format_t::parse_ascii_point_line(const std::string& line,
614 const pcd_header_t& header,
615 point_t<PointStorageT>& point,
616 point_t<PointStorageT>* normal,
617 point_t<PointStorageT>* color)
618{
619 std::stringstream ss(line);
620 std::vector<std::string> tokens;
621 std::string token;
622 while (ss >> token) {
623 tokens.push_back(token);
624 }
625
626 if (tokens.size() < header.fields.size()) {
627 LOG_WARN_S << "pcd_format_t: ASCII line has fewer values (" << tokens.size()
628 << ") than expected fields (" << header.fields.size()
629 << "). Line: " << line;
630 return false;
631 }
632 if (tokens.size() > header.fields.size()) {
633 LOG_WARN_S << "pcd_format_t: ASCII line has more values (" << tokens.size()
634 << ") than expected fields (" << header.fields.size()
635 << "). Ignoring extra values. Line: " << line;
636 }
637
638 std::map<std::string,
639 std::variant<float,
640 double,
641 int8_t,
642 uint8_t,
643 int16_t,
644 uint16_t,
645 int32_t,
646 uint32_t>>
647 field_values;
648
649 for (size_t i = 0; i < header.fields.size(); ++i) {
650 const std::string& field_name = header.fields[i];
651 char pcd_type = header.types[i];
652 size_t pcd_size = header.sizes[i];
653 const std::string& val_str = tokens[i];
654
655 try {
656 if (pcd_type == 'F') {
657 if (pcd_size == 4)
658 field_values[field_name] = std::stof(val_str);
659 else if (pcd_size == 8)
660 field_values[field_name] = std::stod(val_str);
661 else {
662 LOG_ERROR_S << "Unsupported float size " << pcd_size;
663 return false;
664 }
665 } else if (pcd_type == 'I') {
666 long long val = std::stoll(val_str);
667 if (pcd_size == 1)
668 field_values[field_name] = static_cast<int8_t>(val);
669 else if (pcd_size == 2)
670 field_values[field_name] = static_cast<int16_t>(val);
671 else if (pcd_size == 4)
672 field_values[field_name] = static_cast<int32_t>(val);
673 else {
674 LOG_ERROR_S << "Unsupported int size " << pcd_size;
675 return false;
676 }
677 } else if (pcd_type == 'U') {
678 unsigned long long val = std::stoull(val_str);
679 if (pcd_size == 1)
680 field_values[field_name] = static_cast<uint8_t>(val);
681 else if (pcd_size == 2)
682 field_values[field_name] = static_cast<uint16_t>(val);
683 else if (pcd_size == 4)
684 field_values[field_name] = static_cast<uint32_t>(val);
685 else {
686 LOG_ERROR_S << "Unsupported uint size " << pcd_size;
687 return false;
688 }
689 } else {
690 LOG_ERROR_S << "Unsupported type '" << pcd_type << "'";
691 return false;
692 }
693 } catch (const std::exception& e) {
694 LOG_ERROR_S << "Error parsing ASCII value '" << val_str << "' for field '"
695 << field_name << "': " << e.what();
696 return false;
697 }
698 }
699
700 auto get_val = [&](const std::string& name) -> std::optional<PointStorageT>
701 {
702 if (field_values.count(name)) {
703 try {
704 return std::visit([](auto&& arg) -> PointStorageT
705 { return static_cast<PointStorageT>(arg); },
706 field_values.at(name));
707 } catch (...) {
708 LOG_ERROR_S << "Exception casting value for field '" << name << "'";
709 return std::nullopt;
710 }
711 }
712 return std::nullopt;
713 };
714
715 auto get_color_comp =
716 [&](const std::string& name) -> std::optional<PointStorageT>
717 {
718 if (field_values.count(name)) {
719 try {
720 uint8_t val = std::visit(
721 [](auto&& arg) -> uint8_t
722 {
723 using TArg = std::decay_t<decltype(arg)>;
724 if constexpr (std::is_integral_v<TArg>) {
725 return static_cast<uint8_t>(std::clamp(
726 static_cast<int64_t>(arg), INT64_C(0), INT64_C(255)));
727 } else if constexpr (std::is_floating_point_v<TArg>) {
728 return static_cast<uint8_t>(
729 std::clamp(arg * 255.0, 0.0, 255.0));
730 } else {
731 return 0;
732 }
733 },
734 field_values.at(name));
735 if constexpr (std::is_floating_point_v<PointStorageT>) {
736 return static_cast<PointStorageT>(val)
737 / static_cast<PointStorageT>(255.0);
738 } else {
739 return static_cast<PointStorageT>(val);
740 }
741 } catch (...) {
742 LOG_ERROR_S << "Exception casting color value for field '" << name
743 << "'";
744 return std::nullopt;
745 }
746 }
747 return std::nullopt;
748 };
749
750 auto x_opt = get_val("x");
751 auto y_opt = get_val("y");
752 auto z_opt = get_val("z");
753 if (!x_opt || !y_opt || !z_opt) {
754 LOG_ERROR_S << "Missing x, y, or z in ASCII line.";
755 return false;
756 }
757 point.x = *x_opt;
758 point.y = *y_opt;
759 point.z = *z_opt;
760
761 if (normal) {
762 auto nx_opt = get_val("normal_x");
763 auto ny_opt = get_val("normal_y");
764 auto nz_opt = get_val("normal_z");
765 if (nx_opt && ny_opt && nz_opt) {
766 normal->x = *nx_opt;
767 normal->y = *ny_opt;
768 normal->z = *nz_opt;
769 }
770 }
771 if (color) {
772 auto r_opt = get_color_comp("r");
773 auto g_opt = get_color_comp("g");
774 auto b_opt = get_color_comp("b");
775 if (r_opt && g_opt && b_opt) {
776 color->x = *r_opt;
777 color->y = *g_opt;
778 color->z = *b_opt;
779 }
780 }
781 return true;
782}
783
784template<typename DestT, typename PCDT>
785bool pcd_format_t::read_binary_field_value(const unsigned char* point_ptr,
786 size_t offset,
787 DestT& out_val)
788{
789 if (!point_ptr)
790 return false;
791 PCDT pcd_val;
792 std::memcpy(&pcd_val, point_ptr + offset, sizeof(PCDT));
793 out_val = static_cast<DestT>(pcd_val);
794 return true;
795}
796
797template<typename T>
798CPP_TOOLBOX_EXPORT std::unique_ptr<toolbox::types::point_cloud_t<T>> read_pcd(
799 const std::string& path)
800{
801 auto pcd = std::make_unique<toolbox::io::pcd_format_t>();
802 std::unique_ptr<base_file_data_t> base_data =
803 nullptr; // Create ptr of the type expected by read()
804
805 // Call read, passing the base class unique_ptr reference
806 bool success = pcd->read(path, base_data);
807
808 if (success && base_data) {
809 // Read succeeded, now check if the actual data type matches T
810 auto* typed_ptr = dynamic_cast<point_cloud_t<T>*>(base_data.get());
811 if (typed_ptr) {
812 // Type matches T, transfer ownership to a new
813 // unique_ptr<point_cloud_t<T>>
814 base_data.release(); // Prevent base_data from deleting the object
815 return std::unique_ptr<point_cloud_t<T>>(typed_ptr);
816 } else {
817 // Read succeeded, but the resulting type is not point_cloud_t<T>
818 // (e.g., read always returns float, but T was double)
819 LOG_WARN_S << "read_pcd: read data type does not match requested type T="
820 << typeid(T).name() << ". Path: " << path;
821 // Return nullptr as the requested type couldn't be provided.
822 // The base_data unique_ptr will delete the object when it goes out of
823 // scope.
824 return nullptr;
825 }
826 }
827
828 // Read failed or returned null data
829 return nullptr;
830}
831
832template<typename T>
833CPP_TOOLBOX_EXPORT bool write_pcd(const std::string& path,
835 bool binary)
836{
837 auto pcd = std::make_unique<toolbox::io::pcd_format_t>();
838
839 // Create a copy of the input cloud to satisfy the unique_ptr interface
840 auto cloud_copy_ptr =
841 std::make_unique<toolbox::types::point_cloud_t<T>>(cloud);
842
843 // Cast the unique_ptr holding the copy to the base class type unique_ptr
844 std::unique_ptr<base_file_data_t> base_data_ptr = std::move(cloud_copy_ptr);
845
846 // Pass the unique_ptr (as const reference) to the write method
847 return pcd->write(path, base_data_ptr, binary);
848}
849
850} // namespace toolbox::io
RAII wrapper for memory-mapped files. / 内存映射文件的 RAII 封装。
Definition memory_mapped_file.hpp:74
size_t size() const
Gets the size of the mapped file. / 获取映射文件的大小。
Definition memory_mapped_file.hpp:237
bool open(const std::filesystem::path &path)
Opens and memory-maps the specified file. / 打开并内存映射指定的文件。
const unsigned char * data() const
Gets a pointer to the mapped memory region. / 获取指向映射内存区域的指针。
Definition memory_mapped_file.hpp:214
包含点和相关数据的点云类 / A point cloud class containing points and associated data
Definition point.hpp:268
#define LOG_DEBUG_S
DEBUG级别流式日志的宏 / Macro for DEBUG level stream logging.
Definition thread_logger.hpp:1329
#define LOG_ERROR_S
ERROR级别流式日志的宏 / Macro for ERROR level stream logging.
Definition thread_logger.hpp:1332
#define LOG_WARN_S
WARN级别流式日志的宏 / Macro for WARN level stream logging.
Definition thread_logger.hpp:1331
< 用于列出目录下的文件/For listing files in a directory
Definition dataloader.hpp:15
CPP_TOOLBOX_EXPORT std::unique_ptr< toolbox::types::point_cloud_t< T > > read_pcd(const std::string &path)
从文件中读取 PCD 点云数据的独立函数。/Standalone function to read PCD point cloud data from a file.
Definition pcd_impl.hpp:798
CPP_TOOLBOX_EXPORT bool write_pcd(const std::string &path, const toolbox::types::point_cloud_t< T > &cloud, bool binary)
将点云数据写入 PCD 文件的独立函数。/Standalone function to write point cloud data to a PCD file.
Definition pcd_impl.hpp:833
std::vector< typename TContainer::value_type > mode(const TContainer &data)
计算容器中元素的众数(可能有多个)/Compute the mode(s) of elements in a container (may be multiple)
Definition statistics.hpp:138
#define READ_BINARY_CASE(PCDType)