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(crate::capacity_hint(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(crate::capacity_hint(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 let total = width.checked_mul(height).ok_or_else(|| {
401 Error::InvalidData(format!("PCD WIDTH {width} x HEIGHT {height} is too large"))
402 })?;
403 if let Some(points) = points {
405 if points != total {
406 return Err(Error::InvalidData(format!(
407 "POINTS ({points}) doesn't match WIDTH * HEIGHT ({total})"
408 )));
409 }
410 }
411
412 Ok(PcdHeader {
413 version,
414 fields,
415 width,
416 height,
417 viewpoint,
418 data_format,
419 })
420 }
421
422 fn read_points<R: BufRead>(reader: &mut R, header: &PcdHeader) -> Result<Vec<PcdPoint>> {
424 match header.data_format {
425 PcdDataFormat::Ascii => Self::read_ascii_points(reader, header),
426 PcdDataFormat::Binary => Self::read_binary_points(reader, header),
427 PcdDataFormat::BinaryCompressed => Err(Error::Unsupported(
428 "Binary compressed PCD format not yet supported".to_string(),
429 )),
430 }
431 }
432
433 fn read_ascii_points<R: BufRead>(reader: &mut R, header: &PcdHeader) -> Result<Vec<PcdPoint>> {
435 let total = header.width * header.height;
436 let mut points = Vec::with_capacity(crate::capacity_hint(total));
437
438 while points.len() < total {
440 let mut line = String::new();
441 if reader.read_line(&mut line)? == 0 {
442 return Err(Error::InvalidData(format!(
443 "PCD data ended after {} of {total} points",
444 points.len()
445 )));
446 }
447 let line = line.trim();
448
449 if line.is_empty() {
450 continue;
451 }
452
453 let values: Vec<&str> = line.split_whitespace().collect();
454 let mut value_idx = 0;
455 let mut point = PcdPoint::new();
456
457 for field in &header.fields {
458 let field_values = Self::read_ascii_field_values(&values, &mut value_idx, field)?;
459 point.insert(field.name.clone(), field_values);
460 }
461
462 points.push(point);
463 }
464
465 Ok(points)
466 }
467
468 fn read_binary_points<R: Read>(reader: &mut R, header: &PcdHeader) -> Result<Vec<PcdPoint>> {
470 let mut points = Vec::with_capacity(crate::capacity_hint(header.width * header.height));
471
472 for _ in 0..(header.width * header.height) {
473 let mut point = PcdPoint::new();
474
475 for field in &header.fields {
476 let field_values = Self::read_binary_field_values(reader, field)?;
477 point.insert(field.name.clone(), field_values);
478 }
479
480 points.push(point);
481 }
482
483 Ok(points)
484 }
485
486 fn read_ascii_field_values(
488 values: &[&str],
489 value_idx: &mut usize,
490 field: &PcdField,
491 ) -> Result<Vec<PcdValue>> {
492 let mut field_values = Vec::with_capacity(crate::capacity_hint(field.count));
493
494 for _ in 0..field.count {
495 if *value_idx >= values.len() {
496 return Err(Error::InvalidData(
497 "Not enough values in ASCII PCD line".to_string(),
498 ));
499 }
500
501 let value = match field.field_type {
502 PcdFieldType::I8 => {
503 PcdValue::I8(values[*value_idx].parse::<i8>().map_err(|_| {
504 Error::InvalidData(format!("Invalid I8 value: {}", values[*value_idx]))
505 })?)
506 }
507 PcdFieldType::U8 => {
508 PcdValue::U8(values[*value_idx].parse::<u8>().map_err(|_| {
509 Error::InvalidData(format!("Invalid U8 value: {}", values[*value_idx]))
510 })?)
511 }
512 PcdFieldType::I16 => {
513 PcdValue::I16(values[*value_idx].parse::<i16>().map_err(|_| {
514 Error::InvalidData(format!("Invalid I16 value: {}", values[*value_idx]))
515 })?)
516 }
517 PcdFieldType::U16 => {
518 PcdValue::U16(values[*value_idx].parse::<u16>().map_err(|_| {
519 Error::InvalidData(format!("Invalid U16 value: {}", values[*value_idx]))
520 })?)
521 }
522 PcdFieldType::I32 => {
523 PcdValue::I32(values[*value_idx].parse::<i32>().map_err(|_| {
524 Error::InvalidData(format!("Invalid I32 value: {}", values[*value_idx]))
525 })?)
526 }
527 PcdFieldType::U32 => {
528 PcdValue::U32(values[*value_idx].parse::<u32>().map_err(|_| {
529 Error::InvalidData(format!("Invalid U32 value: {}", values[*value_idx]))
530 })?)
531 }
532 PcdFieldType::F32 => {
533 PcdValue::F32(values[*value_idx].parse::<f32>().map_err(|_| {
534 Error::InvalidData(format!("Invalid F32 value: {}", values[*value_idx]))
535 })?)
536 }
537 PcdFieldType::F64 => {
538 PcdValue::F64(values[*value_idx].parse::<f64>().map_err(|_| {
539 Error::InvalidData(format!("Invalid F64 value: {}", values[*value_idx]))
540 })?)
541 }
542 };
543
544 field_values.push(value);
545 *value_idx += 1;
546 }
547
548 Ok(field_values)
549 }
550
551 fn read_binary_field_values<R: Read>(
553 reader: &mut R,
554 field: &PcdField,
555 ) -> Result<Vec<PcdValue>> {
556 let mut field_values = Vec::with_capacity(crate::capacity_hint(field.count));
557
558 for _ in 0..field.count {
559 let value = match field.field_type {
560 PcdFieldType::I8 => {
561 let mut buf = [0u8; 1];
562 reader.read_exact(&mut buf)?;
563 PcdValue::I8(buf[0] as i8)
564 }
565 PcdFieldType::U8 => {
566 let mut buf = [0u8; 1];
567 reader.read_exact(&mut buf)?;
568 PcdValue::U8(buf[0])
569 }
570 PcdFieldType::I16 => {
571 let mut buf = [0u8; 2];
572 reader.read_exact(&mut buf)?;
573 PcdValue::I16(i16::from_le_bytes(buf))
574 }
575 PcdFieldType::U16 => {
576 let mut buf = [0u8; 2];
577 reader.read_exact(&mut buf)?;
578 PcdValue::U16(u16::from_le_bytes(buf))
579 }
580 PcdFieldType::I32 => {
581 let mut buf = [0u8; 4];
582 reader.read_exact(&mut buf)?;
583 PcdValue::I32(i32::from_le_bytes(buf))
584 }
585 PcdFieldType::U32 => {
586 let mut buf = [0u8; 4];
587 reader.read_exact(&mut buf)?;
588 PcdValue::U32(u32::from_le_bytes(buf))
589 }
590 PcdFieldType::F32 => {
591 let mut buf = [0u8; 4];
592 reader.read_exact(&mut buf)?;
593 PcdValue::F32(f32::from_le_bytes(buf))
594 }
595 PcdFieldType::F64 => {
596 let mut buf = [0u8; 8];
597 reader.read_exact(&mut buf)?;
598 PcdValue::F64(f64::from_le_bytes(buf))
599 }
600 };
601
602 field_values.push(value);
603 }
604
605 Ok(field_values)
606 }
607
608 pub fn pcd_to_point_cloud(
610 _header: &PcdHeader,
611 points: &[PcdPoint],
612 ) -> Result<PointCloud<Point3f>> {
613 let mut cloud_points = Vec::with_capacity(points.len());
614
615 for point in points {
616 let x_values = point.get("x").ok_or_else(|| {
618 Error::InvalidData("Missing x coordinate in PCD point".to_string())
619 })?;
620 let y_values = point.get("y").ok_or_else(|| {
621 Error::InvalidData("Missing y coordinate in PCD point".to_string())
622 })?;
623 let z_values = point.get("z").ok_or_else(|| {
624 Error::InvalidData("Missing z coordinate in PCD point".to_string())
625 })?;
626
627 if x_values.is_empty() || y_values.is_empty() || z_values.is_empty() {
628 return Err(Error::InvalidData(
629 "Empty coordinate values in PCD point".to_string(),
630 ));
631 }
632
633 let x = Self::pcd_value_to_f64(&x_values[0])?;
634 let y = Self::pcd_value_to_f64(&y_values[0])?;
635 let z = Self::pcd_value_to_f64(&z_values[0])?;
636
637 cloud_points.push(Point3f::new(x as f32, y as f32, z as f32));
638 }
639
640 Ok(PointCloud::from_points(cloud_points))
641 }
642
643 fn pcd_value_to_f64(value: &PcdValue) -> Result<f64> {
645 match value {
646 PcdValue::I8(v) => Ok(*v as f64),
647 PcdValue::U8(v) => Ok(*v as f64),
648 PcdValue::I16(v) => Ok(*v as f64),
649 PcdValue::U16(v) => Ok(*v as f64),
650 PcdValue::I32(v) => Ok(*v as f64),
651 PcdValue::U32(v) => Ok(*v as f64),
652 PcdValue::F32(v) => Ok(*v as f64),
653 PcdValue::F64(v) => Ok(*v),
654 }
655 }
656}
657
658pub struct RobustPcdWriter;
660
661impl RobustPcdWriter {
662 pub fn write_point_cloud<P: AsRef<Path>>(
664 cloud: &PointCloud<Point3f>,
665 path: P,
666 options: &PcdWriteOptions,
667 ) -> Result<()> {
668 let file = File::create(path)?;
669 let mut writer = std::io::BufWriter::new(file);
670 Self::write_point_cloud_to_writer(cloud, &mut writer, options)
671 }
672
673 pub fn write_point_cloud_to_writer<W: Write>(
675 cloud: &PointCloud<Point3f>,
676 writer: &mut W,
677 options: &PcdWriteOptions,
678 ) -> Result<()> {
679 let mut fields = vec![
681 PcdField {
682 name: "x".to_string(),
683 field_type: PcdFieldType::F32,
684 count: 1,
685 },
686 PcdField {
687 name: "y".to_string(),
688 field_type: PcdFieldType::F32,
689 count: 1,
690 },
691 PcdField {
692 name: "z".to_string(),
693 field_type: PcdFieldType::F32,
694 count: 1,
695 },
696 ];
697
698 fields.extend(options.additional_fields.clone());
700
701 let header = PcdHeader {
702 version: options.version.clone(),
703 fields,
704 width: cloud.len(),
705 height: 1,
706 viewpoint: options
707 .viewpoint
708 .unwrap_or([0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0]),
709 data_format: options.data_format,
710 };
711
712 Self::write_header(writer, &header)?;
714
715 match options.data_format {
717 PcdDataFormat::Ascii => Self::write_ascii_data(writer, cloud, &header),
718 PcdDataFormat::Binary => Self::write_binary_data(writer, cloud, &header),
719 PcdDataFormat::BinaryCompressed => Err(Error::Unsupported(
720 "Binary compressed PCD format not yet supported".to_string(),
721 )),
722 }
723 }
724
725 fn write_header<W: Write>(writer: &mut W, header: &PcdHeader) -> Result<()> {
727 writeln!(
728 writer,
729 "# .PCD v{} - Point Cloud Data file format",
730 header.version
731 )?;
732 writeln!(writer, "VERSION {}", header.version)?;
733 write!(writer, "FIELDS")?;
734 for field in &header.fields {
735 write!(writer, " {}", field.name)?;
736 }
737 writeln!(writer)?;
738
739 write!(writer, "SIZE")?;
740 for field in &header.fields {
741 let size = match field.field_type {
742 PcdFieldType::I8 | PcdFieldType::U8 => 1,
743 PcdFieldType::I16 | PcdFieldType::U16 => 2,
744 PcdFieldType::I32 | PcdFieldType::U32 | PcdFieldType::F32 => 4,
745 PcdFieldType::F64 => 8,
746 };
747 write!(writer, " {}", size)?;
748 }
749 writeln!(writer)?;
750
751 write!(writer, "TYPE")?;
752 for field in &header.fields {
753 let type_char = match field.field_type {
754 PcdFieldType::I8 | PcdFieldType::I16 | PcdFieldType::I32 => "I",
755 PcdFieldType::U8 | PcdFieldType::U16 | PcdFieldType::U32 => "U",
756 PcdFieldType::F32 | PcdFieldType::F64 => "F",
757 };
758 write!(writer, " {}", type_char)?;
759 }
760 writeln!(writer)?;
761
762 write!(writer, "COUNT")?;
763 for field in &header.fields {
764 write!(writer, " {}", field.count)?;
765 }
766 writeln!(writer)?;
767
768 writeln!(writer, "WIDTH {}", header.width)?;
769 writeln!(writer, "HEIGHT {}", header.height)?;
770 writeln!(
771 writer,
772 "VIEWPOINT {} {} {} {} {} {} {}",
773 header.viewpoint[0],
774 header.viewpoint[1],
775 header.viewpoint[2],
776 header.viewpoint[3],
777 header.viewpoint[4],
778 header.viewpoint[5],
779 header.viewpoint[6]
780 )?;
781 writeln!(writer, "POINTS {}", header.width * header.height)?;
782
783 let data_str = match header.data_format {
784 PcdDataFormat::Ascii => "ascii",
785 PcdDataFormat::Binary => "binary",
786 PcdDataFormat::BinaryCompressed => "binary_compressed",
787 };
788 writeln!(writer, "DATA {}", data_str)?;
789
790 Ok(())
791 }
792
793 fn write_ascii_data<W: Write>(
795 writer: &mut W,
796 cloud: &PointCloud<Point3f>,
797 _header: &PcdHeader,
798 ) -> Result<()> {
799 for point in cloud.iter() {
800 write!(writer, "{} {} {}", point.x, point.y, point.z)?;
802
803 writeln!(writer)?;
812 }
813
814 Ok(())
815 }
816
817 fn write_binary_data<W: Write>(
819 writer: &mut W,
820 cloud: &PointCloud<Point3f>,
821 _header: &PcdHeader,
822 ) -> Result<()> {
823 for point in cloud.iter() {
824 writer.write_all(&point.x.to_le_bytes())?;
826 writer.write_all(&point.y.to_le_bytes())?;
827 writer.write_all(&point.z.to_le_bytes())?;
828
829 }
842
843 Ok(())
844 }
845}
846
847pub struct PcdReader;
849
850impl RegistryPointCloudReader for PcdReader {
851 fn read_point_cloud(&self, path: &Path) -> Result<PointCloud<Point3f>> {
852 let (header, points) = RobustPcdReader::read_pcd_file(path)?;
853 RobustPcdReader::pcd_to_point_cloud(&header, &points)
854 }
855
856 fn can_read(&self, path: &Path) -> bool {
857 path.extension()
858 .and_then(|ext| ext.to_str())
859 .map(|ext| ext.to_lowercase() == "pcd")
860 .unwrap_or(false)
861 }
862
863 fn format_name(&self) -> &'static str {
864 "pcd"
865 }
866}
867
868pub struct PcdWriter;
870
871impl RegistryPointCloudWriter for PcdWriter {
872 fn write_point_cloud(&self, cloud: &PointCloud<Point3f>, path: &Path) -> Result<()> {
873 let options = PcdWriteOptions::default();
874 RobustPcdWriter::write_point_cloud(cloud, path, &options)
875 }
876
877 fn format_name(&self) -> &'static str {
878 "pcd"
879 }
880}
881
882impl PointCloudReader for PcdReader {
884 fn read_point_cloud<P: AsRef<Path>>(path: P) -> Result<PointCloud<Point3f>> {
885 let reader = PcdReader;
886 RegistryPointCloudReader::read_point_cloud(&reader, path.as_ref())
887 }
888}
889
890impl PointCloudWriter for PcdWriter {
891 fn write_point_cloud<P: AsRef<Path>>(cloud: &PointCloud<Point3f>, path: P) -> Result<()> {
892 let writer = PcdWriter;
893 RegistryPointCloudWriter::write_point_cloud(&writer, cloud, path.as_ref())
894 }
895}