1#[cfg(feature = "io-mmap")]
7use crate::mmap::MmapReader;
8use crate::registry::{
9 PointCloudReader as RegistryPointCloudReader, PointCloudWriter as RegistryPointCloudWriter,
10};
11use crate::{PointCloudReader, PointCloudWriter};
12use std::collections::HashMap;
13use std::fs::File;
14use std::io::{BufRead, BufReader, Read, Write};
15use std::path::Path;
16use threecrate_core::{Error, Point3f, PointCloud, Result};
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub enum PcdDataFormat {
21 Ascii,
22 Binary,
23 BinaryCompressed,
24}
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28pub enum PcdFieldType {
29 I8,
30 U8,
31 I16,
32 U16,
33 I32,
34 U32,
35 F32,
36 F64,
37}
38
39#[derive(Debug, Clone)]
41pub struct PcdField {
42 pub name: String,
43 pub field_type: PcdFieldType,
44 pub count: usize,
45}
46
47#[derive(Debug, Clone)]
49pub struct PcdHeader {
50 pub version: String,
51 pub fields: Vec<PcdField>,
52 pub width: usize,
53 pub height: usize,
54 pub viewpoint: [f64; 7], pub data_format: PcdDataFormat,
56}
57
58#[derive(Debug, Clone)]
60pub enum PcdValue {
61 I8(i8),
62 U8(u8),
63 I16(i16),
64 U16(u16),
65 I32(i32),
66 U32(u32),
67 F32(f32),
68 F64(f64),
69}
70
71pub type PcdPoint = HashMap<String, Vec<PcdValue>>;
73
74#[derive(Debug, Clone)]
76pub struct PcdWriteOptions {
77 pub data_format: PcdDataFormat,
78 pub version: String,
79 pub viewpoint: Option<[f64; 7]>,
80 pub additional_fields: Vec<PcdField>,
81}
82
83impl Default for PcdWriteOptions {
84 fn default() -> Self {
85 Self {
86 data_format: PcdDataFormat::Binary,
87 version: "0.7".to_string(),
88 viewpoint: None,
89 additional_fields: Vec::new(),
90 }
91 }
92}
93
94pub struct RobustPcdReader;
96
97impl RobustPcdReader {
98 pub fn read_pcd_file<P: AsRef<Path>>(path: P) -> Result<(PcdHeader, Vec<PcdPoint>)> {
100 let path = path.as_ref();
101
102 #[cfg(feature = "io-mmap")]
104 {
105 if let Some((header, points)) = Self::try_read_pcd_mmap(path)? {
106 return Ok((header, points));
107 }
108 }
109
110 let file = File::open(path)?;
112 let mut reader = BufReader::new(file);
113 Self::read_pcd_data(&mut reader)
114 }
115
116 #[cfg(feature = "io-mmap")]
118 fn try_read_pcd_mmap<P: AsRef<Path>>(path: P) -> Result<Option<(PcdHeader, Vec<PcdPoint>)>> {
119 let path = path.as_ref();
120
121 if !crate::mmap::should_use_mmap(path) {
123 return Ok(None);
124 }
125
126 let file = File::open(path)?;
128 let mut reader = BufReader::new(file);
129 let header = Self::read_header(&mut reader)?;
130
131 match header.data_format {
133 PcdDataFormat::Binary => {
134 let file = File::open(path)?;
136 let mut reader = BufReader::new(file);
137 let mut header_size = 0;
138 let mut line = String::new();
139
140 loop {
141 line.clear();
142 let bytes_read = reader.read_line(&mut line)?;
143 if bytes_read == 0 {
144 return Err(Error::InvalidData(
145 "Unexpected end of file in PCD header".to_string(),
146 ));
147 }
148 header_size += bytes_read;
149
150 let line = line.trim();
151 if line == "DATA binary" {
152 break;
153 }
154 }
155
156 if let Some(mut mmap_reader) = MmapReader::new(path)? {
158 mmap_reader.seek(header_size)?;
160
161 let points = Self::read_binary_points_mmap(&mut mmap_reader, &header)?;
163
164 return Ok(Some((header, points)));
165 }
166 }
167 PcdDataFormat::Ascii | PcdDataFormat::BinaryCompressed => {
168 return Ok(None);
170 }
171 }
172
173 Ok(None)
174 }
175
176 #[cfg(feature = "io-mmap")]
178 fn read_binary_points_mmap(
179 reader: &mut MmapReader,
180 header: &PcdHeader,
181 ) -> Result<Vec<PcdPoint>> {
182 let mut points = Vec::with_capacity(header.width * header.height);
183
184 for _ in 0..(header.width * header.height) {
185 let mut point = PcdPoint::new();
186
187 for field in &header.fields {
188 let field_values = Self::read_binary_field_values_mmap(reader, field)?;
189 point.insert(field.name.clone(), field_values);
190 }
191
192 points.push(point);
193 }
194
195 Ok(points)
196 }
197
198 #[cfg(feature = "io-mmap")]
200 fn read_binary_field_values_mmap(
201 reader: &mut MmapReader,
202 field: &PcdField,
203 ) -> Result<Vec<PcdValue>> {
204 let mut field_values = Vec::with_capacity(field.count);
205
206 for _ in 0..field.count {
207 let value = match field.field_type {
208 PcdFieldType::I8 => PcdValue::I8(reader.read_u8()? as i8),
209 PcdFieldType::U8 => PcdValue::U8(reader.read_u8()?),
210 PcdFieldType::I16 => PcdValue::I16(reader.read_u16_le()? as i16),
211 PcdFieldType::U16 => PcdValue::U16(reader.read_u16_le()?),
212 PcdFieldType::I32 => PcdValue::I32(reader.read_u32_le()? as i32),
213 PcdFieldType::U32 => PcdValue::U32(reader.read_u32_le()?),
214 PcdFieldType::F32 => PcdValue::F32(reader.read_f32_le()?),
215 PcdFieldType::F64 => PcdValue::F64(reader.read_f64_le()?),
216 };
217
218 field_values.push(value);
219 }
220
221 Ok(field_values)
222 }
223
224 pub fn read_pcd_data<R: BufRead>(reader: &mut R) -> Result<(PcdHeader, Vec<PcdPoint>)> {
226 let header = Self::read_header(reader)?;
227 let points = Self::read_points(reader, &header)?;
228 Ok((header, points))
229 }
230
231 fn read_header<R: BufRead>(reader: &mut R) -> Result<PcdHeader> {
233 let mut version = None;
234 let mut fields = Vec::new();
235 let mut size = Vec::new();
236 let mut field_types = Vec::new();
237 let mut count = Vec::new();
238 let mut width = None;
239 let mut height = None;
240 let mut viewpoint = [0.0; 7];
241 let mut points = None;
242 let mut _data_format = None;
243
244 let mut line = String::new();
245
246 loop {
247 line.clear();
248 let bytes_read = reader.read_line(&mut line)?;
249 if bytes_read == 0 {
250 return Err(Error::InvalidData(
251 "Unexpected end of file in PCD header".to_string(),
252 ));
253 }
254
255 let line = line.trim();
256 if line.is_empty() {
257 continue;
258 }
259
260 if line.starts_with('#') {
261 continue; }
263
264 if line == "DATA ascii" {
265 _data_format = Some(PcdDataFormat::Ascii);
266 break;
267 } else if line == "DATA binary" {
268 _data_format = Some(PcdDataFormat::Binary);
269 break;
270 } else if line == "DATA binary_compressed" {
271 _data_format = Some(PcdDataFormat::BinaryCompressed);
272 break;
273 }
274
275 let parts: Vec<&str> = line.split_whitespace().collect();
276 if parts.is_empty() {
277 continue;
278 }
279
280 match parts[0] {
281 "VERSION" => {
282 if parts.len() >= 2 {
283 version = Some(parts[1].to_string());
284 }
285 }
286 "FIELDS" => {
287 if parts.len() >= 2 {
288 for &field_name in &parts[1..] {
289 fields.push(PcdField {
290 name: field_name.to_string(),
291 field_type: PcdFieldType::F32, count: 1, });
294 }
295 }
296 }
297 "SIZE" => {
298 if parts.len() >= 2 {
299 for &size_str in &parts[1..] {
300 size.push(size_str.parse::<usize>().map_err(|_| {
301 Error::InvalidData(format!("Invalid SIZE value: {}", size_str))
302 })?);
303 }
304 }
305 }
306 "TYPE" => {
307 if parts.len() >= 2 {
308 for (i, &type_str) in parts[1..].iter().enumerate() {
309 let size = if i < size.len() { size[i] } else { 4 }; let field_type = match (type_str, size) {
311 ("I", 1) => PcdFieldType::I8,
312 ("I", 2) => PcdFieldType::I16,
313 ("I", 4) | ("I", _) => PcdFieldType::I32, ("U", 1) => PcdFieldType::U8,
315 ("U", 2) => PcdFieldType::U16,
316 ("U", 4) | ("U", _) => PcdFieldType::U32,
317 ("F", 4) => PcdFieldType::F32,
318 ("F", 8) | ("F", _) => PcdFieldType::F64,
319 _ => {
320 return Err(Error::InvalidData(format!(
321 "Unknown field type/size combination: {}/{}",
322 type_str, size
323 )))
324 }
325 };
326 field_types.push(field_type);
327 }
328 }
329 }
330 "COUNT" => {
331 if parts.len() >= 2 {
332 for &count_str in &parts[1..] {
333 count.push(count_str.parse::<usize>().map_err(|_| {
334 Error::InvalidData(format!("Invalid COUNT value: {}", count_str))
335 })?);
336 }
337 }
338 }
339 "WIDTH" => {
340 if parts.len() >= 2 {
341 width = Some(parts[1].parse::<usize>().map_err(|_| {
342 Error::InvalidData(format!("Invalid WIDTH value: {}", parts[1]))
343 })?);
344 }
345 }
346 "HEIGHT" => {
347 if parts.len() >= 2 {
348 height = Some(parts[1].parse::<usize>().map_err(|_| {
349 Error::InvalidData(format!("Invalid HEIGHT value: {}", parts[1]))
350 })?);
351 }
352 }
353 "VIEWPOINT" => {
354 if parts.len() >= 8 {
355 for i in 0..7 {
356 viewpoint[i] = parts[i + 1].parse::<f64>().map_err(|_| {
357 Error::InvalidData(format!(
358 "Invalid VIEWPOINT value: {}",
359 parts[i + 1]
360 ))
361 })?;
362 }
363 }
364 }
365 "POINTS" => {
366 if parts.len() >= 2 {
367 points = Some(parts[1].parse::<usize>().map_err(|_| {
368 Error::InvalidData(format!("Invalid POINTS value: {}", parts[1]))
369 })?);
370 }
371 }
372 _ => {
373 }
375 }
376 }
377
378 let version = version
379 .ok_or_else(|| Error::InvalidData("Missing VERSION in PCD header".to_string()))?;
380 let width =
381 width.ok_or_else(|| Error::InvalidData("Missing WIDTH in PCD header".to_string()))?;
382 let height =
383 height.ok_or_else(|| Error::InvalidData("Missing HEIGHT in PCD header".to_string()))?;
384 let data_format = _data_format
385 .ok_or_else(|| Error::InvalidData("Missing DATA format in PCD header".to_string()))?;
386
387 if fields.len() == field_types.len() && fields.len() == count.len() {
389 for (i, field) in fields.iter_mut().enumerate() {
390 field.field_type = field_types[i];
391 field.count = count[i];
392 }
393 } else {
394 return Err(Error::InvalidData(
395 "Mismatch between FIELDS, TYPE, and COUNT declarations".to_string(),
396 ));
397 }
398
399 if let Some(points) = points {
401 if points != width * height {
402 return Err(Error::InvalidData(format!(
403 "POINTS ({}) doesn't match WIDTH * HEIGHT ({})",
404 points,
405 width * height
406 )));
407 }
408 }
409
410 Ok(PcdHeader {
411 version,
412 fields,
413 width,
414 height,
415 viewpoint,
416 data_format,
417 })
418 }
419
420 fn read_points<R: BufRead>(reader: &mut R, header: &PcdHeader) -> Result<Vec<PcdPoint>> {
422 match header.data_format {
423 PcdDataFormat::Ascii => Self::read_ascii_points(reader, header),
424 PcdDataFormat::Binary => Self::read_binary_points(reader, header),
425 PcdDataFormat::BinaryCompressed => Err(Error::Unsupported(
426 "Binary compressed PCD format not yet supported".to_string(),
427 )),
428 }
429 }
430
431 fn read_ascii_points<R: BufRead>(reader: &mut R, header: &PcdHeader) -> Result<Vec<PcdPoint>> {
433 let mut points = Vec::with_capacity(header.width * header.height);
434
435 for _ in 0..(header.width * header.height) {
436 let mut line = String::new();
437 reader.read_line(&mut line)?;
438 let line = line.trim();
439
440 if line.is_empty() {
441 continue;
442 }
443
444 let values: Vec<&str> = line.split_whitespace().collect();
445 let mut value_idx = 0;
446 let mut point = PcdPoint::new();
447
448 for field in &header.fields {
449 let field_values = Self::read_ascii_field_values(&values, &mut value_idx, field)?;
450 point.insert(field.name.clone(), field_values);
451 }
452
453 points.push(point);
454 }
455
456 Ok(points)
457 }
458
459 fn read_binary_points<R: Read>(reader: &mut R, header: &PcdHeader) -> Result<Vec<PcdPoint>> {
461 let mut points = Vec::with_capacity(header.width * header.height);
462
463 for _ in 0..(header.width * header.height) {
464 let mut point = PcdPoint::new();
465
466 for field in &header.fields {
467 let field_values = Self::read_binary_field_values(reader, field)?;
468 point.insert(field.name.clone(), field_values);
469 }
470
471 points.push(point);
472 }
473
474 Ok(points)
475 }
476
477 fn read_ascii_field_values(
479 values: &[&str],
480 value_idx: &mut usize,
481 field: &PcdField,
482 ) -> Result<Vec<PcdValue>> {
483 let mut field_values = Vec::with_capacity(field.count);
484
485 for _ in 0..field.count {
486 if *value_idx >= values.len() {
487 return Err(Error::InvalidData(
488 "Not enough values in ASCII PCD line".to_string(),
489 ));
490 }
491
492 let value = match field.field_type {
493 PcdFieldType::I8 => {
494 PcdValue::I8(values[*value_idx].parse::<i8>().map_err(|_| {
495 Error::InvalidData(format!("Invalid I8 value: {}", values[*value_idx]))
496 })?)
497 }
498 PcdFieldType::U8 => {
499 PcdValue::U8(values[*value_idx].parse::<u8>().map_err(|_| {
500 Error::InvalidData(format!("Invalid U8 value: {}", values[*value_idx]))
501 })?)
502 }
503 PcdFieldType::I16 => {
504 PcdValue::I16(values[*value_idx].parse::<i16>().map_err(|_| {
505 Error::InvalidData(format!("Invalid I16 value: {}", values[*value_idx]))
506 })?)
507 }
508 PcdFieldType::U16 => {
509 PcdValue::U16(values[*value_idx].parse::<u16>().map_err(|_| {
510 Error::InvalidData(format!("Invalid U16 value: {}", values[*value_idx]))
511 })?)
512 }
513 PcdFieldType::I32 => {
514 PcdValue::I32(values[*value_idx].parse::<i32>().map_err(|_| {
515 Error::InvalidData(format!("Invalid I32 value: {}", values[*value_idx]))
516 })?)
517 }
518 PcdFieldType::U32 => {
519 PcdValue::U32(values[*value_idx].parse::<u32>().map_err(|_| {
520 Error::InvalidData(format!("Invalid U32 value: {}", values[*value_idx]))
521 })?)
522 }
523 PcdFieldType::F32 => {
524 PcdValue::F32(values[*value_idx].parse::<f32>().map_err(|_| {
525 Error::InvalidData(format!("Invalid F32 value: {}", values[*value_idx]))
526 })?)
527 }
528 PcdFieldType::F64 => {
529 PcdValue::F64(values[*value_idx].parse::<f64>().map_err(|_| {
530 Error::InvalidData(format!("Invalid F64 value: {}", values[*value_idx]))
531 })?)
532 }
533 };
534
535 field_values.push(value);
536 *value_idx += 1;
537 }
538
539 Ok(field_values)
540 }
541
542 fn read_binary_field_values<R: Read>(
544 reader: &mut R,
545 field: &PcdField,
546 ) -> Result<Vec<PcdValue>> {
547 let mut field_values = Vec::with_capacity(field.count);
548
549 for _ in 0..field.count {
550 let value = match field.field_type {
551 PcdFieldType::I8 => {
552 let mut buf = [0u8; 1];
553 reader.read_exact(&mut buf)?;
554 PcdValue::I8(buf[0] as i8)
555 }
556 PcdFieldType::U8 => {
557 let mut buf = [0u8; 1];
558 reader.read_exact(&mut buf)?;
559 PcdValue::U8(buf[0])
560 }
561 PcdFieldType::I16 => {
562 let mut buf = [0u8; 2];
563 reader.read_exact(&mut buf)?;
564 PcdValue::I16(i16::from_le_bytes(buf))
565 }
566 PcdFieldType::U16 => {
567 let mut buf = [0u8; 2];
568 reader.read_exact(&mut buf)?;
569 PcdValue::U16(u16::from_le_bytes(buf))
570 }
571 PcdFieldType::I32 => {
572 let mut buf = [0u8; 4];
573 reader.read_exact(&mut buf)?;
574 PcdValue::I32(i32::from_le_bytes(buf))
575 }
576 PcdFieldType::U32 => {
577 let mut buf = [0u8; 4];
578 reader.read_exact(&mut buf)?;
579 PcdValue::U32(u32::from_le_bytes(buf))
580 }
581 PcdFieldType::F32 => {
582 let mut buf = [0u8; 4];
583 reader.read_exact(&mut buf)?;
584 PcdValue::F32(f32::from_le_bytes(buf))
585 }
586 PcdFieldType::F64 => {
587 let mut buf = [0u8; 8];
588 reader.read_exact(&mut buf)?;
589 PcdValue::F64(f64::from_le_bytes(buf))
590 }
591 };
592
593 field_values.push(value);
594 }
595
596 Ok(field_values)
597 }
598
599 pub fn pcd_to_point_cloud(
601 _header: &PcdHeader,
602 points: &[PcdPoint],
603 ) -> Result<PointCloud<Point3f>> {
604 let mut cloud_points = Vec::with_capacity(points.len());
605
606 for point in points {
607 let x_values = point.get("x").ok_or_else(|| {
609 Error::InvalidData("Missing x coordinate in PCD point".to_string())
610 })?;
611 let y_values = point.get("y").ok_or_else(|| {
612 Error::InvalidData("Missing y coordinate in PCD point".to_string())
613 })?;
614 let z_values = point.get("z").ok_or_else(|| {
615 Error::InvalidData("Missing z coordinate in PCD point".to_string())
616 })?;
617
618 if x_values.is_empty() || y_values.is_empty() || z_values.is_empty() {
619 return Err(Error::InvalidData(
620 "Empty coordinate values in PCD point".to_string(),
621 ));
622 }
623
624 let x = Self::pcd_value_to_f64(&x_values[0])?;
625 let y = Self::pcd_value_to_f64(&y_values[0])?;
626 let z = Self::pcd_value_to_f64(&z_values[0])?;
627
628 cloud_points.push(Point3f::new(x as f32, y as f32, z as f32));
629 }
630
631 Ok(PointCloud::from_points(cloud_points))
632 }
633
634 fn pcd_value_to_f64(value: &PcdValue) -> Result<f64> {
636 match value {
637 PcdValue::I8(v) => Ok(*v as f64),
638 PcdValue::U8(v) => Ok(*v as f64),
639 PcdValue::I16(v) => Ok(*v as f64),
640 PcdValue::U16(v) => Ok(*v as f64),
641 PcdValue::I32(v) => Ok(*v as f64),
642 PcdValue::U32(v) => Ok(*v as f64),
643 PcdValue::F32(v) => Ok(*v as f64),
644 PcdValue::F64(v) => Ok(*v),
645 }
646 }
647}
648
649pub struct RobustPcdWriter;
651
652impl RobustPcdWriter {
653 pub fn write_point_cloud<P: AsRef<Path>>(
655 cloud: &PointCloud<Point3f>,
656 path: P,
657 options: &PcdWriteOptions,
658 ) -> Result<()> {
659 let file = File::create(path)?;
660 let mut writer = std::io::BufWriter::new(file);
661 Self::write_point_cloud_to_writer(cloud, &mut writer, options)
662 }
663
664 pub fn write_point_cloud_to_writer<W: Write>(
666 cloud: &PointCloud<Point3f>,
667 writer: &mut W,
668 options: &PcdWriteOptions,
669 ) -> Result<()> {
670 let mut fields = vec![
672 PcdField {
673 name: "x".to_string(),
674 field_type: PcdFieldType::F32,
675 count: 1,
676 },
677 PcdField {
678 name: "y".to_string(),
679 field_type: PcdFieldType::F32,
680 count: 1,
681 },
682 PcdField {
683 name: "z".to_string(),
684 field_type: PcdFieldType::F32,
685 count: 1,
686 },
687 ];
688
689 fields.extend(options.additional_fields.clone());
691
692 let header = PcdHeader {
693 version: options.version.clone(),
694 fields,
695 width: cloud.len(),
696 height: 1,
697 viewpoint: options
698 .viewpoint
699 .unwrap_or([0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0]),
700 data_format: options.data_format,
701 };
702
703 Self::write_header(writer, &header)?;
705
706 match options.data_format {
708 PcdDataFormat::Ascii => Self::write_ascii_data(writer, cloud, &header),
709 PcdDataFormat::Binary => Self::write_binary_data(writer, cloud, &header),
710 PcdDataFormat::BinaryCompressed => Err(Error::Unsupported(
711 "Binary compressed PCD format not yet supported".to_string(),
712 )),
713 }
714 }
715
716 fn write_header<W: Write>(writer: &mut W, header: &PcdHeader) -> Result<()> {
718 writeln!(
719 writer,
720 "# .PCD v{} - Point Cloud Data file format",
721 header.version
722 )?;
723 writeln!(writer, "VERSION {}", header.version)?;
724 write!(writer, "FIELDS")?;
725 for field in &header.fields {
726 write!(writer, " {}", field.name)?;
727 }
728 writeln!(writer)?;
729
730 write!(writer, "SIZE")?;
731 for field in &header.fields {
732 let size = match field.field_type {
733 PcdFieldType::I8 | PcdFieldType::U8 => 1,
734 PcdFieldType::I16 | PcdFieldType::U16 => 2,
735 PcdFieldType::I32 | PcdFieldType::U32 | PcdFieldType::F32 => 4,
736 PcdFieldType::F64 => 8,
737 };
738 write!(writer, " {}", size)?;
739 }
740 writeln!(writer)?;
741
742 write!(writer, "TYPE")?;
743 for field in &header.fields {
744 let type_char = match field.field_type {
745 PcdFieldType::I8 | PcdFieldType::I16 | PcdFieldType::I32 => "I",
746 PcdFieldType::U8 | PcdFieldType::U16 | PcdFieldType::U32 => "U",
747 PcdFieldType::F32 | PcdFieldType::F64 => "F",
748 };
749 write!(writer, " {}", type_char)?;
750 }
751 writeln!(writer)?;
752
753 write!(writer, "COUNT")?;
754 for field in &header.fields {
755 write!(writer, " {}", field.count)?;
756 }
757 writeln!(writer)?;
758
759 writeln!(writer, "WIDTH {}", header.width)?;
760 writeln!(writer, "HEIGHT {}", header.height)?;
761 writeln!(
762 writer,
763 "VIEWPOINT {} {} {} {} {} {} {}",
764 header.viewpoint[0],
765 header.viewpoint[1],
766 header.viewpoint[2],
767 header.viewpoint[3],
768 header.viewpoint[4],
769 header.viewpoint[5],
770 header.viewpoint[6]
771 )?;
772 writeln!(writer, "POINTS {}", header.width * header.height)?;
773
774 let data_str = match header.data_format {
775 PcdDataFormat::Ascii => "ascii",
776 PcdDataFormat::Binary => "binary",
777 PcdDataFormat::BinaryCompressed => "binary_compressed",
778 };
779 writeln!(writer, "DATA {}", data_str)?;
780
781 Ok(())
782 }
783
784 fn write_ascii_data<W: Write>(
786 writer: &mut W,
787 cloud: &PointCloud<Point3f>,
788 _header: &PcdHeader,
789 ) -> Result<()> {
790 for point in cloud.iter() {
791 write!(writer, "{} {} {}", point.x, point.y, point.z)?;
793
794 writeln!(writer)?;
803 }
804
805 Ok(())
806 }
807
808 fn write_binary_data<W: Write>(
810 writer: &mut W,
811 cloud: &PointCloud<Point3f>,
812 _header: &PcdHeader,
813 ) -> Result<()> {
814 for point in cloud.iter() {
815 writer.write_all(&point.x.to_le_bytes())?;
817 writer.write_all(&point.y.to_le_bytes())?;
818 writer.write_all(&point.z.to_le_bytes())?;
819
820 }
833
834 Ok(())
835 }
836}
837
838pub struct PcdReader;
840
841impl RegistryPointCloudReader for PcdReader {
842 fn read_point_cloud(&self, path: &Path) -> Result<PointCloud<Point3f>> {
843 let (header, points) = RobustPcdReader::read_pcd_file(path)?;
844 RobustPcdReader::pcd_to_point_cloud(&header, &points)
845 }
846
847 fn can_read(&self, path: &Path) -> bool {
848 path.extension()
849 .and_then(|ext| ext.to_str())
850 .map(|ext| ext.to_lowercase() == "pcd")
851 .unwrap_or(false)
852 }
853
854 fn format_name(&self) -> &'static str {
855 "pcd"
856 }
857}
858
859pub struct PcdWriter;
861
862impl RegistryPointCloudWriter for PcdWriter {
863 fn write_point_cloud(&self, cloud: &PointCloud<Point3f>, path: &Path) -> Result<()> {
864 let options = PcdWriteOptions::default();
865 RobustPcdWriter::write_point_cloud(cloud, path, &options)
866 }
867
868 fn format_name(&self) -> &'static str {
869 "pcd"
870 }
871}
872
873impl PointCloudReader for PcdReader {
875 fn read_point_cloud<P: AsRef<Path>>(path: P) -> Result<PointCloud<Point3f>> {
876 let reader = PcdReader;
877 RegistryPointCloudReader::read_point_cloud(&reader, path.as_ref())
878 }
879}
880
881impl PointCloudWriter for PcdWriter {
882 fn write_point_cloud<P: AsRef<Path>>(cloud: &PointCloud<Point3f>, path: P) -> Result<()> {
883 let writer = PcdWriter;
884 RegistryPointCloudWriter::write_point_cloud(&writer, cloud, path.as_ref())
885 }
886}